Matplotlib 如何为四个 2d 直方图绘制 1 个颜色条
Matplotlib how to plot 1 colorbar for four 2d histogram
在我开始之前,我想说的是,我已经尝试按照 this and this post 解决相同的问题,但是他们使用 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 个直方图。同样出于某种原因,与其他直方图相比,右下角的直方图似乎表现得很奇怪。在我 posted 的链接中,他们的方法似乎没有使用 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, b
和 c
,我总是让我的颜色条位于图表的第二列,如下所示:
现在改变a
只会让整个子图变大或变小,我能做的最好的就是用plt.tight_layout(pad=-10, w_pad=-15, h_pad=0)
调整它,看起来像这样
所以看来无论你的新方法是做什么的,都让整个情节失去了可调整性。您的解决方案在 return 中解决了一个问题,但创造了另一个问题。那么在这里做什么最好呢?
编辑 2:
使用 fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True, constrained_layout=True)
和 plt.show() gives
如您所见,子图的列之间仍然存在垂直间隙,即使使用 plt.subplots_adjust()
也无法消除。
编辑:
正如评论中所指出的,这里最大的问题实际上是使许多直方图的颜色条有意义,因为 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()
此代码生成如下图:
旧答案:
解决您的问题的一种方法是明确地为您的颜色栏生成 space。您可以使用 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()
使用这个修改后的函数,我得到以下输出:
在我开始之前,我想说的是,我已经尝试按照 this and this post 解决相同的问题,但是他们使用 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 个直方图。同样出于某种原因,与其他直方图相比,右下角的直方图似乎表现得很奇怪。在我 posted 的链接中,他们的方法似乎没有使用 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, b
和 c
,我总是让我的颜色条位于图表的第二列,如下所示:
现在改变a
只会让整个子图变大或变小,我能做的最好的就是用plt.tight_layout(pad=-10, w_pad=-15, h_pad=0)
调整它,看起来像这样
所以看来无论你的新方法是做什么的,都让整个情节失去了可调整性。您的解决方案在 return 中解决了一个问题,但创造了另一个问题。那么在这里做什么最好呢?
编辑 2:
使用 fig, axes = plt.subplots(nrows, ncols, sharex=True, sharey=True, constrained_layout=True)
和 plt.show() gives
如您所见,子图的列之间仍然存在垂直间隙,即使使用 plt.subplots_adjust()
也无法消除。
编辑:
正如评论中所指出的,这里最大的问题实际上是使许多直方图的颜色条有意义,因为 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()
此代码生成如下图:
旧答案:
解决您的问题的一种方法是明确地为您的颜色栏生成 space。您可以使用 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()
使用这个修改后的函数,我得到以下输出: