Matplotlib 2 podtypy, 1 pasek kolorów

235

Spędziłem całkowicie zbyt długo na badaniu, jak uzyskać dwa podploty, aby współdzielić tę samą oś Y z jednym paskiem kolorów współdzielonym między nimi w Matplotlib.

Co się dzieje, że kiedy zadzwoniłem do colorbar()funkcji w jednej subplot1lub subplot2, byłoby AUTOSCALE fabułę tak, że colorbar plus działka pasowałby w polu „poletko” ograniczającej, powodując dwa side-by-side działek być dwa bardzo różne rozmiary.

Aby obejść ten problem, próbowałem utworzyć trzeci podplot, który następnie zhakowałem, aby nie wyświetlać żadnej fabuły z obecnym paskiem kolorów. Jedynym problemem jest to, że teraz wysokości i szerokości dwóch wykresów są nierówne i nie mogę wymyślić, jak sprawić, by wyglądało dobrze.

Oto mój kod:

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
źródło

Odpowiedzi:

319

Po prostu umieść pasek koloru we własnej osi i użyj, subplots_adjustaby zrobić dla niego miejsce.

Jako szybki przykład:

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

wprowadź opis zdjęcia tutaj

Należy zauważyć, że zakres kolorów zostanie ustawiony na podstawie ostatniego wydrukowanego obrazu (który spowodował powstanie im), nawet jeśli zakres wartości zostanie ustawiony za pomocą vmini vmax. Jeśli na przykład inny wykres ma wyższą wartość maksymalną, punkty o wartościach wyższych niż maksimum imbędą wyświetlane w jednolitym kolorze.

Joe Kington
źródło
4
ImageGrid jest również bardzo przydatny do tego właśnie celu.
Phillip Cloud
5
jeśli potrzebujesz użyć tight_layout (), będziesz musiał zrobić wszystko po subplots_adjust po tight_layout, a następnie ręcznie dostosować współrzędne dla subplots_adjust i add_axes.
user1748155,
2
Jak mogę mieć jeden pasek kolorów dla dwóch różnych wykresów rozrzutu, które już mam? Próbowałem powyżej, ale nie wiem, jak zastąpić „im” odpowiednimi zmiennymi. Powiedzmy, że moje wykresy rozproszenia to wykres 1 = pylib. rozproszenie (x, y, z) i wykres 2 = pylib. rozproszenie (a, b, c)
Rotail
46
Być może było to oczywiste dla innych, ale chciałem zauważyć, że aby pasek kolorów dokładnie reprezentował kolor na wszystkich wykresach, argumenty vmini vmaxsą krytyczne. Kontrolują zakres kolorów każdej podploty. Jeśli masz prawdziwe dane, być może trzeba to zrobić, aby najpierw znaleźć wartości minimalną i maksymalną.
James Owers,
2
jeśli zakres wartości wykresów jest inny, zakres paska kolorów pokazywałby tylko zakres ostatniego wykresu, prawda? jakieś sugestie?
Lukas
132

Możesz uprościć kod Joe Kingtona za pomocą axparametru figure.colorbar()z listą osi. Z dokumentacji :

topór

Brak | obiekty osi macierzystych, z których zostanie skradzione miejsce na nowe osie paska kolorów. Jeśli podana zostanie lista osi, wszystkie zostaną przeskalowane, aby zrobić miejsce na osie paska kolorów.

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
źródło
4
To rozwiązanie działało tutaj bardzo dobrze i wydaje się najłatwiejsze.
Kknd
8
Jeśli zmienisz nrows na 1, obie działki będą strzelać niż pasek kolorów. jak więc rozwiązać ten problem?
Jin
6
Szkoda, że ​​to nie działa z tight_layout, ale mimo to dobre rozwiązanie.
Mark
1
Pamiętaj tylko ... Uwielbiam to rozwiązanie! Tinha que ser cearense!
iury simoes-sousa
1
Kluczową częścią tej odpowiedzi jest fig.colorbar(im, ax=axes.ravel().tolist()). Jeśli pominiesz ax=axes.ravel().tolist(), pasek kolorów zostanie umieszczony w jednym wątku podrzędnym.
nyanpasu64
55

To rozwiązanie nie wymaga ręcznego dostosowywania lokalizacji osi lub rozmiaru paska kolorów, działa z układami wielorzędowymi i jednorzędowymi i działa z tight_layout(). Jest on przystosowany od przykładzie galerii , korzystając ImageGridz matplotlib za AxesGrid Toolbox .

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

siatka obrazu

zakręcić
źródło
Podwój +1, to świetne podejście
Brett,
Rzeczywiście działa z tight_layout, ale nie mam pojęcia, jak dodać etykietę do tego paska kolorów. Nie akceptuje etykiety kws, tytułu, tekstu ... niczego! A doktorzy niewiele pomagają.
TomCho
3
@TomCho Aby ustawić etykietę, można chwycić za rączkę przez colorbar kiedy oznacz ją jako: thecb = ax.cax.colorbar(im). Potem możesz zrobićthecb.set_label_text("foo")
spinup
1
Jak zmienić mapę kolorów?
Sigur,
1
@ Sigur Jestem pewien, że już to zrozumiałeś, ale dla innych możesz zmienić cmap, deklarując im: im = ax.imshow (data, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis
38

Korzystanie make_axesjest jeszcze łatwiejsze i daje lepszy wynik. Zapewnia również możliwość dostosowania położenia paska kolorów. Zwróć także uwagę na opcję subplotswspółdzielenia osi xiy.

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
źródło
7
Ta metoda nie działa, gdy wykres podrzędny nie jest kwadratowy. Jeśli zmienisz nrows=1, pasek kolorów ponownie stanie się większy niż podploty.
Wesley Tansey,
Jakie są twoje domyślne ustawienia Matplotlib? wygląda świetnie!
rafaelvalle,
18

Jako początkujący, który natknął się na ten wątek, chciałbym dodać adaptację python-for-dummies bardzo trafnej odpowiedzi abevieiramota (ponieważ jestem na poziomie, że musiałem poszukać „ravel”, aby dowiedzieć się, co ich kod działał):

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

O wiele mniej pytoniczny, dla noobów takich jak ja łatwiej jest zobaczyć, co się tutaj naprawdę dzieje.

RChapman
źródło
17

Jak wskazano w innych odpowiedziach, zazwyczaj chodzi o zdefiniowanie osi, w których ma znajdować się pasek kolorów. Istnieją różne sposoby na to; jedną, o której jeszcze nie wspomniano, byłoby bezpośrednie określenie osi paska kolorów przy tworzeniu podplotu za pomocą plt.subplots(). Zaletą jest to, że położenie osi nie musi być ustawiane ręcznie i we wszystkich przypadkach z automatycznym aspektem pasek kolorów będzie miał dokładnie taką samą wysokość jak wykresy podrzędne. Nawet w wielu przypadkach, w których używane są obrazy, wynik będzie satysfakcjonujący, jak pokazano poniżej.

Podczas używania plt.subplots(), użycie gridspec_kwargumentu pozwala, aby osie colorbar znacznie mniejszy niż w innych osiach.

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

Przykład:

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

wprowadź opis zdjęcia tutaj

Działa to dobrze, jeśli aspekt wykresów jest skalowany automatycznie lub obrazy są zmniejszane ze względu na ich proporcje w kierunku szerokości (jak powyżej). Jeśli jednak obrazy są szersze niż wysokie, wynik wyglądałby następująco, co może być niepożądane.

wprowadź opis zdjęcia tutaj

Rozwiązaniem, aby ustalić wysokość paska kolorów do wysokości podplotu, byłoby użycie go mpl_toolkits.axes_grid1.inset_locator.InsetPositiondo ustawienia osi paska kolorów względem osi podplotu obrazu.

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

wprowadź opis zdjęcia tutaj

WażnośćOfBeingErnest
źródło
Nie jestem pewien, czy mogę o to zapytać tutaj, ale czy istnieje sposób na wdrożenie tego rozwiązania za pomocą ax = fig.add_subplot()? Pytam, ponieważ nie mogę wymyślić, jak go używać z mapą bazową.
lanadaquenada
1
@lanadaquenada Tak to jest możliwe, ale będziesz musiał dostarczyć GridSpecdo add_subplot()w tej sprawie.
ImportanceOfBeingErnest
10

Rozwiązanie polegające na użyciu listy osi abevieiramota działa bardzo dobrze, dopóki nie użyje się tylko jednego wiersza obrazów, jak wskazano w komentarzach. Używanie rozsądnego współczynnika proporcji w celu uzyskania figsizepomocy, ale wciąż jest daleki od ideału. Na przykład:

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

Tablica obrazów 1 x 3

Funkcja paska kolorów zapewnia shrinkparametr, który jest współczynnikiem skalowania dla wielkości osi paska kolorów. Wymaga ręcznej próby i błędu. Na przykład:

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

Tablica obrazów 1 x 3 ze zmniejszonym paskiem kolorów

zakręcić
źródło
4

Aby dodać do doskonałej odpowiedzi @ abevieiramota, możesz uzyskać równoważnik tight_layout z constrained_layout. Nadal będziesz otrzymywać duże poziome przerwy, jeśli użyjesz imshowzamiast tego z pcolormeshpowodu narzuconego przez ciebie współczynnika proporcji 1: 1 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()

wprowadź opis zdjęcia tutaj

Jody Klymak
źródło
1

Zauważyłem, że prawie każde opublikowane rozwiązanie dotyczyło ax.imshow(im, ...)i nie znormalizowało kolorów wyświetlanych na pasku kolorów dla wielu podfigur. imOdwzorowywalne pochodzi z ostatniej instancji, ale co, jeśli wartości wielu im-s są różne? (Zakładam, że te odwzorowania są traktowane w taki sam sposób, jak traktowane są zestawy konturów i zestawy powierzchni.) Mam przykład wykorzystujący wykres powierzchni 3D poniżej, który tworzy dwa paski kolorów dla podplotu 2x2 (jeden pasek kolorów na jeden wiersz ). Chociaż pytanie wyraźnie wymaga innego rozwiązania, myślę, że przykład pomaga wyjaśnić niektóre rzeczy. plt.subplots(...)Niestety nie znalazłem sposobu na zrobienie tego z powodu osi 3D.

Przykładowy wykres

Gdybym tylko mógł lepiej ustawić paski kolorów ... (Prawdopodobnie jest na to znacznie lepszy sposób, ale przynajmniej nie powinno być zbyt trudne do naśladowania).

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

źródło
Jeśli wartości z wielu ims są różne, powinny one nie używać tego samego colorbar, więc oryginalne pytanie nie naprawdę zastosować
spinup
0

Ten temat jest dobrze omówiony, ale nadal chciałbym nieco zaproponować inne podejście innej filozofii.

Konfiguracja jest nieco bardziej złożona, ale pozwala (moim zdaniem) na nieco większą elastyczność. Na przykład można grać z odpowiednimi proporcjami każdej podploty / paska kolorów:

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

wprowadź opis zdjęcia tutaj

Enzoupi
źródło