151 votes

Barre de couleur discrète Matplotlib

J'essaie de créer une barre de couleur discrète pour un nuage de points en matplotlib.

J'ai mes données x, y et pour chaque point une valeur de balise entière que je veux voir représentée par une couleur unique, par ex.

plt.scatter(x, y, c=tag)

En général, la balise sera un nombre entier allant de 0 à 20, mais la plage exacte peut changer.

Jusqu'à présent, je n'ai utilisé que les paramètres par défaut, par ex.

plt.colorbar()

ce qui donne une gamme continue de couleurs. Idéalement, je voudrais un ensemble de n couleurs discrètes (n=20 dans cet exemple). Ce qui serait encore mieux, ce serait d'obtenir une valeur d'étiquette de 0 pour produire une couleur grise et de 1 à 20 pour les couleurs.

J'ai trouvé quelques 'cookbook' scripts mais ils sont très compliqués et je ne peux pas penser que ce soit la bonne façon de résoudre un problème apparemment simple

136voto

Rutger Kassies Points 7713

Vous pouvez créer une barre de couleur discrète personnalisée assez facilement en utilisant un BoundaryNorm comme normalisateur pour votre dispersion. Le problème (dans ma méthode) est de faire apparaître le 0 en gris.

Pour les images, j'utilise souvent la cmap.set_bad() et je convertis mes données en un tableau masqué numpy. Ce serait beaucoup plus facile de rendre 0 gris, mais je n'ai pas réussi à le faire fonctionner avec le scatter ou le cmap personnalisé.

Comme alternative, vous pouvez créer votre propre cmap à partir de zéro, ou lire un cmap existant et remplacer seulement certaines entrées spécifiques.

import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt

fig, ax = plt.subplots(1, 1, figsize=(6, 6))  # setup the plot

x = np.random.rand(20)  # define the data
y = np.random.rand(20)  # define the data
tag = np.random.randint(0, 20, 20)
tag[10:12] = 0  # make sure there are some 0 values to show up as grey

cmap = plt.cm.jet  # define the colormap
# extract all colors from the .jet map
cmaplist = [cmap(i) for i in range(cmap.N)]
# force the first color entry to be grey
cmaplist[0] = (.5, .5, .5, 1.0)

# create the new map
cmap = mpl.colors.LinearSegmentedColormap.from_list(
    'Custom cmap', cmaplist, cmap.N)

# define the bins and normalize
bounds = np.linspace(0, 20, 21)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

# make the scatter
scat = ax.scatter(x, y, c=tag, s=np.random.randint(100, 500, 20),
                  cmap=cmap, norm=norm)

# create a second axes for the colorbar
ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])
cb = plt.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
    spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')

ax.set_title('Well defined discrete colors')
ax2.set_ylabel('Very custom cbar [-]', size=12)

enter image description here

Je pense personnellement qu'avec 20 couleurs différentes, il est un peu difficile de lire la valeur spécifique, mais cela dépend de vous bien sûr.

92voto

David Zwicker Points 4319

Vous pourriez suivre cet exemple ci-dessous ou le exemple nouvellement ajouté dans la documentation

#!/usr/bin/env python
"""
Use a pcolor or imshow with a custom colormap to make a contour plot.

Since this example was initially written, a proper contour routine was
added to matplotlib - see contour_demo.py and
http://matplotlib.sf.net/matplotlib.pylab.html#-contour.
"""

from pylab import *

delta = 0.01
x = arange(-3.0, 3.0, delta)
y = arange(-3.0, 3.0, delta)
X,Y = meshgrid(x, y)
Z1 = bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2 - Z1 # difference of Gaussians

cmap = cm.get_cmap('PiYG', 11)    # 11 discrete colors

im = imshow(Z, cmap=cmap, interpolation='bilinear',
            vmax=abs(Z).max(), vmin=-abs(Z).max())
axis('off')
colorbar()

show()

ce qui produit l'image suivante :

poormans_contour

82voto

ben.dichter Points 1343

Les réponses ci-dessus sont bonnes, sauf qu'elles ne permettent pas de placer correctement les tics sur la barre de couleur. J'aime que les points soient placés au milieu de la couleur pour que la correspondance nombre -> couleur soit plus claire. Vous pouvez résoudre ce problème en modifiant les limites de l'appel matshow :

import matplotlib.pyplot as plt
import numpy as np

def discrete_matshow(data):
    # get discrete colormap
    cmap = plt.get_cmap('RdBu', np.max(data) - np.min(data) + 1)
    # set limits .5 outside true range
    mat = plt.matshow(data, cmap=cmap, vmin=np.min(data) - 0.5, 
                      vmax=np.max(data) + 0.5)
    # tell the colorbar to tick at integers
    cax = plt.colorbar(mat, ticks=np.arange(np.min(data), np.max(data) + 1))

# generate data
a = np.random.randint(1, 9, size=(10, 10))
discrete_matshow(a)

example of discrete colorbar

45voto

Joe Kington Points 68089

Pour définir des valeurs supérieures ou inférieures à la plage de la carte des couleurs, vous devez utiliser la fonction set_over y set_under méthodes de la carte des couleurs. Si vous souhaitez marquer une valeur particulière, masquez-la (c'est-à-dire créez un tableau masqué), et utilisez la méthode set_bad méthode. (Consultez la documentation de la classe Colormap de base : http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

On dirait que vous voulez quelque chose comme ça :

import matplotlib.pyplot as plt
import numpy as np

# Generate some data
x, y, z = np.random.random((3, 30))
z = z * 20 + 0.1

# Set some values in z to 0...
z[:5] = 0

cmap = plt.get_cmap('jet', 20)
cmap.set_under('gray')

fig, ax = plt.subplots()
cax = ax.scatter(x, y, c=z, s=100, cmap=cmap, vmin=0.1, vmax=z.max())
fig.colorbar(cax, extend='min')

plt.show()

enter image description here

34voto

Enzoupi Points 341

Ce sujet est déjà bien couvert mais je voulais ajouter quelque chose de plus spécifique : je voulais être sûr qu'une certaine valeur serait affectée à cette couleur (et non à n'importe quelle couleur).

Ce n'est pas compliqué mais comme cela m'a pris du temps, cela pourrait aider d'autres personnes à ne pas perdre autant de temps que moi :)

import matplotlib
from matplotlib.colors import ListedColormap

# Let's design a dummy land use field
A = np.reshape([7,2,13,7,2,2], (2,3))
vals = np.unique(A)

# Let's also design our color mapping: 1s should be plotted in blue, 2s in red, etc...
col_dict={1:"blue",
          2:"red",
          13:"orange",
          7:"green"}

# We create a colormar from our list of colors
cm = ListedColormap([col_dict[x] for x in col_dict.keys()])

# Let's also define the description of each category : 1 (blue) is Sea; 2 (red) is burnt, etc... Order should be respected here ! Or using another dict maybe could help.
labels = np.array(["Sea","City","Sand","Forest"])
len_lab = len(labels)

# prepare normalizer
## Prepare bins for the normalizer
norm_bins = np.sort([*col_dict.keys()]) + 0.5
norm_bins = np.insert(norm_bins, 0, np.min(norm_bins) - 1.0)
print(norm_bins)
## Make normalizer and formatter
norm = matplotlib.colors.BoundaryNorm(norm_bins, len_lab, clip=True)
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: labels[norm(x)])

# Plot our figure
fig,ax = plt.subplots()
im = ax.imshow(A, cmap=cm, norm=norm)

diff = norm_bins[1:] - norm_bins[:-1]
tickz = norm_bins[:-1] + diff / 2
cb = fig.colorbar(im, format=fmt, ticks=tickz)
fig.savefig("example_landuse.png")
plt.show()

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X