Matplotlib 2 Subplots, 1 Colorbar


235

Tôi đã dành quá nhiều thời gian để nghiên cứu làm thế nào để có được hai ô con để chia sẻ cùng một trục y với một thanh màu duy nhất được chia sẻ giữa hai ô trong Matplotlib.

Gì đã xảy ra là khi tôi được gọi là colorbar()chức năng trong một trong hai subplot1hoặc subplot2, nó sẽ autoscale cốt truyện như vậy mà colorbar cộng với cốt truyện sẽ phù hợp với bên trong hộp 'âm mưu phụ' bounding, khiến hai side-by-side lô là hai rất khác nhau kích cỡ.

Để giải quyết vấn đề này, tôi đã cố gắng tạo một subplot thứ ba mà sau đó tôi đã hack để kết xuất không có cốt truyện chỉ với một thanh màu. Vấn đề duy nhất là, bây giờ chiều cao và chiều rộng của hai ô không đồng đều, và tôi không thể tìm ra cách làm cho nó trông ổn.

Đây là mã của tôi:

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

Câu trả lời:


319

Chỉ cần đặt thanh màu theo trục của chính nó và sử dụng subplots_adjustđể nhường chỗ cho nó.

Ví dụ nhanh:

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

nhập mô tả hình ảnh ở đây

Lưu ý rằng phạm vi màu sẽ được đặt theo hình ảnh cuối cùng được vẽ (đã tăng lên im) ngay cả khi phạm vi giá trị được đặt theo vminvmax. Ví dụ, nếu một ô khác có giá trị tối đa cao hơn, các điểm có giá trị cao hơn giá trị tối đa imsẽ hiển thị bằng màu đồng nhất.


4
ImageGrid cũng rất hữu ích cho mục đích chính xác này.
Đám mây Phillip

5
nếu bạn cần sử dụng chặt chẽ_layout (), bạn sẽ muốn làm mọi thứ sau subplots_adjust sau chặt chẽ_layout, sau đó điều chỉnh tọa độ cho subplots_adjust và add_axes theo cách thủ công.
dùng1748155

2
Làm cách nào tôi có thể có một thanh màu duy nhất cho hai ô phân tán khác nhau mà tôi đã có? Tôi đã thử ở trên nhưng tôi không biết cách thay thế "im" bằng các biến thích hợp. Giả sử các biểu đồ phân tán của tôi là plot1 = pylib.scatter (x, y, z) và plot2 = pylib.scatter (a, b, c)
Rotail

46
Điều này có thể rõ ràng với những người khác, nhưng tôi muốn chỉ ra rằng để thanh màu thể hiện chính xác màu sắc trong tất cả các ô, các đối số vminvà các vmaxđối số là rất quan trọng. Họ kiểm soát phạm vi màu của từng ô nhỏ. Nếu bạn có dữ liệu thực, bạn có thể cần phải vượt qua điều này để tìm giá trị tối thiểu và tối đa trước.
James Owers

2
nếu phạm vi giá trị của các ô khác nhau, phạm vi thanh màu sẽ chỉ hiển thị phạm vi của ô cuối cùng, phải không? bất kỳ đề xuất?
Lukas

132

Bạn có thể đơn giản hóa mã của Joe Kington bằng cách sử dụng axtham số figure.colorbar()với danh sách các trục. Từ tài liệu :

cây rìu

Không có gì | (các) đối tượng trục cha mà từ đó không gian cho một trục thanh màu mới sẽ bị đánh cắp. Nếu một danh sách các trục được đưa ra, tất cả chúng sẽ được thay đổi kích thước để nhường chỗ cho các trục thanh màu.

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
Giải pháp này hoạt động rất tốt ở đây, và dường như là cách dễ nhất.
Kknd

8
Nếu bạn thay đổi mũi tên thành 1, cả hai lô đều bắn hơn so với thanh màu. Vì vậy, làm thế nào có thể giải quyết vấn đề này?
Jin

6
Đáng tiếc là nó không hoạt động với chặt chẽ, nhưng giải pháp tốt dù sao.
Đánh dấu

1
Chỉ cần nhớ ... Tôi thích giải pháp này! Tinha que ser cearense!
iury simoes-sousa

1
Phần quan trọng của câu trả lời này là fig.colorbar(im, ax=axes.ravel().tolist()). Nếu bạn bỏ qua ax=axes.ravel().tolist(), thanh màu sẽ được đặt trong một ô phụ.
nyanpasu64

54

Giải pháp này không yêu cầu điều chỉnh thủ công các vị trí trục hoặc kích thước thanh màu, hoạt động với bố cục nhiều hàng một hàng và hoạt động với tight_layout(). Nó được điều chỉnh từ một ví dụ về bộ sưu tập , sử dụng ImageGridtừ Hộp công cụ AxesGrid của 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()

lưới hình ảnh


Nhân đôi +1, đây là một cách tiếp cận tuyệt vời
Brett

Quả thực hoạt động với chặt chẽ, nhưng tôi không biết làm thế nào để thêm nhãn vào thanh màu đó. Nó không chấp nhận nhãn kws, tiêu đề, văn bản ... bất cứ điều gì! Và các tài liệu không giúp được gì nhiều.
TomCho

3
@TomCho Để đặt nhãn, bạn có thể lấy tay cầm của thanh màu khi bạn khởi tạo nó, như : thecb = ax.cax.colorbar(im). Sau đó, bạn có thể làmthecb.set_label_text("foo")
spinup

1
Làm thế nào để thay đổi colormap?
Sigur

1
@Sigur Tôi chắc chắn bạn đã tìm ra nó bây giờ, nhưng đối với những người khác, bạn có thể thay đổi cmap khi khai báo im: im = ax.imshow (data, vmin = 0, vmax = 1, cmap = 'your_cmap_here')
Shaun Lowis

38

Sử dụng make_axesthậm chí còn dễ dàng hơn và cho kết quả tốt hơn. Nó cũng cung cấp các khả năng để tùy chỉnh vị trí của thanh màu. Cũng lưu ý tùy chọn subplotschia sẻ trục x và 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
Phương pháp này không hoạt động khi subplot không vuông. Nếu bạn thay đổi nrows=1, thanh màu sẽ trở nên lớn hơn các ô con một lần nữa.
Wesley Tansey

Matplotlib của bạn mặc định là gì? no trông tuyệt!
rafaelvalle

18

Là một người mới bắt đầu tình cờ tìm thấy chủ đề này, tôi muốn thêm một câu trả lời rất gọn gàng cho câu trả lời rất gọn gàng của abevieiramota (bởi vì tôi ở cấp độ mà tôi phải tìm kiếm 'ravel' để tìm ra điều gì mã của họ đã được thực hiện):

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

Ít pythonic hơn, dễ dàng hơn nhiều cho những người mới như tôi để xem những gì thực sự xảy ra ở đây.


17

Như đã chỉ ra trong các câu trả lời khác, ý tưởng thường là xác định một trục cho thanh màu nằm trong đó. Có nhiều cách khác nhau để làm như vậy; một cái chưa được đề cập đến sẽ chỉ định trực tiếp các trục thanh màu khi tạo subplot với plt.subplots(). Ưu điểm là vị trí trục không cần phải được đặt thủ công và trong mọi trường hợp với khía cạnh tự động, thanh màu sẽ có cùng chiều cao với các ô con. Ngay cả trong nhiều trường hợp hình ảnh được sử dụng, kết quả sẽ được thỏa mãn như hình dưới đây.

Khi sử dụng plt.subplots(), việc sử dụng gridspec_kwđối số cho phép làm cho các trục thanh màu nhỏ hơn nhiều so với các trục khác.

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

Thí dụ:

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

nhập mô tả hình ảnh ở đây

Điều này hoạt động tốt, nếu khía cạnh của các ô được tự động hóa hoặc hình ảnh bị thu nhỏ do khía cạnh của chúng theo hướng chiều rộng (như ở trên). Tuy nhiên, nếu hình ảnh rộng hơn cao, kết quả sẽ như sau, có thể không mong muốn.

nhập mô tả hình ảnh ở đây

Một giải pháp để cố định chiều cao của thanh màu thành chiều cao của ô phụ sẽ được sử dụng mpl_toolkits.axes_grid1.inset_locator.InsetPositionđể đặt các trục của thanh màu tương ứng với các trục của ô phụ hình ảnh.

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

nhập mô tả hình ảnh ở đây


Tôi không chắc chắn liệu tôi có được phép hỏi điều này ở đây không, nhưng có cách nào để thực hiện giải pháp này bằng cách sử dụng ax = fig.add_subplot()không? Tôi đang hỏi bởi vì tôi không thể tìm ra cách sử dụng nó với sơ đồ cơ sở.
lanadaquenada

1
@lanadaquenada Có nghĩa là có thể, nhưng bạn sẽ cần phải cung cấp một GridSpecđể add_subplot()trong trường hợp đó.
ImportanceOfByingErnest

10

Giải pháp sử dụng danh sách các trục của abevieiramota hoạt động rất tốt cho đến khi bạn chỉ sử dụng một hàng hình ảnh, như được chỉ ra trong các bình luận. Sử dụng tỷ lệ khung hình hợp lý để figsizegiúp đỡ, nhưng vẫn còn xa hoàn hảo. Ví dụ:

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

Mảng hình ảnh 1 x 3

Hàm colorbar cung cấp shrinktham số là hệ số tỷ lệ cho kích thước của trục thanh màu. Nó đòi hỏi một số thử nghiệm và lỗi thủ công. Ví dụ:

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

Mảng hình ảnh 1 x 3 với thanh màu thu nhỏ


4

Để thêm vào câu trả lời tuyệt vời của @ abevieiramota, bạn có thể nhận được hiệu quả của tệp chặt chẽ với ràng buộc_layout. Bạn vẫn sẽ nhận được các khoảng trống ngang lớn nếu bạn sử dụng imshowthay pcolormeshvì tỷ lệ khung hình 1: 1 được áp đặt bởi 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()

nhập mô tả hình ảnh ở đây


1

Tôi nhận thấy rằng hầu hết mọi giải pháp được đăng có liên quan ax.imshow(im, ...)và không bình thường hóa các màu được hiển thị trên thanh màu cho nhiều cấu hình con. Ánh imxạ được lấy từ trường hợp cuối cùng, nhưng nếu các giá trị của nhiều- imkhác nhau thì sao? (Tôi giả sử các bản đồ này được xử lý giống như cách xử lý các bộ đường viền và bộ bề mặt.) ). Mặc dù câu hỏi yêu cầu rõ ràng cho một sự sắp xếp khác nhau, tôi nghĩ rằng ví dụ này giúp làm rõ một số điều. Tôi chưa tìm thấy cách nào để thực hiện việc này bằng cách sử dụng plt.subplots(...)trục 3D.

Lô ví dụ

Giá như tôi có thể định vị các thanh màu theo cách tốt hơn ... (Có lẽ có một cách tốt hơn để làm điều này, nhưng ít nhất nó không quá khó để làm theo.)

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

Nếu các giá trị từ nhiều ims khác nhau, chúng không nên sử dụng cùng một thanh màu, vì vậy câu hỏi ban đầu sẽ không thực sự được áp dụng
spinup

0

Chủ đề này được đề cập tốt nhưng tôi vẫn muốn đề xuất một cách tiếp cận khác trong một triết lý hơi khác.

Nó phức tạp hơn một chút để thiết lập nhưng nó cho phép (theo tôi) linh hoạt hơn một chút. Ví dụ: người ta có thể chơi với các tỷ lệ tương ứng của từng ô phụ / thanh màu:

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

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.