Barra de colores discreta matplotlib

95

Estoy tratando de hacer una barra de colores discreta para un diagrama de dispersión en matplotlib

Tengo mis datos x, y y para cada punto un valor de etiqueta de número entero que quiero que se represente con un color único, por ejemplo

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

normalmente, la etiqueta será un número entero comprendido entre 0 y 20, pero el rango exacto puede cambiar

Hasta ahora acabo de usar la configuración predeterminada, por ejemplo

plt.colorbar()

lo que da una gama continua de colores. Idealmente, me gustaría un conjunto de n colores discretos (n = 20 en este ejemplo). Aún mejor sería obtener un valor de etiqueta de 0 para producir un color gris y 1-20 para ser colorido.

He encontrado algunos guiones de 'libros de cocina', pero son muy complicados y no puedo pensar que sean la forma correcta de resolver un problema aparentemente simple.

bph
fuente
1
¿ Esto o esto ayuda?
Francesco Montesano
gracias por los enlaces, pero el segundo ejemplo es a lo que me refiero con medios enormemente complicados para realizar una tarea (aparentemente) trivial: el primer enlace es útil
bph
1
Encontré este enlace muy útil para discretizar un mapa de colores
BallpointBen

Respuestas:

91

Puede crear una barra de colores discreta personalizada con bastante facilidad utilizando un BoundaryNorm como normalizador para su dispersión. El bit peculiar (en mi método) es hacer que 0 aparezca como gris.

Para las imágenes, a menudo uso cmap.set_bad () y convierto mis datos en una matriz enmascarada numpy. Sería mucho más fácil convertir 0 en gris, pero no pude hacer que esto funcione con la dispersión o el cmap personalizado.

Como alternativa, puede crear su propio cmap desde cero o leer uno existente y anular solo algunas entradas específicas.

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)

ingrese la descripción de la imagen aquí

Personalmente, creo que con 20 colores diferentes es un poco difícil leer el valor específico, pero eso depende de usted, por supuesto.

Rutger Kassies
fuente
No estoy seguro de si esto está permitido, pero ¿podría leer mi pregunta aquí ?
vwos
6
plt.colorbar.ColorbarBaselanza Error. Usompl.colorbar.ColorbarBase
zeeshan khan
Gracias por esta respuesta, realmente la extraño del documento. Traté de transponerlo para las rosas de los percentiles y tuve un error con el mapeo de colores. Es un caso de uso diferente, pero puede sugerir que está N-1en cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Si no, los colores no se distribuyen por igual dentro de los contenedores y tiene un problema de barrera de cerca.
jlandercy
1
Aquí está el código para reproducir un mapeo igualmente distribuido: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
No estoy seguro sobre el N-1, quizás tengas razón, pero no puedo replicarlo con mi ejemplo. Puede evitar el LinearSegmentedColormap(y su Nargumento) utilizando un ListedColormap. Los documentos han mejorado mucho desde '13, ver por ejemplo: matplotlib.org/3.1.1/tutorials/colors/…
Rutger Kassies
62

Podrías seguir este ejemplo :

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

que produce la siguiente imagen:

poormans_contour

David Zwicker
fuente
14
cmap = cm.get_cmap ('jet', 20) luego scatter (x, y, c = tags, cmap = cmap) me lleva a la mitad del camino; es muy difícil encontrar documentación útil para matplotlib
bph
El enlace parece estar roto, FYI.
Quinn Culver
44

Las respuestas anteriores son buenas, excepto que no tienen una ubicación adecuada en la barra de colores. Me gusta tener las marcas en el medio del color para que el número -> mapeo de color sea más claro. Puede resolver este problema cambiando los límites de la llamada de 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)

ejemplo de barra de color discreta

ben.dichter
fuente
1
Estoy de acuerdo en que colocar la marca en el medio del color correspondiente es muy útil cuando se buscan datos discretos. Tu segundo método es correcto. Sin embargo, su primer método es, en general, incorrecto : está etiquetando los ticks con valores que son inconsistentes con su ubicación en la barra de colores. set_ticklabels(...)sólo debe utilizarse para controlar el formato de la etiqueta (por ejemplo, número decimal, etc.). Si los datos son realmente discretos, es posible que no note ningún problema. Si hay ruido en el sistema (por ejemplo, 2 -> 1.9), este etiquetado inconsistente dará como resultado una barra de colores incorrecta y engañosa.
E. Davis
E., creo que tiene razón en que cambiar los límites es la solución superior, así que eliminé el otro, aunque ninguno de los dos manejaría bien el "ruido". Se necesitarían algunos ajustes para manejar datos continuos.
ben.dichter
37

Para establecer valores por encima o por debajo del rango del mapa de colores, querrá utilizar los métodos set_overy set_underdel mapa de colores. Si desea marcar un valor en particular, enmascarelo (es decir, cree una matriz enmascarada) y use el set_badmétodo. (Eche un vistazo a la documentación de la clase de mapa de colores base: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Parece que quieres algo como esto:

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

ingrese la descripción de la imagen aquí

Joe Kington
fuente
eso es realmente bueno - intenté usar set_under pero no había incluido vmin, así que no creo que estuviera haciendo nada
bph
9

Este tema ya está bien cubierto, pero quería agregar algo más específico: quería estar seguro de que un cierto valor se asignaría a ese color (no a cualquier color).

No es complicado, pero como me tomó algo de tiempo, podría ayudar a otros a no perder tanto tiempo como yo :)

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

ingrese la descripción de la imagen aquí

Enzoupi
fuente
Estaba intentando replicar esto, sin embargo, el código no se ejecuta porque 'tmp' no está definido. Tampoco está claro qué es 'pos' en la función lambda. ¡Gracias!
George Liu
@GeorgeLiu ¡De hecho, estabas escrito! ¡Hice un error de copiar / pegar y ahora está arreglado! ¡El fragmento de código ya se está ejecutando! Con respecto, posno estoy completamente seguro de por qué está aquí, pero es solicitado por FuncFormatter () ... ¡Quizás alguien más pueda aclararnos al respecto!
Enzoupi
7

He estado investigando estas ideas y aquí están mis cinco centavos. Evita llamar BoundaryNormy especificar normcomo argumento a scattery colorbar. Sin embargo, no he encontrado la manera de eliminar la llamada bastante prolongada a matplotlib.colors.LinearSegmentedColormap.from_list.

Algunos antecedentes son que matplotlib proporciona los llamados mapas de color cualitativos, destinados a usarse con datos discretos. Set1, por ejemplo, tiene 9 colores fácilmente distinguibles y tab20podría usarse para 20 colores. Con estos mapas podría ser natural usar sus primeros n colores para colorear diagramas de dispersión con n categorías, como lo hace el siguiente ejemplo. El ejemplo también produce una barra de colores con n colores discretos debidamente etiquetados.

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)

que produce la imagen de abajo. El nen la llamada a Set1especifica los primeros ncolores de ese mapa de colores, y el último nen la llamada a from_list especifica la construcción de un mapa con ncolores (el valor predeterminado es 256). Para establecer cmcomo mapa de colores predeterminado con plt.set_cmap, encontré que era necesario darle un nombre y registrarlo, a saber:

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)

diagrama de dispersión con colores desagradables

Kristjan Jonasson
fuente
1

Creo que querrás mirar colors.ListedColormap para generar tu mapa de colores , o si solo necesitas un mapa de colores estático, he estado trabajando en una aplicación que podría ayudar.

ChrisC
fuente
que se ve bien, posiblemente exagerado para mis necesidades, ¿podría sugerir una forma de etiquetar un valor gris en un mapa de colores existente? para que los valores 0 salgan grises y los demás como colores?
bph
@Hiett, ¿qué hay de generar una matriz RGB color_list basada en sus valores y y pasarla a ListedColormap? Puede etiquetar un valor con color_list [y == value_to_tag] = gray_color.
ChrisC