cycler icon indicating copy to clipboard operation
cycler copied to clipboard

Add cycles of differing lengths

Open moi90 opened this issue 8 years ago • 10 comments

It would be nice to be able to add cycles of differing lengths whose properties cycle independently.

moi90 avatar Feb 14 '17 09:02 moi90

I am not sure we want to support that in the implementation of + as I do not think this is the typical use-case.

You can write a helper function for this pretty easily:

def force_add(cyc_a, cyc_b):
    try:
        return cyc_a + cyc_b
    except ValueError:
        return cyc_a * len(cyc_b) + cyc_b * len(cyc_a)

Might be worth adding a version of this (that uses least-common-multiple?) to the docs?

tacaswell avatar Feb 14 '17 19:02 tacaswell

Another operator could be used for this, maybe & or /? Or ** due to the similarity to *.

edit & is already suggested as concatenation operator in #1.

zommuter avatar Aug 08 '17 20:08 zommuter

What is the use-case for this?

tacaswell avatar Aug 09 '17 01:08 tacaswell

As an example, say you want to cycle through three colors [rgb] and four linestyles ["-",":","--","-.",]. That already gives you twelve unique combinations which helps better differentiation of many lines. I think I saw a post of yourself on StackOverflow where you mentioned how one can simply use

cycler('color', list('rbg')*4) + cycler('linestyle', ['-', '--', ':', '-.']*3)

But another operator to automate this would save code. And in contrast to the multiplication of two cyclers you'd cycle through both styles simultaneously instead of sequentially.

zommuter avatar Aug 09 '17 06:08 zommuter

Yes please. I think this is a very common use-case for publishing where best-practices for reproducibility assume colors can't be distinguished, and so require unique combinations of marker and linestyle type properties.

bjmuld avatar Jan 14 '19 11:01 bjmuld

In what use-case is the exception more beneficial?

bjmuld avatar Jan 14 '19 11:01 bjmuld

There is an ambiguity in adding two un-equal length cyclers. @bjmuld is making the case for implicitly extending it so a + b -> a * len(b) + b * len(a) (the actual implementation would be a bit more clever and trim to the LCM).

On the other hand, you could make the argument that a + b -> a[:min(len(a), len(b)] + b[:min(len(a), len(b)) which would trim the cycle to the minimum length of the two cycles you are adding. This is consistent with what zip does.

We chose to raise because it is not clear (at least to me) which of those two behaviors is the "expected" one. On one hand, there is clearly a use for the LCM version, but on other hand the min-length version is consistent with zip. By raising cycler is telling the user "you asked me to do something ambiguous, please clarify".

Maybe the LCM version is a candidate for @?

tacaswell avatar Jan 15 '19 14:01 tacaswell

Actually, it depends on the intention if you want MIN or LCM.

a) Adding a secondary property -> MIN Assume you have a line a line plot with color=['r', 'g', 'b'] and you want to add a line style to make sure the lines can still be distinguished in black/white. And assume that you add linestyle=['-', '--']. LCM would draw the third line 'b-' while MIN would draw the third line 'r-' as the first line. In the LCM case you might not notice that linestyle is the same when looking at the colored plot. MIN is better here as it wouldn't allow same secondary properties (ls) with different primary properties (color).

b) Creating more variety -> LCM Actually, I don't see a usecase for LCM. More variety and the example mentioned above are best handled by the product operator cycler(color=['r', 'g', 'b']) * cycler(linestyle=['-', '--', ':', '-.']).

When really thinking about it, LCM is a bit strange: We want to change both properties synchronously for every element (without the synchronous requirement we can simply use *). This implies a certain relation between the i-th properties. But then after one cycle we don't care about the initial relation anymore.

I would be fine with implementing the zip-like MIN behavior for +.

timhoffm avatar Jan 15 '19 15:01 timhoffm

Actually, I don't see a usecase for LCM. More variety and the example mentioned above are best handled by the product operator cycler(color=['r', 'g', 'b']) * cycler(linestyle=['-', '--', ':', '-.']).

~~The product operator returns a cycler of length LCM(a,b), so I'm confused by the "LCM" vs. "+" vs "*" taxonomy above~~ [edit: is it just a non-simplified a*b ].

My only displeasure in the product operator is the order of the generated sequences. having 'b-' followed by 'r.' in sequence is more useful to me than 'b-' followed by 'b.' To achieve this in the present implementation requires careful manual creation(/extension) of the property lists in order to ensure their lengths match... which is more painful as the required number of unique attribute sets increase.

bjmuld avatar Jan 15 '19 17:01 bjmuld

For sequences whose lengths are relatively * and LCM+ have the same values re-ordered, however for pairs who's lengths are not relatively prime the LCM version will never pick out some pairs (ex, for a len2 and len6 sequence, you will never get the second element of the short sequence with the (0, 2, 4) entries in the longer one. You could go down the path of using space filling curves (ex https://en.wikipedia.org/wiki/Peano_curve) to provide a different path through space but it is not may not be an obvious path (and may in the end be more confusing).

tacaswell avatar Jan 16 '19 19:01 tacaswell