Matplotlib 2 sous-parcelles, 1 barre de couleurs

235

J'ai passé trop de temps à rechercher comment obtenir deux sous-tracés pour partager le même axe y avec une seule barre de couleur partagée entre les deux dans Matplotlib.

Ce qui se passait, c'est que lorsque j'appelais la colorbar()fonction dans l'un subplot1ou l' autre subplot2, elle mettait automatiquement l'échelle à l'échelle de sorte que la barre de couleurs plus l'intrigue s'adaptent à l'intérieur du cadre de délimitation du sous-intrigue, ce qui faisait que les deux tracés côte à côte étaient deux très différents tailles.

Pour contourner cela, j'ai essayé de créer une troisième sous-intrigue que j'ai ensuite piratée pour ne rendre aucune intrigue avec juste une barre de couleur présente. Le seul problème est que maintenant les hauteurs et les largeurs des deux parcelles sont inégales, et je ne peux pas comprendre comment le rendre correct.

Voici mon code:

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()
astromax
la source

Réponses:

319

Placez simplement la barre de couleur dans son propre axe et utilisez-la subplots_adjustpour lui faire de la place.

Comme exemple rapide:

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

entrez la description de l'image ici

Notez que la plage de couleurs sera définie par la dernière image tracée (qui a donné lieu à im) même si la plage de valeurs est définie par vminet vmax. Si un autre tracé a, par exemple, une valeur maximale plus élevée, les points avec des valeurs supérieures à la valeur maximale ims'affichent en couleur uniforme.

Joe Kington
la source
4
ImageGrid est également très utile dans ce but précis.
Phillip Cloud
5
si vous devez utiliser tight_layout (), vous voudrez tout faire après subplots_adjust après tight_layout, puis modifier manuellement les coordonnées de subplots_adjust et add_axes.
user1748155
2
Comment puis-je avoir une seule barre de couleur pour deux diagrammes de dispersion différents que j'ai déjà? J'ai essayé ci-dessus mais je ne sais pas comment remplacer "im" par les variables appropriées. Disons que mes diagrammes de dispersion sont plot1 = pylib.scatter (x, y, z) et plot2 = pylib.scatter (a, b, c)
Rotail
46
Cela a peut-être été évident pour d'autres, mais je voulais souligner que pour que la barre de couleur représente avec précision la couleur dans tous les tracés, les arguments vminet vmaxsont essentiels. Ils contrôlent la gamme de couleurs de chaque sous-intrigue. Si vous disposez de données réelles, vous devrez peut-être effectuer une analyse pour trouver les valeurs min et max en premier.
James Owers
2
si la plage de valeurs des tracés est différente, la plage de la barre de couleur n'affichera que la plage du dernier tracé, non? Aucune suggestion?
Lukas
132

Vous pouvez simplifier le code de Joe Kington en utilisant le axparamètre de figure.colorbar()avec une liste d'axes. De la documentation :

hache

Aucun | objet (s) des axes parents à partir desquels de l'espace pour une nouvelle barre de couleur sera volé. Si une liste d'axes est donnée, elles seront toutes redimensionnées pour faire de la place aux axes de la barre de couleur.

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

abevieiramota
la source
4
Cette solution a très bien fonctionné ici et semble être la plus simple.
Kknd
8
Si vous changez nrows à 1, les deux tracés sont plus clairs que la barre de couleurs. alors, comment résoudre ce problème?
Jin
6
Dommage que cela ne fonctionne pas avec tight_layout, mais une bonne solution néanmoins.
Mark
1
Pour rappel ... j'adore cette solution! Tinha que ser cearense!
iury simoes-sousa
1
La partie cruciale de cette réponse est fig.colorbar(im, ax=axes.ravel().tolist()). Si vous omettez ax=axes.ravel().tolist(), la barre de couleurs sera placée dans un sous-tracé.
nyanpasu64
55

Cette solution ne nécessite pas de réglage manuel de l'emplacement des axes ou de la taille de la barre de couleur, fonctionne avec des dispositions à plusieurs lignes et à une seule ligne, et fonctionne avec tight_layout(). Il est adapté d'un exemple de galerie , en utilisant ImageGridla boîte à outils AxesGrid de 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()

grille d'image

Tournoiement en haut
la source
Double +1, c'est une excellente approche
Brett
En effet, fonctionne avec tight_layout, mais je ne sais pas comment ajouter une étiquette à cette barre de couleur. Il n'accepte pas l'étiquette kws, le titre, le texte ... quoi que ce soit! Et la documentation n'aide pas beaucoup.
TomCho
3
@TomCho Pour définir une étiquette, vous pouvez saisir la poignée de la barre de couleurs lorsque vous instancier, comme: thecb = ax.cax.colorbar(im). Ensuite, vous pouvez le fairethecb.set_label_text("foo")
rotation le
1
Comment changer la palette de couleurs?
Sigur
1
@Sigur Je suis sûr que vous l'avez déjà compris, mais pour d'autres, vous pouvez changer la cmap lors de la déclaration de im: im = ax.imshow (data, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis
38

L'utilisation make_axesest encore plus facile et donne un meilleur résultat. Il offre également des possibilités de personnaliser le positionnement de la barre de couleurs. Notez également l'option de subplotspartager les axes x et y.

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

kch
la source
7
Cette méthode ne fonctionne pas lorsque le sous-tracé n'est pas carré. Si vous changez nrows=1, la barre de couleur redevient plus grande que les sous-tracés.
Wesley Tansey
Quels sont vos défauts par défaut matplotlib? cela semble très bien!
rafaelvalle
18

En tant que débutant qui est tombé sur ce fil, je voudrais ajouter une adaptation python pour les nuls de la réponse très soignée d' abevieiramota (parce que je suis au niveau que j'ai dû rechercher `` ravel '' pour déterminer ce que leur code faisait):

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

Beaucoup moins pythonique, beaucoup plus facile pour les noobs comme moi de voir ce qui se passe réellement ici.

RChapman
la source
17

Comme indiqué dans d'autres réponses, l'idée est généralement de définir des axes pour la barre de couleurs à résider. Il existe différentes façons de le faire; celui qui n'a pas encore été mentionné serait de spécifier directement les axes de la barre de couleur lors de la création du sous-tracé avec plt.subplots(). L'avantage est que la position des axes n'a pas besoin d'être réglée manuellement et dans tous les cas avec un aspect automatique, la barre de couleur aura exactement la même hauteur que les sous-tracés. Même dans de nombreux cas où des images sont utilisées, le résultat sera satisfaisant comme indiqué ci-dessous.

Lors de l'utilisation plt.subplots(), l'utilisation d' gridspec_kwargument permet de rendre les axes de la barre de couleur beaucoup plus petits que les autres axes.

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

Exemple:

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

entrez la description de l'image ici

Cela fonctionne bien si l'aspect des tracés est mis à l'échelle automatiquement ou si les images sont rétrécies en raison de leur aspect dans le sens de la largeur (comme ci-dessus). Si, cependant, les images sont plus larges que hautes, le résultat se présenterait comme suit, ce qui pourrait être indésirable.

entrez la description de l'image ici

Une solution pour corriger la hauteur de colorbar à la hauteur de la sous - parcelle consisterait à utiliser mpl_toolkits.axes_grid1.inset_locator.InsetPositionpour définir les axes de colorbar par rapport aux axes de subplot d'image.

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

entrez la description de l'image ici

ImportanceDeBestErnest
la source
Je ne sais pas si je suis autorisé à poser cette question ici, mais existe-t-il un moyen d'implémenter cette solution à la ax = fig.add_subplot()place? Je demande parce que je ne sais pas comment l'utiliser avec le fond de carte.
lanadaquenada
1
@lanadaquenada Oui cela est possible, mais vous devez fournir un GridSpecà add_subplot()dans ce cas.
ImportanceOfBeingErnest
10

La solution d'utiliser une liste d'axes par abevieiramota fonctionne très bien jusqu'à ce que vous n'utilisiez qu'une seule ligne d'images, comme indiqué dans les commentaires. L'utilisation d'un rapport d'aspect raisonnable pour les figsizeaides, mais est encore loin d'être parfaite. Par exemple:

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

Réseau d'images 1 x 3

La fonction de barre de couleur fournit le shrinkparamètre qui est un facteur d'échelle pour la taille des axes de la barre de couleur. Cela nécessite des essais et des erreurs manuels. Par exemple:

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

Réseau d'images 1 x 3 avec barre de couleurs rétrécie

Tournoiement en haut
la source
4

Pour ajouter à l'excellente réponse de @ abevieiramota, vous pouvez obtenir l'euqivalent de tight_layout avec constrained_layout. Vous obtiendrez toujours de grands espaces horizontaux si vous les utilisez imshowplutôt pcolormeshqu'en raison du rapport d'aspect 1: 1 imposé par 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()

entrez la description de l'image ici

Jody Klymak
la source
1

J'ai remarqué que presque toutes les solutions publiées impliquaient ax.imshow(im, ...)et ne normalisaient pas les couleurs affichées dans la barre de couleurs pour les sous-figures multiples. Le immappable provient de la dernière instance, mais que se passe-t-il si les valeurs des multiples im-s sont différentes? (Je suppose que ces mappables sont traités de la même manière que les ensembles de contours et les ensembles de surfaces.) J'ai un exemple utilisant un tracé de surface 3D ci-dessous qui crée deux barres de couleur pour un sous-tracé 2x2 (une barre de couleur par ligne) ). Bien que la question demande explicitement un arrangement différent, je pense que l'exemple aide à clarifier certaines choses. Je n'ai pas encore trouvé de moyen de le faire en plt.subplots(...)raison des axes 3D malheureusement.

Exemple de tracé

Si seulement je pouvais mieux positionner les barres de couleurs ... (Il y a probablement une bien meilleure façon de le faire, mais au moins cela ne devrait pas être trop difficile à suivre.)

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

la source
Si les valeurs des multiples imsont différentes, elles ne devraient pas utiliser la même barre de couleurs, donc la question d'origine ne s'appliquerait pas vraiment
spinup
0

Ce sujet est bien couvert mais je voudrais quand même proposer une autre approche dans une philosophie légèrement différente.

Il est un peu plus complexe à mettre en place mais il permet (à mon avis) un peu plus de flexibilité. Par exemple, on peut jouer avec les ratios respectifs de chaque sous-parcelles / barre de couleurs:

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

entrez la description de l'image ici

Enzoupi
la source