cycler
cycler copied to clipboard
Allow getitem from a cycler
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?
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 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.
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.
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.