Matplotlib 绘图不符合图形尺寸
我正在尝试使用GridSpec方法创建一个特定大小的图形,并且这个大小是4.205 x 2.2752英寸,目的是让它适合在学术图表中作为一个面板。我使用了fig.subplots_adjust来让子图填满整个图形。然而,输出的图形尺寸却完全不同。以下是我的代码:
# set up the figure
fig = plt.figure(figsize = (4.2055, 2.2752))
# number of columns in the Gridspec
fwid = 22
# set up the grid
grid = plt.GridSpec(1,fwid)
# set up the number of columns allocated to each subplot
h0 = int((fwid-1)/3)
h1 = int(h0*2)
ax = [
fig.add_subplot(grid[:h0]),
fig.add_subplot(grid[h0:h1]),
fig.add_subplot(grid[h1]),
]
hm1 = np.random.randn(10,10)
hm2 = np.random.randn(10,10)
# plot the heatmaps
sns.heatmap(
hm1, ax = ax[0],
vmin = 0, vmax = 1,
cbar = False
)
sns.heatmap(
hm2, ax = ax[1],
vmin = 0, vmax = 1,
# put the colorbar in the 3rd subplot
cbar_ax = ax[2]
)
ax[0].set_xticks(np.arange(0.5,10))
ax[0].set_xticklabels(np.arange(0.5,10))
ax[0].set_yticks(np.arange(0.5,10))
ax[1].set_yticks([])
ax[1].set_xticks(np.arange(0.5, 10))
ax[1].set_xticklabels(np.arange(0.5,10))
fig.subplots_adjust(left = 0, right = 1, bottom = 0, top = 1)
plt.savefig('example.png', facecolor = 'white', transparent = False,
bbox_inches = 'tight', dpi = 300)
它的尺寸不是4.205 x 2.2752英寸,而是2.96 x 2.3166英寸。我不太明白这是怎么回事。希望能得到一些帮助!
1 个回答
使用Matplotlib的GridSpec
子图精确调整自定义图像文件输出尺寸(包括用于颜色条图例的第三个axes
对象)
下面实施的关键更改:
- 在创建图像时明确设置DPI(每英寸点数)
- 明确设置坐标轴的宽高比(使用
Axes.set_aspect
) - 了解
matplotlib.pyplot.savefig.bbox_inches
参数的功能,以确保Matplotlib中的图像输出尺寸
还需要实施一些额外的更改(见下面的代码)。实际上,这里采用的方法是经过一些反复试验,微调figsize
、DPI和子图边距(见:matplotlib.pyplot.subplots_adjust
),然后使用PIL
进行图像裁剪。最终结果如图所示,是一个由两个Seaborn热图子图组成的Matplotlib GridSpec
数组图,整齐地填充了你指定的尺寸。
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from PIL import Image
# Define the figure dimensions in inches
w = 4.2055
h = 2.2752
# Set up the figure with a specific DPI
fig = plt.figure(figsize=(w * 1.5, h * 1.5), dpi=300)
# Number of columns in the GridSpec
fwid = 22
# Set up the grid
grid = gridspec.GridSpec(1, fwid)
# Set up the number of columns allocated to each subplot
h0 = int((fwid - 1) / 3)
h1 = int(h0 * 2)
ax = [
fig.add_subplot(grid[:h0]),
fig.add_subplot(grid[h0:h1]),
fig.add_subplot(grid[h1]),
]
hm1 = np.random.randn(10, 10)
hm2 = np.random.randn(10, 10)
sns.heatmap(
hm1, ax=ax[0], vmin=0, vmax=1, cbar=False,
)
sns.heatmap(hm2, ax=ax[1], vmin=0, vmax=1, cbar_ax=ax[2])
ax[0].set_title("Heatmap 1", size=8)
ax[1].set_title("Heatmap 2", size=8)
ax[0].set_ylabel("Y Axis Label", size=6)
# Move the x-axis label downwards and adjust its position
ax[1].set_xlabel("X Axis Label", labelpad=15, size=6)
ax[1].xaxis.set_label_coords(0, -0.15)
# Remove y-axis tick labels for the second heatmap
ax[1].set_yticks([])
# Decrease axes tick labels font size for better readability
for a in ax:
a.tick_params(
axis="both", which="major", labelsize=4, length=2, width=0.25
)
# Explicitly alter the aspect ratios for the three axes in the grid
ax[0].set_aspect((h * 1.75) / w)
ax[1].set_aspect((h * 1.75) / w)
ax[2].set_aspect(10)
# Adjust the figure margins and size (add space in between the two subplots)
plt.subplots_adjust(wspace=2)
plt.savefig(
"example.png",
facecolor="white",
transparent=False,
dpi=300, # , bbox_inches="tight"
)
# Crop high-resolution output figure
image = Image.open("example.png")
crop_box = (80, 200, 1340, 830)
cropped_image = image.crop(crop_box)
cropped_image.save(
"cropped_fig.png", dpi=(image.info["dpi"][0], image.info["dpi"][1])
)
(有关子图实现的更多信息,请参见GridSpec
文档。)
最终生成的输出(cropped_fig.png
)是:
而Matplotlib的直接输出是(黑色虚线边框的测量与指定尺寸精确匹配;这个和红色尺寸标注是在Photoshop中添加的,但图像文件大小并没有被裁剪 - 也就是说,Matplotlib输出的原始尺寸是保留的):
在提供的解决方案中,Seaborn子图的坐标轴对象的宽高比被明确修改,以有效拉伸/加宽热图,从而填满指定尺寸的区域。然而,使用上述所有可自定义的图形和坐标轴参数的值,会导致输出图像中产生大量的空白边距(见上面的截图,查看裁剪前的原始Matplotlib输出图像文件)。为了解决这个问题,图像的初始化尺寸是所需尺寸乘以一个比例因子(即,figsize=(w * 1.5, h * 1.5)
),这样最终生成的可视化在裁剪后尽可能接近所需大小。这些参数的微调值当然可以根据需要进行调整。
*注意: 使用bbox_inches="tight"
会导致输出文件的尺寸与指定的尺寸不完全一致。