Colorbar discreta Matplotlib


94

Sto cercando di creare una barra dei colori discreta per un grafico a dispersione in matplotlib

Ho i miei dati x, y e per ogni punto un valore di tag intero che voglio essere rappresentato con un colore univoco, ad es.

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

in genere il tag sarà un numero intero compreso tra 0 e 20, ma l'intervallo esatto potrebbe cambiare

finora ho usato solo le impostazioni predefinite, ad es

plt.colorbar()

che dà una gamma continua di colori. Idealmente vorrei un insieme di n colori discreti (n = 20 in questo esempio). Ancora meglio sarebbe ottenere un valore di tag pari a 0 per produrre un colore grigio e 1-20 essere colorato.

Ho trovato alcuni script di 'ricettari' ma sono molto complicati e non riesco a pensare che siano il modo giusto per risolvere un problema apparentemente semplice



grazie per i collegamenti, ma il 2 ° esempio è quello che intendo per mezzi estremamente complicati per eseguire un compito (apparentemente) banale - il 1 ° collegamento è utile
bph

1
Ho trovato questo collegamento molto utile per discretizzare una mappa di colori esistente: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

Risposte:


91

È possibile creare una barra dei colori discreta personalizzata abbastanza facilmente utilizzando un BoundaryNorm come normalizzatore per la dispersione. La parte stravagante (nel mio metodo) sta facendo apparire 0 come grigio.

Per le immagini uso spesso cmap.set_bad () e converto i miei dati in un array mascherato numpy. Sarebbe molto più facile rendere 0 grigio, ma non sono riuscito a farlo funzionare con lo scatter o il cmap personalizzato.

In alternativa, puoi creare la tua CMAP da zero o leggerne una esistente e sovrascrivere solo alcune voci specifiche.

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)

inserisci qui la descrizione dell'immagine

Personalmente penso che con 20 colori diversi sia un po 'difficile leggere il valore specifico, ma questo dipende da te ovviamente.


Non sono sicuro che sia consentito, ma potresti guardare la mia domanda qui ?
vwos

6
plt.colorbar.ColorbarBaselancia Error. Usampl.colorbar.ColorbarBase
zeeshan khan

Grazie per questa risposta, manca davvero dal doc. Ho provato a trasporlo per le rose dei venti dei percentili e ho avuto un bug con la mappatura dei colori. È un caso d'uso diverso, ma potrebbe suggerire che sia N-1in formato cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). In caso contrario, i colori non sono distribuiti equamente all'interno dei contenitori e hai un problema di barriera di recinzione.
jlandercy

1
Ecco il codice per riprodurre una mappatura equamente distribuita: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

Non ne sono sicuro N-1, forse hai ragione ma non posso replicarlo con il mio esempio. Potresti evitare il LinearSegmentedColormap(e il suo Nargomento) usando un file ListedColormap. I documenti sono migliorati molto dal '13, vedi ad esempio: matplotlib.org/3.1.1/tutorials/colors/…
Rutger Kassies

62

Puoi seguire questo esempio :

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

che produce la seguente immagine:

poormans_contour


14
cmap = cm.get_cmap ('jet', 20) quindi scatter (x, y, c = tags, cmap = cmap) mi porta a metà strada lì - è molto difficile trovare documentazione utile per matplotlib
bph

Il collegamento sembra essere interrotto, FYI.
Quinn Culver

42

Le risposte di cui sopra sono buone, tranne per il fatto che non hanno il corretto posizionamento del segno di spunta sulla barra dei colori. Mi piace avere i segni di spunta al centro del colore in modo che il numero -> mappatura dei colori sia più chiaro. Puoi risolvere questo problema modificando i limiti della chiamata a 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)

esempio di colorbar discreto


1
Concordo sul fatto che posizionare il segno di spunta al centro del colore corrispondente sia molto utile quando si esaminano dati discreti. Il tuo secondo metodo è corretto. Tuttavia, il tuo primo metodo è, in generale, sbagliato : stai etichettando le zecche con valori che non sono coerenti con il loro posizionamento sulla barra dei colori. set_ticklabels(...)dovrebbe essere usato solo per controllare la formattazione dell'etichetta (es. numero decimale, ecc.). Se i dati sono veramente discreti, potresti non notare alcun problema. Se c'è rumore nel sistema (es. 2 -> 1.9), questa etichettatura incoerente risulterà in una barra dei colori fuorviante e errata.
E. Davis

E., penso che tu abbia ragione sul fatto che cambiare i limiti sia la soluzione migliore, quindi ho rimosso l'altra, sebbene nessuno dei due gestisse bene il "rumore". Sarebbero necessari alcuni aggiustamenti per la gestione dei dati continui.
ben.dichter

37

Per impostare un valore sopra o sotto l'intervallo della mappa dei colori, ti consigliamo di utilizzare i metodi set_overe set_underdella mappa dei colori. Se si desidera contrassegnare un valore particolare, mascherarlo (ovvero creare un array mascherato) e utilizzare il set_badmetodo. (Dai un'occhiata alla documentazione per la classe base colormap: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Sembra che tu voglia qualcosa del genere:

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

inserisci qui la descrizione dell'immagine


è davvero buono - ho provato a usare set_under ma non avevo incluso vmin quindi non penso che stesse facendo nulla
bph

8

Questo argomento è già ben trattato ma volevo aggiungere qualcosa di più specifico: volevo essere sicuro che un certo valore sarebbe stato mappato a quel colore (non a qualsiasi colore).

Non è complicato ma poiché mi ci è voluto del tempo, potrebbe aiutare gli altri a non perdere tanto tempo quanto me :)

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

inserisci qui la descrizione dell'immagine


Stavo cercando di replicarlo, tuttavia il codice non viene eseguito perché "tmp" non è definito. Inoltre non è chiaro cosa sia "pos" nella funzione lambda. Grazie!
George Liu

@GeorgeLiu In effetti stavi scrivendo! Ho fatto un errore di copia / incolla e ora è fisso! Lo snippet di codice è ora in esecuzione! Per quanto riguarda posnon sono del tutto sicuro del motivo per cui è qui ma è richiesto dal FuncFormatter () ... Forse qualcun altro può illuminarci a riguardo!
Enzoupi

7

Ho indagato su queste idee ed ecco i miei cinque centesimi. Evita di chiamare BoundaryNorme di specificare normcome argomento scattere colorbar. Tuttavia non ho trovato alcun modo per eliminare la chiamata piuttosto prolissa a matplotlib.colors.LinearSegmentedColormap.from_list.

Alcune informazioni di base sono che matplotlib fornisce le cosiddette mappe di colori qualitative, intese per l'uso con dati discreti. Set1, ad esempio, ha 9 colori facilmente distinguibili e tab20potrebbe essere utilizzato per 20 colori. Con queste mappe potrebbe essere naturale utilizzare i primi n colori per colorare grafici a dispersione con n categorie, come nell'esempio seguente. L'esempio produce anche una barra dei colori con n colori discreti etichettati in modo appropriato.

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)

che produce l'immagine qui sotto. Il nnella chiamata a Set1specifica i primi ncolori di quella mappa di colori e l'ultimo nnella chiamata a from_listspecifica di costruire una mappa con i ncolori (il valore predefinito è 256). Per impostarlo cmcome mappa di colori predefinita con plt.set_cmap, ho ritenuto necessario dargli un nome e registrarlo, vale a dire:

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)

grafico a dispersione con colori discreti


1

Penso che vorresti guardare colors.ListedColormap per generare la tua mappa di colori, o se hai solo bisogno di una mappa di colori statica ho lavorato su un'app che potrebbe aiutare.


sembra interessante, forse eccessivo per le mie esigenze - potresti suggerire un modo per contrassegnare un valore di grigio su una mappa di colori esistente? in modo che i valori 0 risultino grigi e gli altri risultino colori?
bph

@Hiett, che ne dici di generare un array RGB color_list basato sui tuoi valori y e di passarlo a ListedColormap? Puoi taggare un valore con color_list [y == value_to_tag] = gray_color.
ChrisC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.