matplotlib:我可以创建 AxesSubplot 对象,然后将它们添加到 Figure 实例吗?

Posted

技术标签:

【中文标题】matplotlib:我可以创建 AxesSubplot 对象,然后将它们添加到 Figure 实例吗?【英文标题】:matplotlib: can I create AxesSubplot objects, then add them to a Figure instance? 【发布时间】:2011-09-12 15:51:55 【问题描述】:

查看matplotlib 文档,似乎将AxesSubplot 添加到Figure 的标准方法是使用Figure.add_subplot

from matplotlib import pyplot

fig = pyplot.figure()
ax = fig.add_subplot(1,1,1)
ax.hist( some params .... )

我希望能够独立于图形创建类似AxesSubPlot 的对象,这样我就可以在不同的图形中使用它们。类似的东西

fig = pyplot.figure()
histoA = some_axes_subplot_maker.hist( some params ..... )
histoA = some_axes_subplot_maker.hist( some other params ..... )
# make one figure with both plots
fig.add_subaxes(histo1, 211)
fig.add_subaxes(histo1, 212)
fig2 = pyplot.figure()
# make a figure with the first plot only
fig2.add_subaxes(histo1, 111)

这在matplotlib 中是否可行,如果可以,我该怎么做?

更新:我还没有设法解耦轴和图形的创建,但是按照下面答案中的示例,可以轻松地在新的或 olf Figure 实例中重新使用以前创建的轴。这可以用一个简单的函数来说明:

def plot_axes(ax, fig=None, geometry=(1,1,1)):
    if fig is None:
        fig = plt.figure()
    if ax.get_geometry() != geometry :
        ax.change_geometry(*geometry)
    ax = fig.axes.append(ax)
    return fig

【问题讨论】:

您的函数 plot_axes 似乎不再起作用了。 【参考方案1】:

通常,您只需将坐标区实例传递给函数。

例如:

import matplotlib.pyplot as plt
import numpy as np

def main():
    x = np.linspace(0, 6 * np.pi, 100)

    fig1, (ax1, ax2) = plt.subplots(nrows=2)
    plot(x, np.sin(x), ax1)
    plot(x, np.random.random(100), ax2)

    fig2 = plt.figure()
    plot(x, np.cos(x))

    plt.show()

def plot(x, y, ax=None):
    if ax is None:
        ax = plt.gca()
    line, = ax.plot(x, y, 'go')
    ax.set_ylabel('Yabba dabba do!')
    return line

if __name__ == '__main__':
    main()

要回答您的问题,您总是可以这样做:

def subplot(data, fig=None, index=111):
    if fig is None:
        fig = plt.figure()
    ax = fig.add_subplot(index)
    ax.plot(data)

此外,您可以简单地将轴实例添加到另一个图形:

import matplotlib.pyplot as plt

fig1, ax = plt.subplots()
ax.plot(range(10))

fig2 = plt.figure()
fig2.axes.append(ax)

plt.show()

调整它的大小以匹配其他子图“形状”也是可能的,但它很快就会变得比它的价值更麻烦。根据我的经验,对于复杂情况,仅传递图形或轴实例(或实例列表)的方法要简单得多...

【讨论】:

+1 这很有用,但在我看来,轴仍然与图形和/或 pyplot 中的某些状态耦合。按照您的示例,我无法真正将轴创建与图形制作和绘图分离。 轴从根本上链接到 matplotlib 中的特定图形。没有办法解决这个问题。但是,您仍然可以通过传递轴和图形对象来完全“将轴创建与图形制作和绘图分离”。我不太确定我是否遵循您想要做的... 嗯,实际上,我想它们并没有我想象的那样有根本的联系。您可以将相同的轴添加到不同的图形。 (只需做fig2.axes.append(ax1))调整它的大小以匹配不同的子图形状也是可能的。不过,这可能会带来更多的麻烦而不是它的价值...... 在 Enthought 7.3-2 (matplotlib 1.1.0) 中将轴实例添加到另一个图形(最后一个示例)对我不起作用。 @aaren - 它不起作用,因为在较新版本的 matplotlib 中,图形的轴堆叠方式已更改。现在故意不应该在不同的人物之间共享轴。作为一种解决方法,您可以这样做fig2._axstack.add(fig2._make_key(a), a),但它很老套,将来可能会改变。它似乎工作正常,但它可能会破坏一些东西。【参考方案2】:

下面显示了如何将轴从一个图形“移动”到另一个图形。这是 @JoeKington's last example 的预期功能,在较新的 matplotlib 版本中不再起作用,因为轴不能同时存在于多个图形中。

您首先需要从第一个图中删除轴,然后将其附加到下一个图中并给它一些位置。

import matplotlib.pyplot as plt

fig1, ax = plt.subplots()
ax.plot(range(10))
ax.remove()

fig2 = plt.figure()
ax.figure=fig2
fig2.axes.append(ax)
fig2.add_axes(ax)

dummy = fig2.add_subplot(111)
ax.set_position(dummy.get_position())
dummy.remove()
plt.close(fig1)

plt.show()

【讨论】:

小补充:当plt.show()fig2.savefig('out.png', dpi=300) 替换时,由于dpi 关键字导致定位混乱。这可以通过在初始化ax 时设置最终的dpi 来避免:fig1, ax = plt.subplots(dpi=300) 在我的 Python shell 中,这行似乎没有做任何事情: fig2.axes.append(ax) @ImportanceOfBeingErnest 是的;我从pickle 获得了我的身材。我很抱歉忽略了这个重要的细节。我最终将 9 AxesSubplot 设置为 set_visible(False) 并更改了我只想显示的位置。 @gerrit 也许你需要this answer? @irene 请注意,此解决方案仅将轴移动到新图形,它不设置任何变换。所以这样的问题是意料之中的。由于不鼓励在人物之间移动艺术家,因此如果您需要可靠的输出,最好不要使用它。【参考方案3】:

对于线图,您可以自己处理Line2D 对象:

fig1 = pylab.figure()
ax1 = fig1.add_subplot(111)
lines = ax1.plot(scipy.randn(10))

fig2 = pylab.figure()
ax2 = fig2.add_subplot(111)
ax2.add_line(lines[0])

【讨论】:

+1 好例子。似乎我无法将轴创建与图形创建分离,但我可以获取轴实例并将其传递给新图形。 请注意,这种方法不再有效。请参阅 Joe Kington 于 2012 年 12 月 7 日对上述答案的评论。 ax2.add_line(lines[0]) 结果为 RuntimeError: Can not put single artist in more than one figure(Python 3.7.0,matplotlib 2.2.2)。【参考方案4】:

TL;DR 部分基于 Joe 不错的答案。

选项 1:fig.add_subplot()

def fcn_return_plot():
    return plt.plot(np.random.random((10,)))
n = 4
fig = plt.figure(figsize=(n*3,2))
#fig, ax = plt.subplots(1, n,  sharey=True, figsize=(n*3,2)) # also works
for index in list(range(n)):
    fig.add_subplot(1, n, index + 1)
    fcn_return_plot()
    plt.title(f"plot: index", fontsize=20) 

选项 2:将 ax[index] 传递给返回 ax[index].plot() 的函数

def fcn_return_plot_input_ax(ax=None):
    if ax is None:
        ax = plt.gca()
    return ax.plot(np.random.random((10,)))
n = 4
fig, ax = plt.subplots(1, n,  sharey=True, figsize=(n*3,2))
for index in list(range(n)):
    fcn_return_plot_input_ax(ax[index])
    ax[index].set_title(f"plot: index", fontsize=20)

输出尊重。

注意:选项 1 plt.title() 在选项 2 中更改为 ax[index].set_title()。查找更多Matplotlib Gotchas in Van der Plas book。

【讨论】:

【参考方案5】:

要深入兔子洞。扩展我之前的答案,可以返回一个完整的ax,而不仅仅是ax.plot()。例如

如果数据框有 20 种类型的 100 个测试(此处为 id):

dfA = pd.DataFrame(np.random.random((100,3)), columns = ['y1', 'y2', 'y3'])
dfB = pd.DataFrame(np.repeat(list(range(20)),5), columns = ['id'])
dfC = dfA.join(dfB)

还有绘图功能(这是整个答案的关键):

def plot_feature_each_id(df, feature, id_range=[], ax=None, legend_bool=False):
    feature = df[feature]
    if not len(id_range): id_range=set(df['id'])
    legend_arr = []
    for k in id_range:
        pass
        mask = (df['id'] == k)
        ax.plot(feature[mask])
        legend_arr.append(f"id: k")
    if legend_bool: ax.legend(legend_arr)
    return ax

我们可以实现:

feature_arr = dfC.drop('id',1).columns
id_range= np.random.randint(len(set(dfC.id)), size=(10,))
n = len(feature_arr)
fig, ax = plt.subplots(1, n,  figsize=(n*6,4));
for i,k in enumerate(feature_arr):
    plot_feature_each_id(dfC, k, np.sort(id_range), ax[i], legend_bool=(i+1==n))
    ax[i].set_title(k, fontsize=20)
    ax[i].set_xlabel("test nr. (id)", fontsize=20)

【讨论】:

以上是关于matplotlib:我可以创建 AxesSubplot 对象,然后将它们添加到 Figure 实例吗?的主要内容,如果未能解决你的问题,请参考以下文章

Matplotlib 和 Numpy - 创建日历热图

使用 Matplotlib 创建 CSV 数据的实时图

matplotlib 可以将元数据添加到保存的图形中吗?

如何分享 matplotlib 风格?

如何创建可以使 matplotlib 3d 中的球体看起来像一半亮的颜色图图(看起来像地球的一侧被照亮)

Python - matplotlib:如何更改 RectangleSelector 属性