Matplotlib 2 Untergrundstücke, 1 Farbleiste

235

Ich habe viel zu lange nachgeforscht, wie zwei Unterplots dieselbe y-Achse mit einer einzigen Farbleiste teilen können, die zwischen den beiden in Matplotlib geteilt wird.

Was geschah, war, dass, wenn ich die colorbar()Funktion in entweder subplot1oder subplot2aufrief, das Diagramm automatisch so skaliert wurde, dass die Farbleiste und das Diagramm in den Begrenzungsrahmen 'Unterzeichnung' passten, was dazu führte, dass die beiden nebeneinander angeordneten Diagramme zwei sehr unterschiedlich waren Größen.

Um dies zu umgehen, habe ich versucht, eine dritte Nebenhandlung zu erstellen, die ich dann gehackt habe, um keine Handlung mit nur einer vorhandenen Farbleiste zu rendern. Das einzige Problem ist, dass jetzt die Höhen und Breiten der beiden Diagramme ungleichmäßig sind und ich nicht herausfinden kann, wie es in Ordnung aussehen soll.

Hier ist mein 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
quelle

Antworten:

319

Platzieren Sie einfach die Farbleiste in einer eigenen Achse und verwenden Sie diese subplots_adjust, um Platz dafür zu schaffen.

Als schnelles Beispiel:

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

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass der Farbbereich durch das zuletzt gezeichnete Bild festgelegt wird (das dazu geführt hat im), auch wenn der Wertebereich durch vminund festgelegt ist vmax. Wenn ein anderes Diagramm beispielsweise einen höheren Maximalwert aufweist, werden Punkte mit höheren Werten als dem Maximalwert imin einer einheitlichen Farbe angezeigt.

Joe Kington
quelle
4
ImageGrid ist auch genau für diesen Zweck sehr nützlich.
Phillip Cloud
5
Wenn Sie tight_layout () verwenden müssen, möchten Sie alles nach subplots_adjust nach tight_layout ausführen und dann die Koordinaten für subplots_adjust und add_axes manuell anpassen.
user1748155
2
Wie kann ich einen einzelnen Farbbalken für zwei verschiedene Streudiagramme haben, die ich bereits habe? Ich habe es oben versucht, aber ich weiß nicht, wie ich "im" durch geeignete Variablen ersetzen soll. Angenommen, meine Streudiagramme sind plot1 = pylib.scatter (x, y, z) und plot2 = pylib.scatter (a, b, c)
Rotail
46
Dies mag für andere offensichtlich gewesen sein, aber ich wollte darauf hinweisen, dass die vminund vmaxArgumente kritisch sind , damit der Farbbalken die Farbe in allen Darstellungen genau wiedergibt . Sie steuern den Farbbereich jedes Unterplots. Wenn Sie über echte Daten verfügen, müssen Sie diese möglicherweise durchlaufen, um zuerst die Min- und Max-Werte zu ermitteln.
James Owers
2
Wenn der Wertebereich der Diagramme unterschiedlich ist, zeigt der Farbbalkenbereich nur den Bereich des letzten Diagramms an, oder? irgendwelche Vorschläge?
Lukas
132

Sie können den Code von Joe Kington mithilfe des axParameters figure.colorbar()mit einer Liste von Achsen vereinfachen . Aus der Dokumentation :

Axt

Keine | übergeordnete Achsen Objekte, aus denen Platz für eine neue Farbleistenachse gestohlen wird. Wenn eine Liste von Achsen angegeben wird, wird die Größe aller Achsen geändert, um Platz für die Farbbalkenachsen zu schaffen.

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
quelle
4
Diese Lösung hat hier sehr gut funktioniert und scheint die einfachste zu sein.
Kknd
8
Wenn Sie die Nrows auf 1 ändern, sind beide Diagramme schrotter als die Farbleiste. Wie kann dieses Problem gelöst werden?
Jin
6
Schade, dass es mit tight_layout nicht funktioniert, aber trotzdem eine gute Lösung.
Mark
1
Nur um sich zu erinnern ... Ich liebe diese Lösung! Tinha que ser cearense!
Iury Simoes-Sousa
1
Der entscheidende Teil dieser Antwort ist fig.colorbar(im, ax=axes.ravel().tolist()). Wenn Sie dies weglassen ax=axes.ravel().tolist(), wird die Farbleiste innerhalb eines Unterplots platziert.
nyanpasu64
54

Diese Lösung erfordert keine manuelle Anpassung der Achsenpositionen oder der Farbleistengröße, funktioniert mit mehrzeiligen und einzeiligen Layouts und funktioniert mit tight_layout(). Es wurde anhand eines GaleriebeispielsImageGrid aus der AxesGrid Toolbox von matplotlib angepasst .

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

Bildraster

Spinup
quelle
Double +1, das ist ein großartiger Ansatz
Brett
Funktioniert zwar mit tight_layout, aber ich habe keine Ahnung, wie ich dieser Farbleiste eine Beschriftung hinzufügen soll. Es akzeptiert nicht das kws-Label, den Titel, den Text ... irgendetwas! Und die Dokumente helfen nicht viel.
TomCho
3
@TomCho Um eine Beschriftung festzulegen, können Sie den Griff der Farbleiste greifen, wenn Sie sie instanziieren : thecb = ax.cax.colorbar(im). Dann können Sie tunthecb.set_label_text("foo")
Spinup
1
Wie ändere ich die Farbkarte?
Sigur
1
@Sigur Ich bin sicher, dass Sie es inzwischen herausgefunden haben, aber für andere können Sie die cmap ändern, wenn Sie im: im = ax.imshow deklarieren (Daten, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis
38

Die Verwendung make_axesist noch einfacher und führt zu einem besseren Ergebnis. Es bietet auch Möglichkeiten, die Positionierung der Farbleiste anzupassen. Beachten Sie auch die Option subplots, x- und y-Achsen gemeinsam zu nutzen.

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
quelle
7
Diese Methode funktioniert nicht, wenn das Unterplot nicht quadratisch ist. Wenn Sie ändern nrows=1, wird die Farbleiste wieder größer als die Unterzeichnungen.
Wesley Tansey
Was ist Ihre Matplotlib-Standardeinstellung? Das sieht großartig aus!
Rafaelvalle
18

Als Anfänger, der über diesen Thread gestolpert ist, möchte ich eine Python-für-Dummies-Adaption von Abevieiramotas sehr ordentlicher Antwort hinzufügen (weil ich auf dem Niveau bin, dass ich nach 'Ravel' suchen musste, um herauszufinden, was ihr Code tat):

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

Viel weniger pythonisch, viel einfacher für Noobs wie mich zu sehen, was hier tatsächlich passiert.

RChapman
quelle
17

Wie in anderen Antworten ausgeführt, besteht die Idee normalerweise darin, Achsen zu definieren, in denen sich die Farbleiste befindet. Es gibt verschiedene Möglichkeiten, dies zu tun. Eine, die noch nicht erwähnt wurde, wäre die direkte Angabe der Farbleistenachsen bei der Erstellung von Unterplots mit plt.subplots(). Der Vorteil ist, dass die Achsenposition nicht manuell eingestellt werden muss und in allen Fällen mit automatischem Aspekt die Farbleiste genau die gleiche Höhe wie die Unterzeichnungen hat. Selbst in vielen Fällen, in denen Bilder verwendet werden, ist das Ergebnis wie unten gezeigt zufriedenstellend.

Bei plt.subplots()der Verwendung von gridspec_kwArgumenten können die Farbbalkenachsen viel kleiner als die anderen Achsen gemacht werden.

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

Beispiel:

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

Geben Sie hier die Bildbeschreibung ein

Dies funktioniert gut, wenn der Aspekt der Diagramme automatisch skaliert wird oder die Bilder aufgrund ihres Aspekts in Breitenrichtung (wie oben) verkleinert werden. Wenn die Bilder jedoch breiter als hoch sind, sieht das Ergebnis wie folgt aus, was möglicherweise unerwünscht ist.

Geben Sie hier die Bildbeschreibung ein

Eine Lösung zum Festlegen der Farbbalkenhöhe an der Unterplothöhe wäre mpl_toolkits.axes_grid1.inset_locator.InsetPositiondas Festlegen der Farbbalkenachsen relativ zu den Bildunterplotachsen.

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

Geben Sie hier die Bildbeschreibung ein

Wichtigkeit von BeEnest
quelle
Ich bin mir nicht sicher, ob ich das hier fragen darf, aber gibt es eine Möglichkeit, diese Lösung ax = fig.add_subplot()stattdessen zu implementieren ? Ich frage, weil ich nicht herausfinden kann, wie ich es mit der Grundkarte verwenden soll.
Lanadaquenada
1
@lanadaquenada Ja, das ist möglich, aber in diesem Fall müssten Sie ein GridSpecan angeben add_subplot().
ImportanceOfBeingErnest
10

Die Lösung, eine Liste von Achsen von abevieiramota zu verwenden, funktioniert sehr gut, bis Sie nur eine Bildreihe verwenden, wie in den Kommentaren ausgeführt. Die Verwendung eines angemessenen Seitenverhältnisses figsizehilft, ist aber noch lange nicht perfekt. Beispielsweise:

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 Bildarray

Die Farbbalkenfunktion liefert den shrinkParameter, der ein Skalierungsfaktor für die Größe der Farbbalkenachsen ist. Es erfordert einige manuelle Versuche und Irrtümer. Beispielsweise:

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

1 x 3 Bildarray mit verkleinerter Farbleiste

Spinup
quelle
4

Um die hervorragende Antwort von @ abevieiramota zu ergänzen, können Sie das Äquivalent von tight_layout mit eingeschränktem_layout erhalten. Sie erhalten immer noch große horizontale Lücken, wenn Sie imshowanstelle des pcolormeshdurch verwendeten Seitenverhältnisses von 1: 1 verwenden 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()

Geben Sie hier die Bildbeschreibung ein

Jody Klymak
quelle
1

Ich bemerkte, dass fast jede veröffentlichte Lösung ax.imshow(im, ...)die Farben, die in der Farbleiste für die mehreren Unterfiguren angezeigt wurden, nicht normalisierte. Die imZuordnung stammt aus der letzten Instanz, aber was ist, wenn die Werte der Vielfachen imunterschiedlich sind? (Ich gehe davon aus, dass diese Zuordnungen genauso behandelt werden wie die Kontursätze und Oberflächensätze.) Ich habe ein Beispiel mit einem 3D-Oberflächendiagramm unten, das zwei Farbbalken für ein 2x2-Unterdiagramm erstellt (ein Farbbalken pro Zeile) ). Obwohl die Frage explizit nach einer anderen Anordnung fragt, hilft das Beispiel meiner Meinung nach, einige Dinge zu klären. plt.subplots(...)Aufgrund der 3D-Achsen habe ich leider noch keinen Weg gefunden, dies zu tun .

Beispielplot

Wenn ich nur die Farbbalken besser positionieren könnte ... (Es gibt wahrscheinlich einen viel besseren Weg, dies zu tun, aber zumindest sollte es nicht allzu schwierig sein, dem zu folgen.)

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

quelle
Wenn die Werte aus den mehreren ims unterschiedlich sind, sollten sie nicht dieselbe Farbleiste verwenden, damit die ursprüngliche Frage nicht wirklich zutrifft
Spinup
0

Dieses Thema ist gut behandelt, aber ich möchte dennoch einen anderen Ansatz in einer etwas anderen Philosophie vorschlagen .

Die Einrichtung ist etwas komplexer, ermöglicht aber (meiner Meinung nach) etwas mehr Flexibilität. Zum Beispiel kann man mit den jeweiligen Verhältnissen der einzelnen Unterzeichnungen / Farbbalken spielen:

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

Geben Sie hier die Bildbeschreibung ein

Enzoupi
quelle