Matplotlib 2 Subplot, 1 Colorbar

235

Saya telah menghabiskan terlalu lama meneliti bagaimana mendapatkan dua subplot untuk berbagi sumbu y yang sama dengan satu colorbar bersama antara keduanya di Matplotlib.

Apa yang terjadi adalah ketika saya memanggil colorbar()fungsi di salah satu subplot1atau subplot2, itu akan menskalakan plot sedemikian rupa sehingga colorbar ditambah plot akan muat di dalam kotak pembatas 'subplot', menyebabkan dua plot berdampingan menjadi dua sangat berbeda. ukuran.

Untuk menyiasatinya, saya mencoba membuat subplot ketiga yang kemudian saya retas agar tidak ada plot dengan hanya hadiah colorbar. Satu-satunya masalah adalah, sekarang ketinggian dan lebar kedua plot tidak merata, dan saya tidak tahu bagaimana membuatnya terlihat baik-baik saja.

Ini kode saya:

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
sumber

Jawaban:

319

Cukup letakkan colorbar di porosnya sendiri dan gunakan subplots_adjustuntuk memberi ruang bagi itu.

Sebagai contoh cepat:

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

masukkan deskripsi gambar di sini

Perhatikan bahwa rentang warna akan ditetapkan oleh gambar terakhir yang diplot (yang memunculkan im) bahkan jika rentang nilai diatur oleh vmindan vmax. Jika plot lain memiliki, misalnya, nilai maks lebih tinggi, poin dengan nilai lebih tinggi dari maks imakan ditampilkan dalam warna seragam.

Joe Kington
sumber
4
ImageGrid juga sangat berguna untuk tujuan yang tepat ini.
Phillip Cloud
5
jika Anda perlu menggunakan tight_layout (), Anda ingin melakukan semuanya setelah subplots_adjust setelah tight_layout, dan kemudian men-tweak koordinat untuk subplots_adjust dan add_axes secara manual.
user1748155
2
Bagaimana saya bisa memiliki satu bar warna untuk dua plot pencar berbeda yang sudah saya miliki? Saya mencoba di atas tetapi saya tidak tahu bagaimana cara mengganti "im" dengan variabel yang sesuai. Katakanlah plot pencar saya adalah plot1 = pylib.scatter (x, y, z) dan plot2 = pylib.scatter (a, b, c)
Rotail
46
Ini mungkin sudah jelas bagi orang lain, tetapi saya ingin menunjukkan bahwa agar colourbar benar-benar mewakili warna di semua plot, vmindan vmaxargumen sangat penting. Mereka mengontrol rentang warna setiap subplot. Jika Anda memiliki data nyata, Anda mungkin perlu melewati ini untuk menemukan nilai min dan maks terlebih dahulu.
James Owers
2
jika rentang nilai plot berbeda, rentang colorbar hanya akan menunjukkan rentang plot terakhir, kan? ada saran?
Lukas
132

Anda dapat menyederhanakan kode Joe Kington menggunakan axparameter figure.colorbar()dengan daftar sumbu. Dari dokumentasi :

kapak

Tidak ada | parent axes object (s) dari mana ruang untuk sumbu colorbar baru akan dicuri. Jika daftar sumbu diberikan, mereka semua akan diubah ukurannya untuk memberi ruang bagi sumbu bilah warna.

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
sumber
4
Solusi ini bekerja sangat baik di sini, dan tampaknya menjadi yang paling mudah.
Kknd
8
Jika Anda mengubah nrows menjadi 1, kedua plot lebih mudah ditembak daripada colorbar. jadi, bagaimana mengatasi masalah ini?
Jin
6
Sayang sekali itu tidak bekerja dengan tight_layout, tetapi tetap saja solusi yang bagus.
Markus
1
Hanya untuk mengingat ... Saya suka solusi ini! Tinha que ser cearense!
iury simoes-sousa
1
Bagian penting dari jawaban ini adalah fig.colorbar(im, ax=axes.ravel().tolist()). Jika Anda menghilangkan ax=axes.ravel().tolist(), bilah warna akan ditempatkan dalam satu subplot.
nyanpasu64
55

Solusi ini tidak memerlukan penyesuaian lokasi sumbu atau ukuran bilah warna secara manual, bekerja dengan tata letak multi-baris dan satu-baris, dan bekerja dengan tight_layout(). Ini diadaptasi dari contoh galeri , menggunakan ImageGriddari AxesGrid Toolbox 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()

kisi gambar

putar
sumber
Ganda +1, ini pendekatan yang bagus
Brett
Memang berfungsi dengan tight_layout, tapi saya tidak tahu bagaimana cara menambahkan label ke colorbar itu. Itu tidak menerima label kws, judul, teks ... apa pun! Dan dokumen tidak banyak membantu.
TomCho
3
@TomCho Untuk mengatur label, Anda bisa ambil pegangan colorbar ketika Anda instantiate itu, sebagai: thecb = ax.cax.colorbar(im). Maka Anda dapat melakukanthecb.set_label_text("foo")
spinup
1
Bagaimana cara mengubah colormap?
Sigur
1
@ Konfigurasi Saya yakin Anda sudah mengetahuinya sekarang, tetapi untuk yang lain, Anda dapat mengubah cmap ketika mendeklarasikan im: im = ax.imshow (data, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis
38

Menggunakannya make_axesbahkan lebih mudah dan memberikan hasil yang lebih baik. Ini juga menyediakan kemungkinan untuk menyesuaikan posisi bilah warna. Perhatikan juga opsi subplotsuntuk berbagi sumbu x dan 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
sumber
7
Metode ini tidak berfungsi ketika subplot tidak persegi. Jika Anda berubah nrows=1, colorbar menjadi lebih besar dari subplot lagi.
Wesley Tansey
Apa default matplotlib Anda? itu terlihat sangat bagus!
rafaelvalle
18

Sebagai seorang pemula yang tersandung di utas ini, saya ingin menambahkan adaptasi python-for-dummies dari abevieiramota jawaban yang sangat rapi (karena saya pada level itu saya harus mencari 'ravel' untuk mencari tahu apa kode mereka sedang dilakukan):

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

Apalagi pythonic, lebih mudah bagi noobs seperti saya untuk melihat apa yang sebenarnya terjadi di sini.

RChapman
sumber
17

Seperti yang ditunjukkan dalam jawaban lain, idenya biasanya untuk menentukan sumbu untuk tempat tinggal colorbar. Ada berbagai cara untuk melakukannya; salah satu yang belum disebutkan adalah untuk secara langsung menentukan sumbu colorbar pada pembuatan subplot dengan plt.subplots(). Keuntungannya adalah bahwa posisi sumbu tidak perlu diatur secara manual dan dalam semua kasus dengan aspek otomatis, colorbar akan persis sama tingginya dengan subplot. Bahkan dalam banyak kasus di mana gambar digunakan hasilnya akan memuaskan seperti yang ditunjukkan di bawah ini.

Saat menggunakan plt.subplots(), penggunaan gridspec_kwargumen memungkinkan untuk membuat sumbu colorbar jauh lebih kecil dari sumbu lainnya.

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

Contoh:

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

masukkan deskripsi gambar di sini

Ini bekerja dengan baik, jika aspek plot diotomatiskan atau gambar menyusut karena aspek mereka dalam arah lebar (seperti di atas). Namun, jika gambar lebih luas dari tinggi, hasilnya akan terlihat sebagai berikut, yang mungkin tidak diinginkan.

masukkan deskripsi gambar di sini

Solusi untuk memperbaiki ketinggian colorbar dengan ketinggian subplot adalah dengan menggunakan mpl_toolkits.axes_grid1.inset_locator.InsetPositionuntuk mengatur sumbu colorbar relatif terhadap sumbu subplot gambar.

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

masukkan deskripsi gambar di sini

ImportanceOfBeingErnest
sumber
Saya tidak yakin jika saya diizinkan untuk menanyakan hal ini di sini, tetapi apakah ada cara untuk mengimplementasikan solusi ini dengan menggunakan ax = fig.add_subplot()? Saya bertanya karena saya tidak tahu cara menggunakannya dengan basemap.
lanadaquenada
1
@lanadaquenada Ya itu mungkin, tetapi Anda harus menyediakan GridSpecke add_subplot()dalam hal itu.
ImportanceOfBeingErnest
10

Solusi menggunakan daftar sumbu oleh abevieiramota bekerja sangat baik hingga Anda hanya menggunakan satu baris gambar, seperti yang ditunjukkan dalam komentar. Menggunakan rasio aspek yang masuk akal untuk figsizemembantu, tetapi masih jauh dari sempurna. Sebagai contoh:

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 susunan gambar

Fungsi colorbar menyediakan shrinkparameter yang merupakan faktor penskalaan untuk ukuran sumbu colorbar. Itu memang membutuhkan beberapa percobaan dan kesalahan manual. Sebagai contoh:

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

1 x 3 array gambar dengan colorbar menyusut

putar
sumber
4

Untuk menambahkan jawaban sempurna @ abevieiramota, Anda bisa mendapatkan euqivalent dari tight_layout dengan constrained_layout. Anda masih akan mendapatkan celah horizontal yang besar jika Anda menggunakan imshowalih-alih pcolormeshkarena rasio aspek 1: 1 yang dikenakan oleh 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()

masukkan deskripsi gambar di sini

Jody Klymak
sumber
1

Saya perhatikan bahwa hampir setiap solusi yang diposting terlibat ax.imshow(im, ...)dan tidak menormalkan warna yang ditampilkan ke colorbar untuk beberapa subfigures. The immappable diambil dari contoh terakhir, tetapi bagaimana jika nilai-nilai dari beberapa im-s berbeda? (Saya mengasumsikan pemetaan ini diperlakukan dengan cara yang sama dengan contour-set dan surface-set.) Saya punya contoh menggunakan plot permukaan 3d di bawah ini yang membuat dua colorbars untuk subplot 2x2 (satu colorbar per satu baris ). Meskipun pertanyaannya secara eksplisit meminta pengaturan yang berbeda, saya pikir contoh ini membantu menjelaskan beberapa hal. Saya belum menemukan cara untuk melakukan ini menggunakan plt.subplots(...)karena kapak 3D sayangnya.

Contoh Plot

Kalau saja saya bisa memposisikan colorbars dengan cara yang lebih baik ... (Mungkin ada cara yang jauh lebih baik untuk melakukan ini, tetapi setidaknya seharusnya tidak terlalu sulit untuk diikuti.)

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

sumber
Jika nilai dari beberapa ims berbeda, mereka tidak boleh menggunakan colorbar yang sama, jadi pertanyaan awal tidak akan benar-benar berlaku
spinup
0

Topik ini dibahas dengan baik tetapi saya masih ingin mengusulkan pendekatan lain dalam filosofi yang sedikit berbeda.

Pengaturannya sedikit lebih rumit, tetapi memungkinkan (menurut saya) sedikit lebih fleksibel. Misalnya, seseorang dapat bermain dengan rasio masing-masing subplot / colorbar masing-masing:

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

masukkan deskripsi gambar di sini

Enzoupi
sumber