【问题标题】:Matplotlib how to plot 1 colorbar for four 2d histogramMatplotlib 如何为四个二维直方图绘制 1 个颜色条
【发布时间】:2019-08-19 18:28:32
【问题描述】:

在我开始之前,我想说我已经尝试关注 thisthis 在同一问题上的帖子,但是他们使用 imshow 热图进行操作,这与我正在做的 2d 直方图不同。

这是我的代码(实际数据已替换为随机生成的数据,但要点相同):

import matplotlib.pyplot as plt
import numpy as np

def subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles):
    fig, a = plt.subplots(2, 2)

    a = a.ravel()
    for idx, ax in enumerate(a):
        image = ax.hist2d(x_data[idx], y_data[idx], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[idx], fontsize=12)
        ax.set_xlabel(x_labels[idx])
        ax.set_ylabel(y_labels[idx])
        ax.set_aspect("equal")
    cb = fig.colorbar(image[idx])
    cb.set_label("Intensity", rotation=270)

    # pad = how big overall pic is
    # w_pad = how separate they're left to right
    # h_pad = how separate they're top to bottom
    plt.tight_layout(pad=-1, w_pad=-10, h_pad=0.5)

x1, y1 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x2, y2 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x3, y3 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x4, y4 = np.random.uniform(-2, 2, 10000), np.random.uniform(-2, 2, 10000)
x_data = [x1, x2, x3, x4]
y_data = [y1, y2, y3, y4]
x_labels = ["x1", "x2", "x3", "x4"]
y_labels = ["y1", "y2", "y3", "y4"]
titles = ["1", "2", "3", "4"]
subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles)

这就是它正在生成的:

所以现在我的问题是我无法让颜色条适用于所有 4 个直方图。同样出于某种原因,与其他直方图相比,右下角直方图的行为似乎很奇怪。在我发布的链接中,他们的方法似乎没有使用a = a.ravel(),我只在这里使用它,因为这是让我将 4 个直方图绘制为子图的唯一方法。帮助?

编辑: Thomas Kuhn 您的新方法实际上解决了我所有的问题,直到我放下标签并尝试使用plt.tight_layout() 来解决重叠问题。似乎如果我在plt.tight_layout(pad=i, w_pad=0, h_pad=0) 中输入特定参数,那么颜色条就会开始出现异常。我现在将解释我的问题。

我已经对你的新方法进行了一些更改,使其适合我想要的,就像这样

def test_hist_2d(x_data, y_data, x_labels, y_labels, titles):
    nrows, ncols = 2, 2
    fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True)
    ##produce the actual data and compute the histograms
    mappables=[]
    for (i, j), ax in np.ndenumerate(axes):
        H, xedges, yedges = np.histogram2d(x_data[i][j], y_data[i][j], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[i][j], fontsize=12)
        ax.set_xlabel(x_labels[i][j])
        ax.set_ylabel(y_labels[i][j])
        ax.set_aspect("equal")
        mappables.append(H)

    ##the min and max values of all histograms
    vmin = np.min(mappables)
    vmax = np.max(mappables)

    ##second loop for visualisation
    for ax, H in zip(axes.ravel(), mappables):
        im = ax.imshow(H,vmin=vmin, vmax=vmax, extent=[-2,2,-2,2])
    
    ##colorbar using solution from linked question
    fig.colorbar(im,ax=axes.ravel())
    plt.show()
#    plt.tight_layout
#    plt.tight_layout(pad=i, w_pad=0, h_pad=0)

现在如果我尝试生成我的数据,在这种情况下:

phi, cos_theta = get_angles(runs)

detector_x1, detector_y1, smeared_x1, smeared_y1 = detection_vectorised(1.5, cos_theta, phi)
detector_x2, detector_y2, smeared_x2, smeared_y2 = detection_vectorised(1, cos_theta, phi)
detector_x3, detector_y3, smeared_x3, smeared_y3 = detection_vectorised(0.5, cos_theta, phi)
detector_x4, detector_y4, smeared_x4, smeared_y4 = detection_vectorised(0, cos_theta, phi)

这里detector_x, detector_y, smeared_x, smeared_y是所有数据点列表 所以现在我将它们放入2x2 列表中,以便它们可以通过我的绘图函数适当地解包,如下所示:

data_x = [[detector_x1, detector_x2], [detector_x3, detector_x4]]
data_y = [[detector_y1, detector_y2], [detector_y3, detector_y4]]
x_labels = [["x positions(m)", "x positions(m)"], ["x positions(m)", "x positions(m)"]]
y_labels = [["y positions(m)", "y positions(m)"], ["y positions(m)", "y positions(m)"]]
titles = [["0.5m from detector", "1.0m from detector"], ["1.5m from detector", "2.0m from detector"]]

我现在运行我的代码

test_hist_2d(data_x, data_y, x_labels, y_labels, titles)

只打开plt.show(),它会给出这个:

这很棒,因为在数据和视觉方面,这正是我想要的,即颜色图对应于所有 4 个直方图。但是,由于标签与标题重叠,我想我会运行相同的东西,但这次使用plt.tight_layout(pad=a, w_pad=b, h_pad=c) 希望我能够调整重叠标签问题。但是这一次我如何更改数字 a, bc 并不重要,我总是让我的颜色条位于图表的第二列,如下所示:

现在更改a 只会使整个子图变大或变小,我能做的最好的就是用plt.tight_layout(pad=-10, w_pad=-15, h_pad=0) 调整它,看起来像这样

看来无论你的新方法在做什么,它都让整个情节失去了可调整性。你的解决方案在解决一个问题方面同样出色,反过来又创造了另一个问题。那么在这里做什么最好呢?

编辑 2:

fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True, constrained_layout=True)plt.show() gives 一起使用

如您所见,子图的列之间仍然存在垂直间隙,即使使用 plt.subplots_adjust() 也无法消除。

【问题讨论】:

  • 如果你有足够新的 matplotlib colorbar(image[idx], ax=a) 应该可以工作。
  • @ThomasKühn 我不确定您是否阅读了我的问题。我特别说过,您刚刚发布的链接中的帖子,我已经尝试了他们的方法,但它们对我不起作用。
  • 对不起,我显然准备得不够彻底。
  • 您的新问题似乎与直方图无关;像往常一样minimal reproducible examples 会有所帮助。该问题可能会在较新版本的 matplotlib 中得到解决,因此报告您的 matplotlib 版本至关重要。另外,你可以试试constrained_layout;这是tight_layout 的替代品,在某些情况下效果更好。
  • 好的,非常感谢!

标签: python-3.x matplotlib subplot colorbar


【解决方案1】:

编辑

正如 cmets 中所指出的,这里最大的问题实际上是使许多直方图的颜色条有意义,因为ax.hist2d 将始终缩放它从numpy 接收到的直方图数据。因此,最好先使用numpy 计算二维直方图数据,然后再次使用imshow 将其可视化。这样,linked question 的解决方案也可以应用。为了使归一化问题更加明显,我使用scipy.stats.multivariate_normal 制作了一些质量不同的二维直方图,这表明即使每个图中的样本数量相同,直方图的高度也会发生相当大的变化.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec as gs
from scipy.stats import multivariate_normal

##opening figure and axes
nrows=3
ncols=3
fig, axes = plt.subplots(nrows,ncols)

##generate some random data for the distributions
means = np.random.rand(nrows,ncols,2)
sigmas = np.random.rand(nrows,ncols,2)
thetas = np.random.rand(nrows,ncols)*np.pi*2

##produce the actual data and compute the histograms
mappables=[]
for mean,sigma,theta in zip( means.reshape(-1,2), sigmas.reshape(-1,2), thetas.reshape(-1)):

    ##the data (only cosmetics):    
    c, s = np.cos(theta), np.sin(theta)
    rot = np.array(((c,-s), (s, c)))
    cov = rot@np.diag(sigma)@rot.T
    rv = multivariate_normal(mean,cov)
    data = rv.rvs(size = 10000)

    ##the 2d histogram from numpy
    H,xedges,yedges = np.histogram2d(data[:,0], data[:,1], bins=50, range=[[-2, 2],[-2, 2]])

    mappables.append(H)

##the min and max values of all histograms
vmin = np.min(mappables)
vmax = np.max(mappables)

##second loop for visualisation
for ax,H in zip(axes.ravel(),mappables):
    im = ax.imshow(H,vmin=vmin, vmax=vmax, extent=[-2,2,-2,2])

##colorbar using solution from linked question
fig.colorbar(im,ax=axes.ravel())

plt.show()

这段代码会产生这样的图形:

旧答案

解决问题的一种方法是显式地为颜色条生成空间。您可以使用 GridSpec 实例来定义颜色条的宽度。在您的 subplots_hist_2d() 函数下方进行一些修改。请注意,您对tight_layout() 的使用将颜色条转移到了一个有趣的地方,因此进行了替换。如果您希望情节彼此更接近,我宁愿建议使用图形的纵横比。

def subplots_hist_2d(x_data, y_data, x_labels, y_labels, titles):

##    fig, a = plt.subplots(2, 2)
    fig = plt.figure()
    g = gs.GridSpec(nrows=2, ncols=3, width_ratios=[1,1,0.05])
    a = [fig.add_subplot(g[n,m]) for n in range(2) for m in range(2)]
    cax = fig.add_subplot(g[:,2])


##    a = a.ravel()
    for idx, ax in enumerate(a):
        image = ax.hist2d(x_data[idx], y_data[idx], bins=50, range=[[-2, 2],[-2, 2]])
        ax.set_title(titles[idx], fontsize=12)
        ax.set_xlabel(x_labels[idx])
        ax.set_ylabel(y_labels[idx])
        ax.set_aspect("equal")
##    cb = fig.colorbar(image[-1],ax=a)
    cb = fig.colorbar(image[-1], cax=cax)
    cb.set_label("Intensity", rotation=270)

    # pad = how big overall pic is
    # w_pad = how separate they're left to right
    # h_pad = how separate they're top to bottom
##    plt.tight_layout(pad=-1, w_pad=-10, h_pad=0.5)
    fig.tight_layout()

使用这个修改后的函数,我得到以下输出:

【讨论】:

  • 那个答案可能更适合Matplotlib 2 Subplots, 1 Colorbar。正如我所看到的,这里主要和唯一的区别不是条形图的定位,而是颜色标准化。在这里,您获取最后一张图像的颜色条并将其放置,以便人们可能认为它适用于每个情节 - 这是不正确的。
  • @ImportanceOfBeingErnest 您对颜色条的标准化是正确的,但是在您链接的解决方案中这不是问题吗?在那里,似乎总是使用相应 for 循环的最后一个可映射对象。但是,以不同的方式标准化应该不会太难。我稍后再看看。
  • 如果是imshow,它很容易标准化,如果是hist2d,你需要在计算直方图之前知道它的结果。所以我想解决方案是首先计算所有直方图,然后找到最小值和最大值,然后将它们全部绘制为 vmin 和 vmax。
  • @ImportanceOfBeingErnest 这可能是最好的解决方案,使用numpy.histogram2d 计算所有数据,然后再次使用imshow 进行实际绘图。然后可以回退到链接问题中的答案。
  • @ThomasKühn 你好,你的新方法似乎引入了一个关于情节可调整性的新问题。请您阅读我的编辑。提前致谢。
猜你喜欢
  • 2016-11-01
  • 1970-01-01
  • 2012-10-20
  • 1970-01-01
  • 1970-01-01
  • 2021-10-13
  • 2020-09-07
  • 2015-05-29
  • 1970-01-01
相关资源
最近更新 更多