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

时间:2019-02-06 02:17:06

标签: 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()

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

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 colorbarpython interplay between axis('square') and set_xlim之类的问题。

2 个答案:

答案 0 :(得分:4)

解决该问题的一种方法是使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

当然,只要调整图的大小,方形部分就会消失。但是,如果您已经知道图形的最终大小,并且只想保存以备将来使用,那么这应该是一个足够好的快速解决方案。

答案 1 :(得分:1)

axes_grid1的{​​{1}}的工作方式与通常的子图略有不同。它不能直接处理方面,因为轴的大小是在绘制时以相对绝对坐标确定的。

如果需要,可以在绝对坐标中手动指定轴的大小,以获得正方形子图。

Divider

enter image description here

从栅格始终为2.8 + 0.1 + 1 = 3.9英寸宽和高的意义上来说,该解决方案对于图形尺寸的更改是强大的。因此,只需要确保图形尺寸始终足够大即可容纳网格。否则,它可能会裁剪边际图,看起来像这样:

enter image description here

要获得一种仍能随图形大小缩放的自适应解决方案,可以定义一个自定义import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size 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) horiz = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)] vert = [axes_size.Fixed(2.8), axes_size.Fixed(.1), axes_size.Fixed(1)] divider.set_horizontal(horiz) divider.set_vertical(vert) ax.scatter(x, y) xhax.hist(x) yhax.hist(y, orientation="horizontal") plt.setp(xhax.get_xticklabels(), visible=False) plt.setp(yhax.get_yticklabels(), visible=False) plt.show() ,该样式将使用其余绝对大小的填充轴和边缘轴,并返回绝对坐标中的最小值(英寸),以确保两个轴始终为正方形。

Size

enter image description here enter image description here

请注意,边距的大小如何始终为1英寸,而与图形大小无关,分散轴如何调整为剩余空间并保持正方形。