Matplotlib 2 sottotrame, 1 barra dei colori


235

Ho impiegato troppo tempo a cercare come ottenere due sottotrame per condividere lo stesso asse y con una singola barra dei colori condivisa tra i due in Matplotlib.

Quello che stava succedendo era che quando chiamavo la colorbar()funzione in uno dei due subplot1o subplot2, scalava automaticamente la trama in modo tale che la barra dei colori più la trama si adattassero all'interno del riquadro di delimitazione "sottotrama", facendo sì che le due trame affiancate fossero due molto diverse dimensioni.

Per ovviare a questo, ho provato a creare un terzo sottotrame che poi ho hackerato per rendere nessuna trama con solo una barra dei colori presente. L'unico problema è che ora le altezze e le larghezze dei due grafici sono irregolari e non riesco a capire come farlo sembrare a posto.

Ecco il mio codice:

from __future__ import division
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches
from matplotlib.ticker import NullFormatter

# SIS Functions
TE = 1 # Einstein radius
g1 = lambda x,y: (TE/2) * (y**2-x**2)/((x**2+y**2)**(3/2)) 
g2 = lambda x,y: -1*TE*x*y / ((x**2+y**2)**(3/2))
kappa = lambda x,y: TE / (2*np.sqrt(x**2+y**2))

coords = np.linspace(-2,2,400)
X,Y = np.meshgrid(coords,coords)
g1out = g1(X,Y)
g2out = g2(X,Y)
kappaout = kappa(X,Y)
for i in range(len(coords)):
    for j in range(len(coords)):
        if np.sqrt(coords[i]**2+coords[j]**2) <= TE:
            g1out[i][j]=0
            g2out[i][j]=0

fig = plt.figure()
fig.subplots_adjust(wspace=0,hspace=0)

# subplot number 1
ax1 = fig.add_subplot(1,2,1,aspect='equal',xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{1}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
plt.ylabel(r"y ($\theta_{E}$)",rotation='horizontal',fontsize="15")
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.imshow(g1out,extent=(-2,2,-2,2))
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
e1 = patches.Ellipse((0,0),2,2,color='white')
ax1.add_patch(e1)

# subplot number 2
ax2 = fig.add_subplot(1,2,2,sharey=ax1,xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{2}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
ax2.yaxis.set_major_formatter( NullFormatter() )
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
plt.imshow(g2out,extent=(-2,2,-2,2))
e2 = patches.Ellipse((0,0),2,2,color='white')
ax2.add_patch(e2)

# subplot for colorbar
ax3 = fig.add_subplot(1,1,1)
ax3.axis('off')
cbar = plt.colorbar(ax=ax2)

plt.show()

Risposte:


319

Posiziona la barra dei colori sul suo asse e usala subplots_adjustper fare spazio.

A titolo di esempio:

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)

plt.show()

inserisci qui la descrizione dell'immagine

Si noti che l'intervallo di colori verrà impostato dall'ultima immagine tracciata (che ha dato origine a im) anche se l'intervallo di valori è impostato da vmine vmax. Se un altro grafico ha, ad esempio, un valore massimo più alto, i punti con valori più alti del massimo di imverranno visualizzati in un colore uniforme.


4
ImageGrid è anche molto utile per questo preciso scopo.
Phillip Cloud,

5
se devi usare tight_layout (), vorrai fare tutto dopo subplots_adjust dopo tight_layout, quindi modificare le coordinate per subplots_adjust e add_axes manualmente.
user1748155,

2
Come posso avere un'unica barra dei colori per due diversi grafici a dispersione che già ho? Ho provato sopra ma non so come sostituire "im" con variabili appropriate. Supponiamo che i miei grafici a dispersione siano plot1 = pylib.scatter (x, y, z) e plot2 = pylib.scatter (a, b, c)
Rotail

46
Questo potrebbe essere stato ovvio per gli altri, ma volevo sottolineare che, per far sì che il colore rappresentasse accuratamente il colore in tutte le trame, gli argomenti vmine vmaxsono fondamentali. Controllano la gamma di colori di ogni sottotrama. Se disponi di dati reali, potresti dover passare attraverso questo per trovare prima i valori minimo e massimo.
James Owers,

2
se l'intervallo di valori dei grafici è diverso, l'intervallo della barra dei colori mostrerebbe solo l'intervallo dell'ultimo diagramma, giusto? eventuali suggerimenti?
Lukas,

132

Puoi semplificare il codice di Joe Kington usando il axparametro di figure.colorbar()con un elenco di assi. Dalla documentazione :

ascia

Nessuno | oggetto (i) asse (i) padre (i) da cui verrà rubato lo spazio per una nuova barra dei colori. Se viene fornito un elenco di assi, verranno tutti ridimensionati per fare spazio agli assi della barra dei colori.

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.ravel().tolist())

plt.show()

1


4
Questa soluzione ha funzionato molto bene qui, e sembra essere la più semplice.
Kknd,

8
Se cambiate i nrows in 1, entrambi i grafici sono sparati rispetto alla barra dei colori. quindi, come può risolvere questo problema?
Jin

6
Peccato che non funzioni con tight_layout, ma comunque una buona soluzione.
Segna il

1
Solo per ricordare ... Adoro questa soluzione! Tinha que ser cearense!
iury simoes-sousa,

1
La parte cruciale di questa risposta è fig.colorbar(im, ax=axes.ravel().tolist()). Se si omette ax=axes.ravel().tolist(), la barra dei colori verrà inserita in una sottotrama.
nyanpasu64,

55

Questa soluzione non richiede l'ottimizzazione manuale delle posizioni degli assi o delle dimensioni della barra dei colori, funziona con layout a più file e a riga singola e funziona con tight_layout(). È adattato da un esempio di galleria , utilizzando ImageGriddalla AxesGrid Toolbox di matplotlib .

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid

# Set up figure and image grid
fig = plt.figure(figsize=(9.75, 3))

grid = ImageGrid(fig, 111,          # as in plt.subplot(111)
                 nrows_ncols=(1,3),
                 axes_pad=0.15,
                 share_all=True,
                 cbar_location="right",
                 cbar_mode="single",
                 cbar_size="7%",
                 cbar_pad=0.15,
                 )

# Add data to image grid
for ax in grid:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

# Colorbar
ax.cax.colorbar(im)
ax.cax.toggle_label(True)

#plt.tight_layout()    # Works, but may still require rect paramater to keep colorbar labels visible
plt.show()

griglia dell'immagine


Doppio +1, questo è un ottimo approccio
Brett

Funziona davvero con tight_layout, ma non ho idea di come aggiungere un'etichetta a quella barra dei colori. Non accetta l'etichetta kws, il titolo, il testo ... niente! E i documenti non aiutano molto.
TomCho,

3
@TomCho Per impostare un'etichetta, si può afferrare la maniglia della colorbar quando si crea un'istanza di esso, come: thecb = ax.cax.colorbar(im). Quindi puoi farlothecb.set_label_text("foo")
spinup il

1
Come cambiare la mappa dei colori?
Sigur,

1
@Sigur Sono sicuro che ormai l'hai capito, ma per gli altri puoi cambiare il cmap quando dichiari im: im = ax.imshow (data, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis

38

L'uso make_axesè ancora più semplice e dà un risultato migliore. Offre inoltre la possibilità di personalizzare il posizionamento della barra dei colori. Si noti inoltre l'opzione di subplotscondividere gli assi xey.

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

fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

cax,kw = mpl.colorbar.make_axes([ax for ax in axes.flat])
plt.colorbar(im, cax=cax, **kw)

plt.show()


7
Questo metodo non funziona quando la sottotrama non è quadrata. Se si cambia nrows=1, la barra dei colori diventa di nuovo più grande delle sottotrame.
Wesley Tansey,

Quali sono le impostazioni predefinite di matplotlib? sembra grandioso!
rafaelvalle,

18

Come un principiante che si è imbattuto in questo thread, vorrei aggiungere un adattamento pitone-per-manichini della risposta molto accurata di abevieiramota (perché sono al livello in cui ho dovuto cercare 'ravel' per capire cosa il loro codice stava funzionando):

import numpy as np
import matplotlib.pyplot as plt

fig, ((ax1,ax2,ax3),(ax4,ax5,ax6)) = plt.subplots(2,3)

axlist = [ax1,ax2,ax3,ax4,ax5,ax6]

first = ax1.imshow(np.random.random((10,10)), vmin=0, vmax=1)
third = ax3.imshow(np.random.random((12,12)), vmin=0, vmax=1)

fig.colorbar(first, ax=axlist)

plt.show()

Molto meno pitonico, molto più facile per i rumori come me vedere cosa sta realmente accadendo qui.


17

Come sottolineato in altre risposte, l'idea è di solito di definire un asse in cui risiedere la barra dei colori. Esistono vari modi per farlo; uno che non è stato ancora menzionato sarebbe quello di specificare direttamente gli assi della barra dei colori durante la creazione della sottotrama plt.subplots(). Il vantaggio è che non è necessario impostare manualmente la posizione degli assi e in tutti i casi con aspetto automatico la barra dei colori avrà esattamente la stessa altezza dei grafici secondari. Anche in molti casi in cui vengono utilizzate le immagini, il risultato sarà soddisfacente, come mostrato di seguito.

Quando si utilizza plt.subplots(), l'uso gridspec_kwdell'argomento consente di rendere gli assi della barra dei colori molto più piccoli degli altri assi.

fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3), 
                  gridspec_kw={"width_ratios":[1,1, 0.05]})

Esempio:

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

fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3), 
                  gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im  = ax.imshow(np.random.rand(11,8), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.rand(11,8), vmin=0, vmax=1)
ax.set_ylabel("y label")

fig.colorbar(im, cax=cax)

plt.show()

inserisci qui la descrizione dell'immagine

Funziona bene se l'aspetto delle trame viene ridimensionato automaticamente o le immagini vengono ridotte a causa del loro aspetto nella direzione della larghezza (come sopra). Se, tuttavia, le immagini sono più larghe che alte, il risultato sarebbe il seguente, il che potrebbe essere indesiderato.

inserisci qui la descrizione dell'immagine

Una soluzione per fissare l'altezza della barra dei colori all'altezza della sottotrama sarebbe quella di utilizzare mpl_toolkits.axes_grid1.inset_locator.InsetPositiongli assi della barra dei colori relativi agli assi della sottotrama dell'immagine.

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition

fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(7,3), 
                  gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im  = ax.imshow(np.random.rand(11,16), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.rand(11,16), vmin=0, vmax=1)
ax.set_ylabel("y label")

ip = InsetPosition(ax2, [1.05,0,0.05,1]) 
cax.set_axes_locator(ip)

fig.colorbar(im, cax=cax, ax=[ax,ax2])

plt.show()

inserisci qui la descrizione dell'immagine


Non sono sicuro di poterlo chiedere qui, ma c'è un modo per implementare questa soluzione usando ax = fig.add_subplot()invece? Lo sto chiedendo perché non riesco a capire come usarlo con la mappa di base.
lanadaquenada,

1
@lanadaquenada Sì, è possibile, ma in tal caso dovrai fornire un GridSpeca add_subplot().
ImportanceOfBeingErnest

10

La soluzione di utilizzare un elenco di assi di abevieiramota funziona molto bene fino a quando non si utilizza solo una riga di immagini, come sottolineato nei commenti. L'uso di un formato ragionevole per gli figsizeaiuti, ma è ancora lungi dall'essere perfetto. Per esempio:

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9.75, 3))
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.ravel().tolist())

plt.show()

1 x 3 array di immagini

La funzione barra dei colori fornisce il shrinkparametro che è un fattore di ridimensionamento per la dimensione degli assi della barra dei colori. Richiede alcuni tentativi ed errori manuali. Per esempio:

fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)

1 x 3 array di immagini con barra dei colori ridotta


4

Per aggiungere all'eccellente risposta di @ abevieiramota, puoi ottenere l'euqivalent di tight_layout con Vincolo_distinto. Otterrai comunque ampi spazi orizzontali se lo utilizzi imshowanziché a pcolormeshcausa delle proporzioni 1: 1 imposte da imshow.

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2, constrained_layout=True)
for ax in axes.flat:
    im = ax.pcolormesh(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.flat)
plt.show()

inserisci qui la descrizione dell'immagine


1

Ho notato che quasi tutte le soluzioni pubblicate riguardavano ax.imshow(im, ...)e non normalizzavano i colori visualizzati sulla barra dei colori per le varie sottofigure. Il immapping è preso dall'ultima istanza, ma cosa succede se i valori dei multipli imsono diversi? (Suppongo che questi mappabili siano trattati nello stesso modo in cui vengono trattati i set di contorni e i set di superfici.) Ho un esempio che usa un diagramma di superficie 3d in basso che crea due barre dei colori per una sottotrama 2x2 (una barra dei colori per una riga ). Sebbene la domanda richieda esplicitamente un accordo diverso, penso che l'esempio aiuti a chiarire alcune cose. plt.subplots(...)Purtroppo non ho ancora trovato un modo per farlo usando gli assi 3D.

Grafico di esempio

Se solo potessi posizionare le barre dei colori in un modo migliore ... (Probabilmente esiste un modo molto migliore per farlo, ma almeno non dovrebbe essere troppo difficile da seguire.)

import matplotlib
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

cmap = 'plasma'
ncontours = 5

def get_data(row, col):
    """ get X, Y, Z, and plot number of subplot
        Z > 0 for top row, Z < 0 for bottom row """
    if row == 0:
        x = np.linspace(1, 10, 10, dtype=int)
        X, Y = np.meshgrid(x, x)
        Z = np.sqrt(X**2 + Y**2)
        if col == 0:
            pnum = 1
        else:
            pnum = 2
    elif row == 1:
        x = np.linspace(1, 10, 10, dtype=int)
        X, Y = np.meshgrid(x, x)
        Z = -np.sqrt(X**2 + Y**2)
        if col == 0:
            pnum = 3
        else:
            pnum = 4
    print("\nPNUM: {}, Zmin = {}, Zmax = {}\n".format(pnum, np.min(Z), np.max(Z)))
    return X, Y, Z, pnum

fig = plt.figure()
nrows, ncols = 2, 2
zz = []
axes = []
for row in range(nrows):
    for col in range(ncols):
        X, Y, Z, pnum = get_data(row, col)
        ax = fig.add_subplot(nrows, ncols, pnum, projection='3d')
        ax.set_title('row = {}, col = {}'.format(row, col))
        fhandle = ax.plot_surface(X, Y, Z, cmap=cmap)
        zz.append(Z)
        axes.append(ax)

## get full range of Z data as flat list for top and bottom rows
zz_top = zz[0].reshape(-1).tolist() + zz[1].reshape(-1).tolist()
zz_btm = zz[2].reshape(-1).tolist() + zz[3].reshape(-1).tolist()
## get top and bottom axes
ax_top = [axes[0], axes[1]]
ax_btm = [axes[2], axes[3]]
## normalize colors to minimum and maximum values of dataset
norm_top = matplotlib.colors.Normalize(vmin=min(zz_top), vmax=max(zz_top))
norm_btm = matplotlib.colors.Normalize(vmin=min(zz_btm), vmax=max(zz_btm))
cmap = cm.get_cmap(cmap, ncontours) # number of colors on colorbar
mtop = cm.ScalarMappable(cmap=cmap, norm=norm_top)
mbtm = cm.ScalarMappable(cmap=cmap, norm=norm_btm)
for m in (mtop, mbtm):
    m.set_array([])

# ## create cax to draw colorbar in
# cax_top = fig.add_axes([0.9, 0.55, 0.05, 0.4])
# cax_btm = fig.add_axes([0.9, 0.05, 0.05, 0.4])
cbar_top = fig.colorbar(mtop, ax=ax_top, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_top)
cbar_top.set_ticks(np.linspace(min(zz_top), max(zz_top), ncontours))
cbar_btm = fig.colorbar(mbtm, ax=ax_btm, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_btm)
cbar_btm.set_ticks(np.linspace(min(zz_btm), max(zz_btm), ncontours))

plt.show()
plt.close(fig)
## orientation of colorbar = 'horizontal' if done by column

Se i valori dei multipli ims sono diversi, essi dovrebbero non utilizzano la stessa barra colorata, quindi la domanda originale non si applicherebbe in realtà
spinup

0

Questo argomento è ben trattato, ma vorrei ancora proporre un altro approccio in una filosofia leggermente diversa.

È un po 'più complesso da configurare ma consente (secondo me) un po' più di flessibilità. Ad esempio, si può giocare con i rispettivi rapporti di ogni sottotrame / barra dei colori:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec

# Define number of rows and columns you want in your figure
nrow = 2
ncol = 3

# Make a new figure
fig = plt.figure(constrained_layout=True)

# Design your figure properties
widths = [3,4,5,1]
gs = GridSpec(nrow, ncol + 1, figure=fig, width_ratios=widths)

# Fill your figure with desired plots
axes = []
for i in range(nrow):
    for j in range(ncol):
        axes.append(fig.add_subplot(gs[i, j]))
        im = axes[-1].pcolormesh(np.random.random((10,10)))

# Shared colorbar    
axes.append(fig.add_subplot(gs[:, ncol]))
fig.colorbar(im, cax=axes[-1])

plt.show()

inserisci qui la descrizione dell'immagine

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.