python - 当数据限制不同时,创建具有多个轴的等宽(正方形)图?

标签 python matplotlib

我想使用 make_axes_locateable 创建一个使用多个轴的方形图,如 matplotlib documentation 中所示.然而,虽然这适用于 x 和 y 数据具有相同范围的图,但当范围相差几个数量级时它不起作用。

import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.axes_grid1 import make_axes_locatable

x = np.random.normal(512, 112, 240)
y = np.random.normal(0.5, 0.1, 240)

_, ax = plt.subplots()
divider = make_axes_locatable(ax)

xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)

ax.scatter(x, y)
xhax.hist(x)
yhax.hist(y, orientation="horizontal")

x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
ax.set_aspect(abs(x1-x0)/abs(y1-y0))

plt.show()

虽然此代码使用 set_aspect 答案,如 How do I make a matplotlib scatter plot square?轴未正确修改,如下所示:

enter image description here

我试图通过以下方式修复此问题:

ax.set_aspect(abs(x1-x0)/abs(y1-y0), share=True)

但这导致了以下结果:

enter image description here

在调用散点图之后和创建两个直方图轴之前设置纵横比似乎没有效果,即使在文档示例中似乎已经完成了。当数据范围相同时,此代码确实有效:

enter image description here

更新:这个问题的关键限制之一是使用 make_axes_locateable 而不是 GridSpec,如以下评论中所述。我正在处理的问题涉及创建绘图函数,该函数接受 Axes 对象来处理和修改它,而无需了解图形或图中的任何其他 Axes,如以下代码所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as grid

from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size


def joint_plot(x, y, ax=None):
    """
    Create a square joint plot of x and y.
    """
    if ax is None:
        ax = plt.gca()

    divider = make_axes_locatable(ax)
    xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
    yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)

    ax.scatter(x, y)
    xhax.hist(x)
    yhax.hist(y, orientation="horizontal")

    x0,x1 = ax.get_xlim()
    y0,y1 = ax.get_ylim()
    ax.set_aspect(abs(x1-x0)/abs(y1-y0))

    plt.sca(ax)
    return ax, xhax, yhax


def color_plot(x, y, colors, ax=None):
    if ax is None:
        ax = plt.gca()

    divider = make_axes_locatable(ax)
    cbax = divider.append_axes("right", size="5%", pad=0.1)

    sc = ax.scatter(x, y, marker='o', c=colors, cmap='RdBu')
    plt.colorbar(sc, cax=cbax)

    ax.set_aspect("equal")

    plt.sca(ax)
    return ax, cbax


if __name__ == "__main__":
    _, axes = plt.subplots(nrows=2, ncols=2, figsize=(9,6))

    # Plot 1
    x = np.random.normal(100, 17, 120)
    y = np.random.normal(0.5, 0.1, 120)
    joint_plot(x, y, axes[0,0])

    # Plot 2
    x = np.random.normal(100, 17, 120)
    y = np.random.normal(100, 17, 120)
    c = np.random.normal(100, 17, 120)
    color_plot(x, y, c, axes[0,1])

    # Plot 3
    x = np.random.normal(100, 17, 120)
    y = np.random.normal(0.5, 0.1, 120)
    c = np.random.uniform(0.0, 1.0, 120)
    color_plot(x, y, c, axes[1,0])

    # Plot 4
    x = np.random.normal(0.5, 0.1, 120)
    y = np.random.normal(0.5, 0.1, 120)
    joint_plot(x, y, axes[1,1])

    plt.tight_layout()
    plt.show()

enter image description here

这个问题扩展了诸如 Set equal aspect in plot with colorbar 之类的问题和 python interplay between axis('square') and set_xlim因为 Axes-only 约束。

最佳答案

处理该问题的一种方法是保持 x 轴和 y 轴的数据限制相等。这可以通过将值标准化为介于 0 和 1 之间来完成。这样命令 ax.set_aspect('equal') 可以按预期工作。当然,如果只这样做,刻度标签的范围只会从 0 到 1,因此必须应用一点 matplotlib 魔法来将刻度标签调整到原始数据范围。答案here显示了如何使用 FuncFormatter 来完成此操作。然而,由于原始刻度是根据区间 [0,1] 选择的,单独使用 FuncFormatter 将导致奇数刻度,例如如果该因子是 635,则 0.2 的原始刻度将变为 127。要获得“不错”的刻度,还可以使用 AutoLocator,它可以使用 tick_values 计算原始数据范围的刻度() 函数。然后可以将这些刻度再次缩放到区间 [0,1],然后 FuncFormatter 可以计算刻度标签。它有点复杂,但最终只需要大约 10 行额外的代码:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

from mpl_toolkits.axes_grid1 import make_axes_locatable

x = np.random.normal(512, 112, 240)
y = np.random.normal(0.5, 0.1, 240)


fig,ax=plt.subplots()

divider = make_axes_locatable(ax)


##increased pad from 0.1 to 0.2 so that tick labels don't overlap
xhax = divider.append_axes("top", size=1, pad=0.2, sharex=ax)
yhax = divider.append_axes("right", size=1, pad=0.2, sharey=ax)

##'normalizing' x and y values to be between 0 and 1:
xn = (x-min(x))/(max(x)-min(x))
yn = (y-min(y))/(max(y)-min(y))

##producinc the plots
ax.scatter(xn, yn)
xhax.hist(xn)
yhax.hist(yn, orientation="horizontal")

##turning off duplicate ticks (if needed):
plt.setp(xhax.get_xticklabels(), visible=False)
plt.setp(yhax.get_yticklabels(), visible=False)

ax.set_aspect('equal')


##setting up ticks and labels to simulate real data:
locator = mticker.AutoLocator()

xticks = (locator.tick_values(min(x),max(x))-min(x))/(max(x)-min(x))
ax.set_xticks(xticks)
ax.xaxis.set_major_formatter(mticker.FuncFormatter(
    lambda t, pos: '{0:g}'.format(t*(max(x)-min(x))+min(x))
))

yticks = (locator.tick_values(min(y),max(y))-min(y))/(max(y)-min(y))
ax.set_yticks(yticks)
ax.yaxis.set_major_formatter(mticker.FuncFormatter(
    lambda t, pos: '{0:g}'.format(t*(max(y)-min(y))+min(y))
))

fig.tight_layout()
plt.show()

生成的图片看起来符合预期,并且在调整图像大小时也保持方形。

旧答案:

这与其说是解决方案,不如说是一种变通方法:

不使用 ax.set_aspect(),您可以通过向 提供 figsize=(n,n) 来设置您的图形,使其成为正方形>plt.subplots,其中 n 是以英寸为单位的宽度和高度。由于 xhax 的高度和 yhax 的宽度都是 1 英寸,这意味着 ax 也变成了正方形。

import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.axes_grid1 import make_axes_locatable

x = np.random.normal(512, 112, 240)
y = np.random.normal(0.5, 0.1, 240)

fig, ax = plt.subplots(figsize=(5,5))

divider = make_axes_locatable(ax)

xhax = divider.append_axes("top", size=1, pad=0.1, sharex=ax)
yhax = divider.append_axes("right", size=1, pad=0.1, sharey=ax)

ax.scatter(x, y)
xhax.hist(x)
yhax.hist(y, orientation="horizontal")

##turning off duplicate ticks:
plt.setp(xhax.get_xticklabels(), visible=False)
plt.setp(yhax.get_yticklabels(), visible=False)

plt.show()

结果是这样的:

enter image description here

当然,一旦你调整了你的身材,正方形就会消失。但是,如果您已经知道图形的最终大小并且只想保存它以供进一步使用,那么这应该是一个足够好的快速修复。

关于python - 当数据限制不同时,创建具有多个轴的等宽(正方形)图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54545758/

相关文章:

python - 将反向特征添加到插入排序的典型方法是什么?

python - Keras 自定义数据生成器,适用于无法放入内存的大型 hdf5 文件

python - 关于 matplotlib 中的子图和子子图位置

python - 使用 pandas 和 pyplot 绘图时出现内存泄漏

python - 在 Sagemath 中提取数字的第 n 位数字

python - 基本的 python 缩进/缩进问题

python - matplotlib 中具有相同数量 xticklabels 的六个子图

3d - 设置/获取Matplotlib Axes3D的3D观看方向

python - 在 py.test 中的每个测试之前和之后运行代码?

python - 更改颜色条宽度