it-swarm.com.de

Matplotlib 2 Subplots, 1 Farbleiste

Ich habe viel zu lange damit verbracht, zu untersuchen, wie man zwei Subplots erhält, um dieselbe Y-Achse mit einer einzigen Farbleiste zwischen den beiden in Matplotlib zu teilen. 

Wenn ich die Funktion colorbar() in subplot1 oder subplot2 aufrief, wurde die Grafik automatisch so skaliert, dass die Farbleiste und die Grafik in den Begrenzungsrahmen 'Subplot' passen, was dazu führt, dass die beiden nebeneinander liegenden Plots angezeigt werden zwei sehr unterschiedliche größen.

Um dies zu umgehen, habe ich versucht, ein drittes Subplot zu erstellen, das ich dann hackte, um kein Plot mit nur einer vorhandenen Farbleiste zu rendern. Das einzige Problem ist, jetzt sind die Höhen und Breiten der beiden Diagramme uneinheitlich und ich kann nicht herausfinden, wie ich es gut aussehen lassen kann.

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

Platzieren Sie die Farbleiste einfach in ihrer eigenen Achse und verwenden Sie subplots_adjust, um Platz für sie 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()

enter image description here

252
Joe Kington

Sie können den Code von Joe Kington vereinfachen, indem Sie den Parameter ax von figure.colorbar() mit einer Liste von Achsen verwenden . Aus der Dokumentation :

axt 

Keine | übergeordnete Achsenobjekte, aus denen Platz für eine neue Farbbalkenachse gestohlen wird. Wenn eine Liste von Achsen angegeben wird, wird die Größe aller Achsen geändert, um Platz für die Farbleistenachsen 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

97
abevieiramota

Die Verwendung von make_axes ist noch einfacher und führt zu einem besseren Ergebnis. Es bietet auch die Möglichkeit, die Position der Farbleiste anzupassen. __ Beachten Sie auch die Option subplots, um 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()

35
kch

Diese Lösung erfordert kein manuelles Anpassen der Achsenpositionen oder der Farbbalkengröße. Sie kann mit mehrzeiligen und einzeiligen Layouts und mit tight_layout() verwendet werden. Es wird aus einem galerie-Beispiel angepasst, wobei ImageGrid aus matplotlibs AxesGrid Toolbox verwendet wird.

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

 image grid

33
spinup

In anderen Antworten wurde darauf hingewiesen, dass in der Regel eine Achse für die Farbleiste definiert werden soll. Es gibt verschiedene Möglichkeiten, dies zu tun. Eine, die noch nicht erwähnt wurde, wäre, die Farbbalkenachsen bei der Erstellung des Unterplots direkt mit plt.subplots() anzugeben. Der Vorteil ist, dass die Achsenposition nicht manuell eingestellt werden muss. In allen Fällen mit automatischem Seitenausschnitt hat die Farbleiste genau die gleiche Höhe wie die Subplots. Selbst in vielen Fällen, in denen Bilder verwendet werden, ist das Ergebnis wie unten gezeigt zufriedenstellend. 

Bei Verwendung von plt.subplots() können mit Hilfe des gridspec_kw-Arguments 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()

 enter image description here

Dies funktioniert gut, wenn der Aspekt der Diagramme automatisch skaliert wird oder die Bilder aufgrund ihres Aspekts in der Breitenrichtung verkleinert werden (wie oben). Wenn die Bilder jedoch breiter als hoch sind, würde das Ergebnis wie folgt aussehen, was unerwünscht sein könnte.

 enter image description here

Eine Lösung zum Festlegen der Farbleistenhöhe auf die Unterplothöhe wäre die Verwendung von mpl_toolkits.axes_grid1.inset_locator.InsetPosition, um die Farbleistenachsen relativ zu den Bildunterplotachsen festzulegen.

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

 enter image description here

Als Anfänger, der über diesen Thread gestolpert ist, möchte ich eine Python-for-Dummies-Anpassung von abevieiramota 's 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 Pythonic, viel einfacher für Noobs wie mich, um zu sehen, was hier wirklich passiert. 

9
RChapman

Die Lösung der Verwendung einer Achsenliste von abevieiramota funktioniert sehr gut, bis Sie, wie in den Kommentaren beschrieben, nur eine Bildzeile verwenden. Die Verwendung eines angemessenen Seitenverhältnisses für figsize hilft, ist aber noch lange nicht perfekt. Zum Beispiel:

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 image array

Die Funktion colorbar stellt den Parameter shrink bereit, der ein Skalierungsfaktor für die Größe der Farbleistenachsen ist. Es erfordert einige manuelle Versuche und Fehler. Zum Beispiel:

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

 1 x 3 image array with shrunk colorbar

7
spinup

Ich habe festgestellt, dass an fast jeder Lösung ax.imshow(im, ...) beteiligt war und die Farben, die in der Farbleiste angezeigt werden, nicht normalisiert wurden. Das Mapping von im stammt von der letzten Instanz, aber was ist, wenn die Werte der mehreren im- unterschiedlich sind? (Ich gehe davon aus, dass diese Mappables auf dieselbe Weise behandelt werden, wie die Konturensätze und Oberflächensätze behandelt werden.) Ich habe ein Beispiel mit einem 3D-Oberflächendiagramm, das zwei Farbbalken für ein 2x2-Subplot erstellt (ein Farbbalken pro Zeile) ). Obwohl die Frage ausdrücklich nach einer anderen Anordnung verlangt, scheint mir das Beispiel zu helfen, einige Dinge zu klären. Ich habe leider noch keinen Weg gefunden, dies mit plt.subplots(...) zu tun, wegen der 3D-Achsen.

 Example Plot

Wenn ich nur die Farbbalken besser positionieren könnte ... (Es gibt wahrscheinlich einen besseren Weg, dies zu tun, aber zumindest sollte es nicht zu schwierig sein, ihm 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

Um @ Abevieiramotas exzellente Antwort hinzuzufügen, können Sie das Äquivalent von tight_layout mit constrained_layout erhalten. Sie erhalten immer noch große horizontale Lücken, wenn Sie imshow anstelle von pcolormesh verwenden, da das Seitenverhältnis 1: 1 von imshow vorgegeben ist. 

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

 enter image description here

0
Jody Klymak