在不手动计算子图数量的情况下创建 matplotlib 子图?
Create matplotlib subplots without manually counting number of subplots?
在 Jupyter Notebook 中进行临时分析时,我经常想将某些 Pandas DataFrame
的转换序列视为垂直堆叠的子图。我常用的快捷方法是根本不使用子图,而是为每个图创建一个新图形:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.DataFrame({"a": range(100)}) # Some arbitrary DataFrame
df.plot(title="0 to 100")
plt.show()
df = df * -1 # Some transformation
df.plot(title="0 to -100")
plt.show()
df = df * 2 # Some other transformation
df.plot(title="0 to -200")
plt.show()
此方法有局限性。即使索引相同(因为 x 轴宽度取决于 y 轴标签),x 轴刻度线也未对齐,并且 Jupyter 单元格输出包含多个单独的内联图像,而不是我可以保存或复制粘贴的单个图像.
据我所知,正确的解决方案是使用 plt.subplots()
:
fig, axes = plt.subplots(3, figsize=(20, 9))
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=axes[0], title="0 to 100")
df = df * -1 # Some transformation
df.plot(ax=axes[1], title="0 to -100")
df = df * 2 # Some other transformation
df.plot(ax=axes[2], title="0 to -200")
plt.tight_layout()
plt.show()
这会产生我想要的输出。然而,它也带来了一个麻烦,让我默认使用第一种方法:我必须手动 count 我创建的子图的数量,并在几个不同的地方更新这个计数作为代码更改。
在多图的情况下,添加第四个图就像第四次调用 df.plot()
和 plt.show()
一样简单。对于子图,等效的更改需要更新子图计数,加上调整输出图形大小的算法,将 plt.subplots(3, figsize=(20, 9))
替换为 plt.subplots(4, figsize=(20, 12))
。每个新添加的子图都需要知道有多少其他子图已经存在(ax=axes[0]
、ax=axes[1]
、ax=axes[2]
等),因此任何添加或删除都需要对下面的图进行级联更改。
这 似乎 应该很容易实现自动化 — 它只是计数和乘法 — 但我发现它不可能用 matplotlib/pyplot API.我能得到的最接近的是以下部分解决方案,它足够简洁但仍然需要显式计数:
n_subplots = 3 # Must still be updated manually as code changes
fig, axes = plt.subplots(n_subplots, figsize=(20, 3 * n_subplots))
i = 0 # Counts how many subplots have been added so far
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=axes[i], title="0 to 100")
i += 1
df = df * -1 # Arbitrary transformation
df.plot(ax=axes[i], title="0 to -100")
i += 1
df = df * 2 # Arbitrary transformation
df.plot(ax=axes[i], title="0 to -200")
i += 1
plt.tight_layout()
plt.show()
根本问题是任何时候调用 df.plot()
,都必须存在一个已知大小的 axes
列表。我考虑过以某种方式延迟 df.plot()
的执行,例如通过附加到 lambda 函数列表,这些函数可以在按顺序调用之前进行计数,但这似乎是一种极端的仪式,只是为了避免手动更新整数。
有没有更方便的方法呢?具体来说,有没有一种方法可以创建一个具有 "expandable" 个子图的图形,适用于 ad-hoc/interactive 事先不知道计数的上下文?
(注意: 这个问题可能看起来与 this question or this one 中的任何一个重复,但接受的答案 都问题恰好包含我要解决的问题——plt.subplots()
的 nrows=
参数必须在添加子图之前声明。)
IIUC 您需要一些容器来进行转换以实现此目的 - 例如 list
。类似于:
arbitrary_trx = [
lambda x: x, # No transformation
lambda x: x * -1, # Arbitrary transformation
lambda x: x * 2] # Arbitrary transformation
fig, axes = plt.subplots(nrows=len(arbitrary_trx))
for ax, f in zip(axes, arbitrary_trx):
df = df.apply(f)
df.plot(ax=ax)
您可以创建一个对象来存储数据,并且只在您告诉它这样做时才创建图形。
import pandas as pd
import matplotlib.pyplot as plt
class AxesStacker():
def __init__(self):
self.data = []
self.titles = []
def append(self, data, title=""):
self.data.append(data)
self.titles.append(title)
def create(self):
nrows = len(self.data)
self.fig, self.axs = plt.subplots(nrows=nrows)
for d, t, ax in zip(self.data, self.titles, self.axs.flat):
d.plot(ax=ax, title=t)
stacker = AxesStacker()
df = pd.DataFrame({"a": range(100)}) # Some arbitrary DataFrame
stacker.append(df, title="0 to 100")
df = df * -1 # Some transformation
stacker.append(df, title="0 to -100")
df = df * 2 # Some other transformation
stacker.append(df, title="0 to -200")
stacker.create()
plt.show()
首先创建一个空图形,然后使用 add_subplot
. Update the subplotspec
s of the existing subplots in the figure using a new GridSpec
为新几何添加子图(仅当您使用 constrained
布局而不是 [= 时才需要 figure
关键字17=]布局)。
import matplotlib.pyplot as plt
import matplotlib as mpl
import pandas as pd
def append_axes(fig, as_cols=False):
"""Append new Axes to Figure."""
n = len(fig.axes) + 1
nrows, ncols = (1, n) if as_cols else (n, 1)
gs = mpl.gridspec.GridSpec(nrows, ncols, figure=fig)
for i,ax in enumerate(fig.axes):
ax.set_subplotspec(mpl.gridspec.SubplotSpec(gs, i))
return fig.add_subplot(nrows, ncols, n)
fig = plt.figure(layout='tight')
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=append_axes(fig), title="0 to 100")
df = df * -1 # Some transformation
df.plot(ax=append_axes(fig), title="0 to -100")
df = df * 2 # Some other transformation
df.plot(ax=append_axes(fig), title="0 to -200")
将新子图添加为列的示例(并使用约束布局进行更改):
fig = plt.figure(layout='constrained')
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=append_axes(fig, True), title="0 to 100")
df = df + 10 # Some transformation
df.plot(ax=append_axes(fig, True), title="10 to 110")
在 Jupyter Notebook 中进行临时分析时,我经常想将某些 Pandas DataFrame
的转换序列视为垂直堆叠的子图。我常用的快捷方法是根本不使用子图,而是为每个图创建一个新图形:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.DataFrame({"a": range(100)}) # Some arbitrary DataFrame
df.plot(title="0 to 100")
plt.show()
df = df * -1 # Some transformation
df.plot(title="0 to -100")
plt.show()
df = df * 2 # Some other transformation
df.plot(title="0 to -200")
plt.show()
此方法有局限性。即使索引相同(因为 x 轴宽度取决于 y 轴标签),x 轴刻度线也未对齐,并且 Jupyter 单元格输出包含多个单独的内联图像,而不是我可以保存或复制粘贴的单个图像.
据我所知,正确的解决方案是使用 plt.subplots()
:
fig, axes = plt.subplots(3, figsize=(20, 9))
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=axes[0], title="0 to 100")
df = df * -1 # Some transformation
df.plot(ax=axes[1], title="0 to -100")
df = df * 2 # Some other transformation
df.plot(ax=axes[2], title="0 to -200")
plt.tight_layout()
plt.show()
这会产生我想要的输出。然而,它也带来了一个麻烦,让我默认使用第一种方法:我必须手动 count 我创建的子图的数量,并在几个不同的地方更新这个计数作为代码更改。
在多图的情况下,添加第四个图就像第四次调用 df.plot()
和 plt.show()
一样简单。对于子图,等效的更改需要更新子图计数,加上调整输出图形大小的算法,将 plt.subplots(3, figsize=(20, 9))
替换为 plt.subplots(4, figsize=(20, 12))
。每个新添加的子图都需要知道有多少其他子图已经存在(ax=axes[0]
、ax=axes[1]
、ax=axes[2]
等),因此任何添加或删除都需要对下面的图进行级联更改。
这 似乎 应该很容易实现自动化 — 它只是计数和乘法 — 但我发现它不可能用 matplotlib/pyplot API.我能得到的最接近的是以下部分解决方案,它足够简洁但仍然需要显式计数:
n_subplots = 3 # Must still be updated manually as code changes
fig, axes = plt.subplots(n_subplots, figsize=(20, 3 * n_subplots))
i = 0 # Counts how many subplots have been added so far
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=axes[i], title="0 to 100")
i += 1
df = df * -1 # Arbitrary transformation
df.plot(ax=axes[i], title="0 to -100")
i += 1
df = df * 2 # Arbitrary transformation
df.plot(ax=axes[i], title="0 to -200")
i += 1
plt.tight_layout()
plt.show()
根本问题是任何时候调用 df.plot()
,都必须存在一个已知大小的 axes
列表。我考虑过以某种方式延迟 df.plot()
的执行,例如通过附加到 lambda 函数列表,这些函数可以在按顺序调用之前进行计数,但这似乎是一种极端的仪式,只是为了避免手动更新整数。
有没有更方便的方法呢?具体来说,有没有一种方法可以创建一个具有 "expandable" 个子图的图形,适用于 ad-hoc/interactive 事先不知道计数的上下文?
(注意: 这个问题可能看起来与 this question or this one 中的任何一个重复,但接受的答案 都问题恰好包含我要解决的问题——plt.subplots()
的 nrows=
参数必须在添加子图之前声明。)
IIUC 您需要一些容器来进行转换以实现此目的 - 例如 list
。类似于:
arbitrary_trx = [
lambda x: x, # No transformation
lambda x: x * -1, # Arbitrary transformation
lambda x: x * 2] # Arbitrary transformation
fig, axes = plt.subplots(nrows=len(arbitrary_trx))
for ax, f in zip(axes, arbitrary_trx):
df = df.apply(f)
df.plot(ax=ax)
您可以创建一个对象来存储数据,并且只在您告诉它这样做时才创建图形。
import pandas as pd
import matplotlib.pyplot as plt
class AxesStacker():
def __init__(self):
self.data = []
self.titles = []
def append(self, data, title=""):
self.data.append(data)
self.titles.append(title)
def create(self):
nrows = len(self.data)
self.fig, self.axs = plt.subplots(nrows=nrows)
for d, t, ax in zip(self.data, self.titles, self.axs.flat):
d.plot(ax=ax, title=t)
stacker = AxesStacker()
df = pd.DataFrame({"a": range(100)}) # Some arbitrary DataFrame
stacker.append(df, title="0 to 100")
df = df * -1 # Some transformation
stacker.append(df, title="0 to -100")
df = df * 2 # Some other transformation
stacker.append(df, title="0 to -200")
stacker.create()
plt.show()
首先创建一个空图形,然后使用 add_subplot
. Update the subplotspec
s of the existing subplots in the figure using a new GridSpec
为新几何添加子图(仅当您使用 constrained
布局而不是 [= 时才需要 figure
关键字17=]布局)。
import matplotlib.pyplot as plt
import matplotlib as mpl
import pandas as pd
def append_axes(fig, as_cols=False):
"""Append new Axes to Figure."""
n = len(fig.axes) + 1
nrows, ncols = (1, n) if as_cols else (n, 1)
gs = mpl.gridspec.GridSpec(nrows, ncols, figure=fig)
for i,ax in enumerate(fig.axes):
ax.set_subplotspec(mpl.gridspec.SubplotSpec(gs, i))
return fig.add_subplot(nrows, ncols, n)
fig = plt.figure(layout='tight')
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=append_axes(fig), title="0 to 100")
df = df * -1 # Some transformation
df.plot(ax=append_axes(fig), title="0 to -100")
df = df * 2 # Some other transformation
df.plot(ax=append_axes(fig), title="0 to -200")
将新子图添加为列的示例(并使用约束布局进行更改):
fig = plt.figure(layout='constrained')
df = pd.DataFrame({"a": range(100)}) # Arbitrary DataFrame
df.plot(ax=append_axes(fig, True), title="0 to 100")
df = df + 10 # Some transformation
df.plot(ax=append_axes(fig, True), title="10 to 110")