Matplotlib 绘图不符合图形尺寸

1 投票
1 回答
51 浏览
提问于 2025-04-13 16:50

我正在尝试使用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 个回答

1

使用Matplotlib的GridSpec子图精确调整自定义图像文件输出尺寸(包括用于颜色条图例的第三个axes对象)

下面实施的关键更改:

还需要实施一些额外的更改(见下面的代码)。实际上,这里采用的方法是经过一些反复试验,微调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)是:

GridSpec双热图,明确定义的图像尺寸

而Matplotlib的直接输出是(黑色虚线边框的测量与指定尺寸精确匹配;这个和红色尺寸标注是在Photoshop中添加的,但图像文件大小并没有被裁剪 - 也就是说,Matplotlib输出的原始尺寸是保留的):

Photoshop标注的Matplotlib直接图像输出

在提供的解决方案中,Seaborn子图的坐标轴对象的宽高比被明确修改,以有效拉伸/加宽热图,从而填满指定尺寸的区域。然而,使用上述所有可自定义的图形和坐标轴参数的值,会导致输出图像中产生大量的空白边距(见上面的截图,查看裁剪前的原始Matplotlib输出图像文件)。为了解决这个问题,图像的初始化尺寸是所需尺寸乘以一个比例因子(即,figsize=(w * 1.5, h * 1.5)),这样最终生成的可视化在裁剪后尽可能接近所需大小。这些参数的微调值当然可以根据需要进行调整。


*注意: 使用bbox_inches="tight"会导致输出文件的尺寸与指定的尺寸不完全一致。

撰写回答