83

Say I have data about 3 trading strategies, each with and without transaction costs. I want to plot, on the same axes, the time series of each of the 6 variants (3 strategies * 2 trading costs). I would like the "with transaction cost" lines to be plotted with alpha=1 and linewidth=1 while I want the "no transaction costs" to be plotted with alpha=0.25 and linewidth=5. But I would like the color to be the same for both versions of each strategy.

I would like something along the lines of:

fig, ax = plt.subplots(1, 1, figsize=(10, 10))

for c in with_transaction_frame.columns:
    ax.plot(with_transaction_frame[c], label=c, alpha=1, linewidth=1)

****SOME MAGIC GOES HERE TO RESET THE COLOR CYCLE

for c in no_transaction_frame.columns:
    ax.plot(no_transaction_frame[c], label=c, alpha=0.25, linewidth=5)

ax.legend()

What is the appropriate code to put on the indicated line to reset the color cycle so it is "back to the start" when the second loop is invoked?

8one6
  • 13,078
  • 12
  • 62
  • 84

6 Answers6

113

You can reset the colorcycle to the original with Axes.set_color_cycle. Looking at the code for this, there is a function to do the actual work:

def set_color_cycle(self, clist=None):
    if clist is None:
        clist = rcParams['axes.color_cycle']
    self.color_cycle = itertools.cycle(clist

And a method on the Axes which uses it:

def set_color_cycle(self, clist):
    """
    Set the color cycle for any future plot commands on this Axes.

    *clist* is a list of mpl color specifiers.
    """
    self._get_lines.set_color_cycle(clist)
    self._get_patches_for_fill.set_color_cycle(clist)

This basically means you can call the set_color_cycle with None as the only argument, and it will be replaced with the default cycle found in rcParams['axes.color_cycle'].

I tried this with the following code and got the expected result:

import matplotlib.pyplot as plt
import numpy as np

for i in range(3):
    plt.plot(np.arange(10) + i)

# for Matplotlib version < 1.5
plt.gca().set_color_cycle(None)
# for Matplotlib version >= 1.5
plt.gca().set_prop_cycle(None)

for i in range(3):
    plt.plot(np.arange(10, 1, -1) + i)

plt.show()

Code output, showing the color cycling reset functionality

gg349
  • 21,996
  • 5
  • 54
  • 64
pelson
  • 21,252
  • 4
  • 92
  • 99
  • Thanks @8one6. Matplotlib is incredibly powerful when you know how - I think the real problem is that power doesn't document so well, so IMHO a really important skill with open source Python packages is to be able to follow the actual implementation/code. It really isn't that complex - I imagine it is just daunting to do the first time... – pelson Jun 25 '14 at 08:55
  • 20
    Since Matplotlib 1.5.0, `set_color_cycle` is deprecated and does not accept `None` anymore! Luckily, the new (broader) alternative `set_prop_cycle` *does* accept `None` still... – burnpanck Nov 13 '15 at 03:15
  • 3
    Setting set_prop_cycle to None also gets rid of the labels. Is there a way to only reset the colors? plt.gca().set_prop_cycle(color=None) does not seem to work. – Chogg Nov 02 '18 at 17:26
39

As the answer given by @pelson uses set_color_cycle and this is deprecated in Matplotlib 1.5, I thought it would be useful to have an updated version of his solution using set_prop_cycle:

import matplotlib.pyplot as plt
import numpy as np

for i in range(3):
    plt.plot(np.arange(10) + i)

plt.gca().set_prop_cycle(None)

for i in range(3):
    plt.plot(np.arange(10, 0, -1) + i)

plt.show()

Remark also that I had to change np.arange(10,1,-1) to np.arange(10,0,-1). The former gave an array of only 9 elements. This probably arises from using different Numpy versions. Mine is 1.10.2.

EDIT: Removed the need to use rcParams. Thanks to @divenex for pointing that out in a comment.

Ramon Crehuet
  • 3,679
  • 1
  • 22
  • 37
  • 2
    Contrary to what stated in this answer `plt.gca().set_prop_cycle(None)` works from Matplotlib 1.5 (as pointed out by @burnpanck) and I just verified it works in Matplotlib 2.0 too. – divenex Feb 10 '17 at 12:50
  • 1
    "deprecated" means it will probably be removed in the future, even if it works in the current version. See http://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.set_color_cycle.html – Ramon Crehuet Feb 13 '17 at 18:45
  • 1
    My comment is not about the deprecation, but about the fact that there is no need to specify the cycler in `set_prop_cycle`. – divenex Feb 14 '17 at 13:33
  • Sorry. You're completely right. I misread your comment. I've edited my answer – Ramon Crehuet Feb 17 '17 at 13:53
5

Since you mentioned you're using seaborn, what I would recommend doing is:

with sns.color_palette(n_colors=3):

    ax.plot(...)
    ax.plot(...)

This will set the color palette to use the currently active color cycle, but only the first three colors from it. It's also a general purpose solution for any time you want to set a temporary color cycle.

Note that the only thing that actually needs to be under the with block is whatever you are doing to create the Axes object (i.e. plt.subplots, fig.add_subplot(), etc.). This is just because of how the matplotlib color cycle itself works.

Doing what you specifically want, "resetting" the color cycle, is possible, but it's a hack and I wouldn't do it in any kind of production code. Here, though, is how it could happen:

f, ax = plt.subplots()
ax.plot(np.random.randn(10, 3))
ax._get_lines.color_cycle = itertools.cycle(sns.color_palette())
ax.plot(np.random.randn(10, 3), lw=5, alpha=.25)

enter image description here

mwaskom
  • 46,693
  • 16
  • 125
  • 127
  • Thanks for taking the time to write up this answer. I understand that this will work because I know a priori that I'll be plotting 3 series using each of the `ax.plot` commands above. But do you know if there's a general way to "reset" the color cycle at a given point in code? Without specific knowledge of what the color cycle is (or what its status is) at the point in code that command is issued? – 8one6 Jun 13 '14 at 03:44
  • 1
    It's possible to do, but it's a hack that I would not really recommend. See edit to answer. – mwaskom Jun 13 '14 at 16:11
  • 1
    I'd also point out that you should always be able to infer how many colors you need from the data. – mwaskom Jun 13 '14 at 16:30
  • This is very helpful (and I'll accept the answer). While you're right that I can infer the number of lines from the context, I was hoping to keep the code more readable. If there were literally a `reset_color_cycle` command, I think things would read very naturally. Actually, your 1-line 'hack' above doesn't bother me too much. Why don't you recommend its use in production? – 8one6 Jun 13 '14 at 20:18
  • In general you want to avoid using internal features (that by convention are methods or attributes where the name starts with a single underscore). That generally signals the API could change without warning. It's specifically a concern here because I know the matplotlib devs are talking about changing how the color cycle is implemented, and so it's possible this hack will not work on future versions of matplotlib. – mwaskom Jun 13 '14 at 20:47
  • Thanks for the great comment. Do you think this is a worthwhile feature request for matplotlib? – 8one6 Jun 14 '14 at 01:36
3

Simply choose your colours and assign them to a list, then when you plot your data iterate over a zip object containing your column and the colour you wish.

colors = ['red', 'blue', 'green']

for col, color in zip(colors, with_transaction_frame.columns):
    ax.plot(with_transaction_frame[col], label=col, alpha=1.0, linewidth=1.0, color=color)

for col, color in zip(no_transaction_frame.columns):
    ax.plot(no_transaction_frame[col], label=col, alpha=0.25, linewidth=5, color=color)

zip creates a list that aggregates the elements from each of your lists. This allows you to iterate over both easily at the same time.

Ffisegydd
  • 51,807
  • 15
  • 147
  • 125
  • you could actually build up that list of colors by calling `get_color` on the return of `ax.plot` in the first loop. – M4rtini Jun 12 '14 at 21:15
  • Kind of sidesteps the question. In my case, I'm working with `seaborn` and in general, there might be a complicated color palette default in place. I don't want to screw with that. I just want to plot twice with the same color cycle used each time...without needing to know what that color cycle is ahead of time. – 8one6 Jun 12 '14 at 21:30
  • Ok fair enough :) it's not really side-stepping the question as it's a perfectly valid and simple answer to the question as you stated it but if you're using seaborn then I can see how you wouldn't want to mess with the colors by choosing them manually. In this case I would do as @M4rtini suggests and use `get_color` to get the colors from the first plotting iteration and use them in the 2nd, possibly they may want to write that up as an answer for you. – Ffisegydd Jun 12 '14 at 21:33
  • Somehow i am not able to edit your answer, but could you insert a commata in `colors = ['red', 'blue', 'green']` ? – PhilipB Jul 20 '16 at 10:21
2

You can get the colors from seaborn like this: colors = sns.color_palette(). Ffisegydd's answer would then work great. You could also get the color to plot using the modulus/remainder operater (%): mycolor = colors[icolumn % len(colors]. I use often use this approach myself. So you could do:

for icol, column in enumerate(with_transaction_frame.columns): mycolor = colors[icol % len(colors] ax.plot(with_transaction_frame[col], label=col, alpha=1.0, color=mycolor)

Ffisegydd's answer may be more 'pythonic', though.

Brad Campbell
  • 2,969
  • 2
  • 23
  • 21
0

As an addition to the already excellent answers, you can consider using a colormap:

import matplotlib.pyplot as plt
import numpy as np

cmap = plt.cm.viridis

datarange = np.arange(4)

for d in datarange:
    # generate colour by feeding float between 0 and 1 to colormap
    color = cmap(d/np.max(datarange)) 
    plt.plot(np.arange(5)+d, c=color)

for d in datarange:
    # generate colour by feeding float between 0 and 1 to colormap
    color = cmap(d/np.max(datarange))
    plt.plot(-np.arange(5)+d, c=color)

enter image description here

warped
  • 8,947
  • 3
  • 22
  • 49