Barre de couleurs discrète Matplotlib


95

J'essaie de créer une barre de couleurs discrète pour un nuage de points dans matplotlib

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

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

généralement la balise sera un entier compris entre 0 et 20, mais la plage exacte peut changer

jusqu'à présent, je viens d'utiliser les paramètres par défaut, par exemple

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). Encore mieux serait d'obtenir une valeur de balise de 0 pour produire une couleur grise et de 1 à 20 pour être coloré.

J'ai trouvé des scripts de `` livres de recettes '' mais ils sont très compliqués et je ne peux pas penser qu'ils sont la bonne façon de résoudre un problème apparemment simple


1
est- ce que ceci ou cela aide?
Francesco Montesano

merci pour les liens, mais le deuxième exemple est ce que je veux dire à propos des moyens extrêmement compliqués d'effectuer une tâche (apparemment) triviale - le premier lien est utile
bph

1
J'ai trouvé ce lien très utile pour discrétiser une palette de couleurs existante: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

Réponses:


91

Vous pouvez créer une barre de couleurs discrète personnalisée assez facilement en utilisant un BoundaryNorm comme normalisateur pour votre dispersion. Le peu bizarre (dans ma méthode) fait apparaître 0 comme gris.

Pour les images, j'utilise souvent 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 ne pouvais pas faire fonctionner cela avec le scatter ou le cmap personnalisé.

Comme alternative, vous pouvez créer votre propre cmap à partir de zéro, ou lire une cmap existante et remplacer uniquement 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)

entrez la description de l'image ici

Personnellement, je pense 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.


Je ne sais pas si cela est autorisé, mais pouvez-vous regarder ma question ici ?
vwos du

6
plt.colorbar.ColorbarBasejette une erreur. Utilisationmpl.colorbar.ColorbarBase
zeeshan khan

Merci pour cette réponse, ça manque vraiment de la doc. J'ai essayé de le transposer pour les windroses des percentiles et j'ai eu un bug avec la cartographie des couleurs. Il est un cas d'utilisation différente, mais il peut suggérer qu'il est N-1en cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Sinon, les couleurs ne sont pas également réparties dans les bacs et vous avez un problème de barrière de clôture.
jlandercy le

1
Voici le code pour reproduire une cartographie également distribuée:q=np.arange(0.0, 1.01, 0.1) cmap = mpl.cm.get_cmap('jet') cmaplist = [cmap(x) for x in q] cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, len(q)-1) norm = mpl.colors.BoundaryNorm(q, cmap.N)
jlandercy le

Je ne suis pas sûr du N-1, vous avez peut-être raison, mais je ne peux pas le reproduire avec mon exemple. Vous pouvez éviter l' argument LinearSegmentedColormap(et son Nargument) en utilisant un ListedColormap. La documentation s'est beaucoup améliorée depuis '13, voir par exemple: matplotlib.org/3.1.1/tutorials/colors
Rutger Kassies

62

Vous pouvez suivre cet exemple :

#!/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()

qui produit l'image suivante:

poormans_contour


14
CMAP = cm.get_cmap ( 'jet', 20) , puis la dispersion (x, y, c = balises, CMAP = CMAP) me fait là en partie - il est très difficile de trouver la documentation utile pour matplotlib
BPH

Le lien semble être cassé, FYI.
Quinn Culver

43

Les réponses ci-dessus sont bonnes, sauf qu'elles n'ont pas le placement correct des coches sur la barre de couleurs. J'aime avoir les coches au milieu de la couleur pour que le mappage nombre -> couleur soit plus clair. 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)-.5, vmax = np.max(data)+.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)

exemple de barre de couleurs discrète


1
Je conviens que placer la coche au milieu de la couleur correspondante est très utile lorsque l'on regarde des données discrètes. Votre deuxième méthode est correcte. Cependant, votre première méthode est, en général, fausse : vous étiquetez les graduations avec des valeurs qui ne correspondent pas à leur placement sur la barre de couleurs. set_ticklabels(...)ne doit être utilisé que pour contrôler le formatage des étiquettes (par exemple, nombre décimal, etc.). Si les données sont vraiment discrètes, vous ne remarquerez peut-être aucun problème. S'il y a du bruit dans le système (par exemple 2 -> 1.9), cet étiquetage incohérent entraînera une barre de couleurs trompeuse et incorrecte.
E. Davis

E., je pense que vous avez raison de dire que changer les limites est la meilleure solution, alors j'ai supprimé l'autre - même si aucun des deux ne gère bien le «bruit». Certains ajustements seraient nécessaires pour traiter les données en continu.
ben.dichter

37

Pour définir une valeur supérieure ou inférieure à la plage de la palette de couleurs, vous souhaiterez utiliser les méthodes set_overet set_underde la palette de couleurs. Si vous voulez marquer une valeur particulière, masquez-la (c'est-à-dire créez un tableau masqué) et utilisez la set_badméthode. (Consultez la documentation de la classe de palette de couleurs de base: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Il semble que vous vouliez quelque chose comme ceci:

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()

entrez la description de l'image ici


c'est vraiment bien - j'ai essayé d'utiliser set_under mais je n'avais pas inclus vmin donc je ne pense pas qu'il faisait quoi que ce soit
bph

9

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 mappée à cette couleur (pas à n'importe quelle couleur).

Ce n'est pas compliqué mais comme cela m'a pris du temps, cela pourrait aider les autres à 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()

entrez la description de l'image ici


Essayait de répliquer cela, mais le code ne s'exécute pas car «tmp» n'est pas défini. On ne sait pas non plus ce qu'est «pos» dans la fonction lambda. Merci!
George Liu

@GeorgeLiu En effet, vous étiez en train d'écrire! J'ai fait une erreur de copier / coller et c'est maintenant corrigé! L'extrait de code est maintenant en cours d'exécution! En posce qui concerne je ne suis pas tout à fait sûr de savoir pourquoi il est ici mais c'est demandé par le FuncFormatter () ... Peut-être que quelqu'un d'autre pourra nous éclairer à ce sujet!
Enzoupi le

7

J'ai étudié ces idées et voici ma valeur de cinq cents. Cela évite d'appeler BoundaryNormet de spécifier normcomme argument de scatteret colorbar. Cependant, je n'ai trouvé aucun moyen d'éliminer l'appel plutôt long matplotlib.colors.LinearSegmentedColormap.from_list.

Un certain contexte est que matplotlib fournit des cartes de couleurs dites qualitatives, destinées à être utilisées avec des données discrètes. Set1, par exemple, a 9 couleurs faciles à distinguer et tab20peut être utilisé pour 20 couleurs. Avec ces cartes, il peut être naturel d'utiliser leurs n premières couleurs pour colorer les nuages ​​de points avec n catégories, comme le fait l'exemple suivant. L'exemple produit également une barre de couleurs avec n couleurs discrètes étiquetées de manière appropriée.

import matplotlib, numpy as np, matplotlib.pyplot as plt
n = 5
from_list = matplotlib.colors.LinearSegmentedColormap.from_list
cm = from_list(None, plt.cm.Set1(range(0,n)), n)
x = np.arange(99)
y = x % 11
z = x % n
plt.scatter(x, y, c=z, cmap=cm)
plt.clim(-0.5, n-0.5)
cb = plt.colorbar(ticks=range(0,n), label='Group')
cb.ax.tick_params(length=0)

qui produit l'image ci-dessous. Le ndans l'appel à Set1spécifie les premières ncouleurs de cette palette de couleurs, et la dernière ndans l'appel à from_list spécifie de construire une carte avec des ncouleurs (la valeur par défaut étant 256). Afin de définir cmcomme palette de couleurs par défaut avec plt.set_cmap, j'ai trouvé qu'il était nécessaire de lui donner un nom et de l'enregistrer, à savoir:

cm = from_list('Set15', plt.cm.Set1(range(0,n)), n)
plt.cm.register_cmap(None, cm)
plt.set_cmap(cm)
...
plt.scatter(x, y, c=z)

nuage de points avec des couleurs disrete


1

Je pense que vous voudriez regarder colors.ListedColormap pour générer votre palette de couleurs, ou si vous avez juste besoin d'une palette de couleurs statique, j'ai travaillé sur une application qui pourrait vous aider.


cela a l'air cool, peut-être excessif pour mes besoins - pourriez-vous suggérer un moyen de marquer une valeur grise sur une palette de couleurs existante? de sorte que les valeurs 0 apparaissent en gris et les autres en couleurs?
bph

@Hiett qu'en est-il de générer un tableau RVB color_list basé sur vos valeurs y et de le transmettre à ListedColormap? Vous pouvez baliser une valeur avec color_list [y == value_to_tag] = gray_color.
ChrisC
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.