Matplotlib 2 Subplots, 1 Colorbar


235

Я провів зовсім занадто довго, вивчаючи, як змусити два субплоти для спільного використання однієї і тієї ж осі y однією кольоровою смугою, яка спільна між ними в Matplotlib.

Що відбувалося, це те, що коли я викликав colorbar()функцію в будь-якій subplot1або subplot2, вона автоматично розмальовує графік таким чином, щоб кольорова смужка плюс графік помістилася всередині обмежувального поля 'subplot', внаслідок чого два діаграми набік були двома дуже різними розміри.

Щоб обійти це, я спробував створити третій субплот, який я тоді зламав, щоб не відображати сюжет із лише кольоровою панеллю. Єдина проблема полягає в тому, що зараз висота і ширина двох ділянок нерівні, і я не можу зрозуміти, як це виглядати нормально.

Ось мій код:

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

Відповіді:


319

Просто розмістіть кольорову панель у власній осі та використовуйте, subplots_adjustщоб звільнити місце для неї.

Як короткий приклад:

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

введіть тут опис зображення

Зауважте, що кольоровий діапазон буде встановлений останнім графічним зображенням (яке породжувало im), навіть якщо діапазон значень встановлюється vminі і vmax. Якщо інший графік має, наприклад, більш високе значення max, точки з більш високими значеннями, ніж max im, відображатимуться в однорідному кольорі.


4
ImageGrid також дуже корисний для цієї точної мети.
Phillip Cloud

5
якщо вам потрібно використовувати Close_layout (), ви хочете зробити все після subplots_adjust після Close_layout, а потім налаштувати координати для subplots_adjust та add_axes вручну.
користувач1748155

2
Як я можу мати одну кольорову смугу для двох різних ділянок розкидання, які у мене вже є? Я спробував вище, але не знаю, як замінити "im" відповідними змінними. Скажімо, мої сюжетні ділянки - plot1 = pylib.scatter (x, y, z) та plot2 = pylib.scatter (a, b, c)
Rotail

46
Можливо, це було очевидно для інших, але я хотів би відзначити, що для того, щоб colourbar дійсно точного уявлення кольору на всіх ділянках, то vminі vmaxаргументи мають вирішальне значення. Вони контролюють колірну гамму кожної субплоти. Якщо у вас є реальні дані, можливо, вам знадобиться пройти цей шлях, щоб спочатку знайти значення min та max.
Джеймс Оверс

2
якщо діапазон значень сюжетів різний, діапазон кольорової смуги відображатиме лише діапазон останнього сюжету, правда? будь-які пропозиції?
Лукас

132

Ви можете спростити код Джо Кінгтона, використовуючи axпараметр figure.colorbar()зі списком осей. З документації :

сокира

Немає | об'єкт (-и) батьківських осей, з якого буде вкрадено місце для нових осей кольорової панелі. Якщо наведено список осей, всі вони будуть змінені, щоб звільнити місце для осей кольорової панелі.

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


4
Це рішення спрацювало тут дуже добре, і, здається, найлегше.
Ккнд

8
Якщо ви зміните число на 1, обидва сюжети є кращими, ніж кольорові. Отже, як можна вирішити цю проблему?
Цзінь

6
Шкода, що він не працює з тісною версією, але все-таки гарне рішення.
Марк

1
Просто пам’ятаю… Я люблю це рішення! Tinha que ser cearense!
iury simoes-sousa

1
Важлива частина цієї відповіді fig.colorbar(im, ax=axes.ravel().tolist()). Якщо ви ax=axes.ravel().tolist()опуститеся, кольорова смужка буде розміщена в межах однієї субплоти.
nyanpasu64

55

Це рішення не потребує ручного налаштування розташування осей чи розміру кольорової смуги, працює з багаторядними та однорядними макетами та працює з ними tight_layout(). Він адаптований з прикладу галереї , використовуючи AxesGrid ToolboxImageGrid від 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()

зображення сітки


Подвійний +1, це чудовий підхід
Бретт

Дійсно працює з rest_layout, але я не знаю, як додати мітку до цієї кольорової панелі. Він не приймає етикетку, назву, текст kws, що завгодно! І документи не дуже допомагають.
TomCho

3
@TomCho Щоб встановити мітку, ви можете захопити ручку Colorbar, коли ви його примірник, як: thecb = ax.cax.colorbar(im). Тоді ви можете зробитиthecb.set_label_text("foo")
спінуп

1
Як змінити кольорову карту?
Сигур

1
@Sigur Я впевнений, що ви це вже зрозуміли, але для інших ви можете змінити cmap при оголошенні im: im = ax.imshow (дані, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis

38

Використання make_axesще простіше і дає кращий результат. Він також надає можливість налаштувати розташування кольорової панелі. Також зверніть увагу на можливість subplotsподілу осей x та 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()


7
Цей метод не працює, коли субплот не є квадратним. Якщо змінити nrows=1, кольорова смуга знову стає більшою, ніж підскладки.
Веслі Тансі

Які за замовчуванням matplotlib? це чудово виглядає!
rafaelvalle

18

Як початківець, який наткнувся на цю нитку, я хотів би додати адаптацію пітона для манекенів дуже акуратної відповіді abevieiramota (тому що я на рівні, що мені довелося шукати "хит", щоб розібратися, що їх код робив):

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

Набагато менше пітонічних, набагато простіше для нообів, як я, бачити, що тут насправді відбувається.


17

Як вказувалося в інших відповідях, ідея зазвичай полягає у визначенні осей для кольорової смуги, де вони можуть проживати. Існують різні способи; Однією, про яку ще не згадувалося, було б безпосередньо вказати осі кольорової панелі при створенні субплотиplt.subplots() . Перевага полягає в тому, що положення осей не потрібно встановлювати вручну, і в усіх випадках з автоматичним аспектом панель кольорів буде точно такої ж висоти, що і підплоти. Навіть у багатьох випадках, коли використовуються зображення, результат буде задоволення, як показано нижче.

Під plt.subplots()час використання gridspec_kwаргумент дозволяє зробити осі кольорової панелі набагато меншими, ніж інші осі.

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

Приклад:

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

введіть тут опис зображення

Це добре працює, якщо аспект сюжетів є автоматичним масштабуванням або зображення зменшуються через їх аспект у напрямку ширини (як у вище). Якщо, однак, зображення ширші, ніж високі, результат виглядав би наступним чином, що може бути небажаним.

введіть тут опис зображення

Рішенням, щоб зафіксувати висоту кольорової смуги до висоти підмножини, було б використовувати mpl_toolkits.axes_grid1.inset_locator.InsetPositionдля встановлення осей кольорової гамми відносно осей підмножини зображення.

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

введіть тут опис зображення


Я не впевнений, чи мені тут дозволено запитати, але чи є спосіб реалізувати це рішення, використовуючи ax = fig.add_subplot()натомість? Я запитую, тому що я не можу зрозуміти, як це використовувати з базовою картою.
Ланадакенада

1
@lanadaquenada Так, це можливо, але ви повинні були б поставити GridSpecдо add_subplot()цього випадку.
ВажливістьBeingErnest

10

Рішення використання списку осей abevieiramota працює дуже добре, поки ви не використовуєте лише один ряд зображень, як зазначено в коментарях. Використання розумного співвідношення сторін для допомоги figsize, але все ще далеко не ідеальне. Наприклад:

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 масив зображень

Функція кольорової смуги забезпечує shrinkпараметр, який є коефіцієнтом масштабування розміру осей кольорової панелі. Це вимагає певної ручної проби та помилок. Наприклад:

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

1 x 3 масив зображень із зменшеною кольоровою панеллю


4

Щоб додати до відмінної відповіді @ abevieiramota, ви можете отримати еквівалент тісного_лаута з constrained_layout. Ви все одно отримаєте великі горизонтальні прогалини, якщо використовуватимете imshowзамість того, pcolormeshщо пропорції 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()

введіть тут опис зображення


1

Я помітив, що майже кожне розміщене рішення стосується ax.imshow(im, ...)і не нормалізує кольори, що відображаються на кольоровій панелі для декількох підфігур. Відображається imузяте з останньої інстанції, але що робити, якщо значення множинних значень imвідрізняються? (Я припускаю, що ці картограми обробляються так само, як обробляються контурні набори та набори поверхонь.) Я маю приклад, використовуючи 3d графік поверхні внизу, який створює дві кольорові смуги для подплоти 2x2 (одна кольорова смуга в одному рядку ). Хоча питання прямо задає іншу домовленість, я думаю, що приклад допомагає з’ясувати деякі речі. Я ще не знайшов способу це зробити, використовуючи, plt.subplots(...)на жаль, 3D-осі.

Приклад сюжету

Якби я міг би краще розташувати кольорові смуги ... (Мабуть, це набагато кращий спосіб зробити це, але принаймні це слід не надто складно.)

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

Якщо значення у кількох ims відрізняються, вони не повинні використовувати одну і ту ж кольорову панель, тому оригінальне запитання дійсно не стосується
спінуп

0

Ця тема добре висвітлена, але я все ж хотів би запропонувати інший підхід у дещо іншій філософії.

Це трохи складніше в налаштуванні, але це дозволяє (на мою думку) трохи більше гнучкості. Наприклад, можна грати з відповідними співвідношеннями кожного субплота / кольорової смуги:

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

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.