Matplotlib 2 участка, 1 цветная полоса

235

Я потратил слишком много времени на изучение того, как получить два вспомогательных участка, чтобы совместно использовать одну и ту же ось Y с одной цветной полосой, разделяемой между ними в Matplotlib.

То, что происходило, было то, что, когда я вызывал colorbar()функцию в subplot1или subplot2, она автоматически масштабировала график таким образом, чтобы цветовая полоса плюс график помещались в ограничивающей рамке «подзаговор», в результате чего два соседних графика были совершенно разными. размеры.

Чтобы обойти это, я попытался создать третий субплот, который затем взломал, чтобы не отображать никакого графика с помощью только цветовой шкалы. Единственная проблема в том, что теперь высота и ширина двух графиков неравномерны, и я не могу понять, как сделать так, чтобы все выглядело нормально.

Вот мой код:

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
источник

Ответы:

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. Если другой график имеет, например, более высокое максимальное значение, точки с более высокими значениями, чем максимальное, imбудут отображаться однородным цветом.

Джо Кингтон
источник
4
ImageGrid также очень полезен для этой цели.
Филлип Облако
5
если вам нужно использовать тесную связь (), вы захотите сделать все после subplots_adjust после плотной игры, а затем вручную настроить координаты для subplots_adjust и add_axes.
user1748155
2
Как я могу иметь одну цветную полосу для двух разных графиков рассеяния, которые у меня уже есть? Я пытался выше, но я не знаю, как заменить «я» с соответствующими переменными. Допустим, мои диаграммы рассеяния: plot1 = pylib.scatter (x, y, z) и plot2 = pylib.scatter (a, b, c)
Роут
46
Возможно, это было очевидно для других, но я хотел бы отметить, что для того, чтобы colourbar действительно точного представления цвета на всех участках, то vminи vmaxаргументы имеют решающее значение. Они контролируют цветовую гамму каждого участка. Если у вас есть реальные данные, вам может понадобиться пройти через это, чтобы сначала найти минимальное и максимальное значения.
Джеймс Оверс
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

abevieiramota
источник
4
Это решение сработало очень хорошо и кажется самым простым.
Kknd
8
Если вы измените nrows на 1, оба графика будут стрелкой, чем цветная полоса. Итак, как можно решить эту проблему?
Джин
6
Жаль, что это не работает с sharp_layout, но, тем не менее, хорошее решение.
Отметить
1
Просто чтобы вспомнить ... Я люблю это решение! Tinha Que Ser Cearense!
iury simoes-sousa
1
Важнейшая часть этого ответа fig.colorbar(im, ax=axes.ravel().tolist()). Если вы пропустите ax=axes.ravel().tolist(), цветовая шкала будет размещена в пределах одного субплота.
nyanpasu64
55

Это решение не требует ручной настройки расположения осей или размера цветовой шкалы, работает с многорядными и однорядными макетами и работает с ними tight_layout(). Он адаптирован из примера галереи , используя ImageGridнабор инструментов AxesGrid от 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, это отличный подход
Бретт
На самом деле работает с тесной связью, но я понятия не имею, как добавить метку к этой цветовой панели. Он не принимает метку kws, заголовок, текст ... что угодно! И документы не очень помогают.
ТомЧо
3
@TomCho Чтобы установить метку, вы можете захватить ручку Colorbar, когда вы его экземпляр, как: thecb = ax.cax.colorbar(im). Тогда вы можете сделатьthecb.set_label_text("foo")
раскрутка
1
Как изменить цветовую карту?
Сигур
1
@Sigur Я уверен, что вы уже поняли это, но для других вы можете изменить cmap при объявлении im: im = ax.imshow (data, 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()

KCH
источник
7
Этот метод не работает, если субплот не квадратный. Если вы измените nrows=1, цветовая шкала снова станет больше, чем субплоты.
Уэсли Тэнси
Каковы ваши значения по умолчанию в matplotlib? это здорово выглядит!
rafaelvalle
18

Как новичок, который наткнулся на эту ветку, я хотел бы добавить адаптацию python-for-dummies очень аккуратного ответа abevieiramota (потому что я нахожусь на том уровне, на котором мне пришлось искать 'ravel', чтобы понять, что их код делал)

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

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

RChapman
источник
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()

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

ImportanceOfBeingErnest
источник
Я не уверен, что мне разрешено спрашивать это здесь, но есть ли способ реализовать это решение, используя ax = fig.add_subplot()вместо этого? Я спрашиваю, потому что я не могу понять, как использовать его с базовой картой.
lanadaquenada
1
@lanadaquenada Да, это возможно, но вы должны были бы поставить GridSpecк add_subplot()в этом случае.
Важное значение
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

Функция colorbar предоставляет shrinkпараметр, который является масштабным коэффициентом для размера осей colorbar. Это требует ручной проб и ошибок. Например:

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

Массив изображений 1 x 3 с уменьшенной цветовой шкалой

раскрутить
источник
4

Чтобы добавить к отличному ответу @ abevieiramota, вы можете получить euqivalent of Proper_layout с 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-s разные? (Я предполагаю, что эти сопоставляемые объекты обрабатываются так же, как обрабатываются наборы контуров и наборы поверхностей.) У меня есть пример с использованием трехмерного графика поверхности ниже, который создает две цветовые полосы для субплота 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()

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

Enzoupi
источник