cycler icon indicating copy to clipboard operation
cycler copied to clipboard

Allow getitem from a cycler

Open tritemio opened this issue 8 years ago • 4 comments

It would be useful to add __getitem__ functionality to cycler.

The use case is a "crowded" for loop where you want to get the cycler style without using a loop variable. Something like:

data_cycler = cycler('alpha', (0.2, 0.4, 0.6)) + cycler('color', ('r', 'g', 'b'))
fit_cycler = cycler('color', ('k', 'm', 'orange'))

for i, (data, fit) in enumerate(zip(data_list, fit_list)):
    ...
    plt.plot('x', 'y', data=data, data_cycler[i])
    plt.plot('x', 'y', data=data, fit_cycler[i])
    ...

In this case the index i is computed anyway, so using it for the cyclers would be handy, instead of adding the cycler inside zip().

Ideally mycycler[i] should return the item with index i % len(mycycler).

I haven't looked at the code yet. Would this be possible? Would a PR be welcome?

tritemio avatar Jan 26 '16 02:01 tritemio

Cycler already has some support for __getitem__ which lets you slice it, ex cy2 = cy[::2] but it does not work on a single element. (http://matplotlib.org/cycler/#slicing)

I am not convinced that using index is clearer than using zip, somethig like

styles = zip(data_cycler, fit_cycler)
values = zip(data_list, fit_list)
for vals, cys in zip(styles, values):
    for v, c in zip(vals, cys):
        ax.plot('x', 'y', data=v, **c)

is a bit clearer (and avoids what I assume is the typo where you meant data=fit in the second plot)

To do it efficiently you would probably have to hold a list of the transposed cycler (ex list(cy)), right now when you iterate over a cycler everything is lazy. It might be simpler to in these cases hold a listified version of the cycler, the main win of cycler is the composition, not necessarily it's use as a special snow-flake object.

That said, we would have use of this functionality upstream in mpl.

I am -0 on this, I won't merge it, but will not block someone else from doing so.

tacaswell avatar Jan 26 '16 02:01 tacaswell

@tacaswell yep, I fixed the typo in the example. Anyway, it was just an example. The point is, there are legitimate situations where indexing a cycler can be convenient.

Another example more similar to what I'm doing ATM. I keep a few datatsets in dictionaries whose keys (the same for all dict) are parameters. Would be nice to be able to plot them like this (let's pretend I still have fit and data curves):

for i, (param, data) in enumerate(sorted(data_dict.items())):
    ax.plot('x', 'y', data=data, **data_cycler[i])
    ax.plot(data.x, model_func(data.x **fit_dict[param]), **fit_cycler[i])
    ax.text(1, 0.9 - i*0.1, 'parameter = %d, tau = %.1fs' % (param, fit_dict[param]['tau']))

instead of:

for i, (param, datasty, fitsty) in enumerate(zip(sorted(data_dict), data_cycler, fit_cycler)):
    ax.plot('x', 'y', data=data_dict[param], **datasty)
    ax.plot(data.x, model_func(data.x **fit_dict[param]), **fitsty)
    ax.text(1, 0.9 - i*0.1, 'parameter = %d, tau = %.1fs' % (param, fit_dict[param]['tau']))

All these "style" variables in the latter example are just visual noise, distracting from the important thing: the data I'm looping upon.

tritemio avatar Jan 26 '16 07:01 tritemio

The example at https://github.com/matplotlib/cycler/blob/master/doc/source/index.rst#persistent-cycles (which needs to get rebuilt and pushed to the main page) I think captures your second use case in a nicer way.

tacaswell avatar Feb 14 '16 19:02 tacaswell

I think that the defaultdict construction addresses this use case.

Do we want wrap it in a function and ship it with cycler? I am leaning towards yes.

tacaswell avatar Jun 04 '16 18:06 tacaswell