Creating Over 20 Unique Legend Colors Using Matplotlib

creating over 20 unique legend colors using matplotlib

The answer to your question is related to two other SO questions.

The answer to How to pick a new color for each plotted line within a figure in matplotlib? explains how to define the default list of colors that is cycled through to pick the next color to plot. This is done with the Axes.set_color_cycle method.

You want to get the correct list of colors though, and this is most easily done using a color map, as is explained in the answer to this question: Create a color generator from given colormap in matplotlib. There a color map takes a value from 0 to 1 and returns a color.

So for your 20 lines, you want to cycle from 0 to 1 in steps of 1/20. Specifically you want to cycle form 0 to 19/20, because 1 maps back to 0.

This is done in this example:

import matplotlib.pyplot as plt
import numpy as np

NUM_COLORS = 20

cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
ax.plot(np.arange(10)*(i+1))

fig.savefig('moreColors.png')
plt.show()

This is the resulting figure:

Yosemitebear Mountain Giant Double Rainbow 1-8-10

Alternative, better (debatable) solution

There is an alternative way that uses a ScalarMappable object to convert a range of values to colors. The advantage of this method is that you can use a non-linear Normalization to convert from line index to actual color. The following code produces the same exact result:

import matplotlib.pyplot as plt
import matplotlib.cm as mplcm
import matplotlib.colors as colors
import numpy as np

NUM_COLORS = 20

cm = plt.get_cmap('gist_rainbow')
cNorm = colors.Normalize(vmin=0, vmax=NUM_COLORS-1)
scalarMap = mplcm.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = fig.add_subplot(111)
# old way:
#ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
# new way:
ax.set_prop_cycle(color=[scalarMap.to_rgba(i) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
ax.plot(np.arange(10)*(i+1))

fig.savefig('moreColors.png')
plt.show()

python matplotlib how to plot 20 or required number unique and different colors not gradient colors

You can create a list with a lot of colors and take some of them:

import matplotlib._color_data as mcd
palette = list(mcd.XKCD_COLORS.values())[::10]

This part [::10] is to skip similar colors.

Manually set legend colors using matplotlib Python

A simple approach would be to save handles to each type of element. In the code below, handleA, = plt.plot(..., label='A') stored the line element created by plt.plot into a variable named handleA. The handle will keep its label to automatically use in the legend. (A comma is needed because plt.plot always returns a tuple, even if only one line element is created.)

import random
import matplotlib.pyplot as plt

random.seed(10)
data = [(i, i + random.randint(1, 20), random.choice(list("ABC"))) for i in range(2000, 2025)]

plt.figure(figsize=(14, 8))
for x, y, z in data:
a = (x, x + y)
b = (y + random.random(), y + random.random())
if z == "A":
a = (x, x)
handleA, = plt.plot(a, b, '-o', linewidth=0.4, color="blue", label='A')
elif z == "B":
handleB, = plt.plot(a, b, '-o', linewidth=0.4, color="green", label='B')
else:
handleC, = plt.plot(a, b, '-o', linewidth=0.4, color="red", label='C')

plt.legend(handles=[handleA, handleB, handleC], bbox_to_anchor=(1.01, 1.01), loc='upper left')
plt.tight_layout()
plt.show()

custom legend created from handles

How to set unique colors for each line using matplotlib.pyplot format

This is not an issue of the errorbar function, but rather the default color cycle (or, more generally, property cycle) of matplotlib. Check out this link for an example.

Probably the easiest way to get a different color for every line is to define them yourself, using any of the predefined colormaps - e.g. jet:

colors = plt.cm.jet(np.linspace(0, 1, n_columns))

This partitions the colormap range into the number of colors you need. Then you can either change the default property cycler to the newly created one or simply pass the color code for every line:

for i, col in enumerate(df_avg_diff.columns):
plt.errorbar(x=df_avg_diff.index, y=df_avg_diff[col], color=colors[i])

How to set unique color for individual value of legend

You might create a list of colors with the same length as the number of unique complaints. For example gist_ncar. In the code I shuffled the order of the colors to make it less likely that similar colors are near.

Note that it is very hard to have more than 20 colors that are different enough visually. Different people and different monitors may cause colors hard to distinguish.

This and this post provide more ideas to choose enough colors. In your case it might be interesting to color similar complaints with similar hues.

As your example code doesn't provide enough data, the code below generates some random numbers.

import pandas as pd
from matplotlib import pyplot as plt
import random
import matplotlib as mpl

city = ['Londen', 'Paris', 'Rome', 'Brussels', 'Cologne', 'Madrid',
'Athens', 'Geneva', 'Oslo', 'Barcelona', 'Berlin']
complaints = list('abcdefghijklmnopqrstuv')

N = 100
city_column = random.choices(city, k=N)
complaints_column = random.choices(complaints, k=N)
test2 = pd.DataFrame({'City': city_column, 'Complaint Type': complaints_column})

# take a colormap with many different colors and sample it at regular intervals
cmap = plt.cm.gist_rainbow
norm = mpl.colors.Normalize(vmin=0, vmax=len(complaints) - 1)
colors = [cmap(norm(i)) for i in range(len(complaints))]

# create a stack bar chart
cmpltnt_rela = test2.groupby(['City', 'Complaint Type']).size().unstack().fillna(0).plot(kind='bar',
legend=True, stacked=True, color=colors)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), ncol=2)
cmpltnt_rela.plot(figsize=(18, 14))
plt.tick_params('x', labelrotation=30)

plt.tight_layout()
plt.show()

example plot

How to generate random colors in matplotlib?

I'm calling scatter inside a loop and want each plot in a different color.

Based on that, and on your answer: It seems to me that you actually want n distinct colors for your datasets; you want to map the integer indices 0, 1, ..., n-1 to distinct RGB colors. Something like:

mapping index to color

Here is the function to do it:

import matplotlib.pyplot as plt

def get_cmap(n, name='hsv'):
'''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
RGB color; the keyword argument name must be a standard mpl colormap name.'''
return plt.cm.get_cmap(name, n)

Usage in your pseudo-code snippet in the question:

cmap = get_cmap(len(data))
for i, (X, Y) in enumerate(data):
scatter(X, Y, c=cmap(i))

I generated the figure in my answer with the following code:

import matplotlib.pyplot as plt

def get_cmap(n, name='hsv'):
'''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct
RGB color; the keyword argument name must be a standard mpl colormap name.'''
return plt.cm.get_cmap(name, n)

def main():
N = 30
fig=plt.figure()
ax=fig.add_subplot(111)
plt.axis('scaled')
ax.set_xlim([ 0, N])
ax.set_ylim([-0.5, 0.5])
cmap = get_cmap(N)
for i in range(N):
rect = plt.Rectangle((i, -0.5), 1, 1, facecolor=cmap(i))
ax.add_artist(rect)
ax.set_yticks([])
plt.show()

if __name__=='__main__':
main()

Tested with both Python 2.7 & matplotlib 1.5, and with Python 3.5 & matplotlib 2.0. It works as expected.

Matplotlib adding legend based on existing color series

You can create the legend handles using an empty plot with the color based on the colormap and normalization of the scatter plot.

import pandas as pd
import numpy as np; np.random.seed(1)
import matplotlib.pyplot as plt

x = [np.random.normal(5,2, size=20), np.random.normal(10,1, size=20),
np.random.normal(5,1, size=20), np.random.normal(10,1, size=20)]
y = [np.random.normal(5,1, size=20), np.random.normal(5,1, size=20),
np.random.normal(10,2, size=20), np.random.normal(10,2, size=20)]
c = [np.ones(20)*(i+1) for i in range(4)]

df = pd.DataFrame({"x":np.array(x).flatten(),
"y":np.array(y).flatten(),
"colors":np.array(c).flatten()})

size=81
sc = plt.scatter(df['x'], df['y'], s=size, c=df['colors'], edgecolors='none')

lp = lambda i: plt.plot([],color=sc.cmap(sc.norm(i)), ms=np.sqrt(size), mec="none",
label="Feature {:g}".format(i), ls="", marker="o")[0]
handles = [lp(i) for i in np.unique(df["colors"])]
plt.legend(handles=handles)
plt.show()

Alternatively you may filter your dataframe by the values in the colors column, e.g. using groubpy, and plot one scatter plot for each feature:

import pandas as pd
import numpy as np; np.random.seed(1)
import matplotlib.pyplot as plt

x = [np.random.normal(5,2, size=20), np.random.normal(10,1, size=20),
np.random.normal(5,1, size=20), np.random.normal(10,1, size=20)]
y = [np.random.normal(5,1, size=20), np.random.normal(5,1, size=20),
np.random.normal(10,2, size=20), np.random.normal(10,2, size=20)]
c = [np.ones(20)*(i+1) for i in range(4)]

df = pd.DataFrame({"x":np.array(x).flatten(),
"y":np.array(y).flatten(),
"colors":np.array(c).flatten()})

size=81
cmap = plt.cm.viridis
norm = plt.Normalize(df['colors'].values.min(), df['colors'].values.max())

for i, dff in df.groupby("colors"):
plt.scatter(dff['x'], dff['y'], s=size, c=cmap(norm(dff['colors'])),
edgecolors='none', label="Feature {:g}".format(i))

plt.legend()
plt.show()

Both methods produce the same plot:

Sample Image

How can I stop Matplotlib from repeating colors?

How about the colors of the rainbow? The key here is to use ax.set_prop_cycle to assign colors to each line.

NUM_COLORS = len(plist)

cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_prop_cycle('color', [cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
# Or,
# ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
for i, p in enumerate(plist):
ax.plot(data1[i], np.exp(data2)[i], marker='o', label=str(p))

plt.legend()
plt.show()

Borrowed from here. Other options possible.

Matplotlib - plot lines in more colors than 10

If you are working with Pandas dataframe, you can pass a cmap to plot:

df.plot(cmap='tab20')

Update: With matplotlib:

cmap=plt.get_cmap('tab20')
for i,col in enumerate(df.columns):
plt.plot(df[col], color=cmap(i), label=col)

plt.legend()


Related Topics



Leave a reply



Submit