通过数据框的循环创建子图

Creating subplots through a loop from a dataframe

案例:

  1. 我收到一个包含(比如 50)列的数据框。
  2. 我使用条件从该数据框中提取了必要的列。
  3. 所以我们现在有一个数据框的选定列列表。 (假设这个变量是sel_cols)
  4. 我需要每个列的条形图 value_counts()。
  5. 我需要将所有这些条形图排列成 3 列,并根据 sel_cols 中选择的列数改变行数。

所以,如果说选择了 8 列,我希望图形有 3 列和 3 行,如果可能的话,最后一个子图为空或 3x3 矩阵中只有 8 个子图。

我可以使用以下代码分别生成每个图表:

for col in sel_cols:
    df[col].value_counts().plot(kind='bar)
    plt.show()

plt.show() 在循环内,以便显示每个图表,而不仅仅是最后一个。

我也试过以这种方式将这些图表附加到列表中:

charts = []
for col in sel_cols:
    charts.append(df[col].value_counts().plot(kind='bar))

我可以通过 reshape() 将这个列表转换成一个 numpy 数组,但是它必须可以完全整除成那个形状。所以 8 个图表对象不会被重新整形为 3x3 数组。

然后我尝试先用这种方式创建子图:

row = len(sel_cols)//3
fig, axes = plt.subplots(nrows=row,ncols=3)

这样我会得到子图,但我遇到了两个问题:

我试过这个:

for row in axes:
    for chart, col in zip(row,sel_cols):
        chart = data[col].value_counts().plot(kind='bar')

但这只绘制了最后一列的最后一个子图。所有其他子图保持空白。

如何用最少的代码行做到这一点,可能不需要对最终的子图放置进行人工验证?

您可以使用这个示例数据框:

pd.DataFrame({'A':['Y','N','N','Y','Y','N','N','Y','N'],
          'B':['E','E','E','E','F','F','F','F','E'],
          'C':[1,1,0,0,1,1,0,0,1],
          'D':['P','Q','R','S','P','Q','R','P','Q'],
          'E':['E','E','E','E','F','F','G','G','G'],
          'F':[1,1,0,0,1,1,0,0,1],
          'G':['N','N','N','N','Y','N','N','Y','N'],
          'H':['G','G','G','E','F','F','G','F','E'],
          'I':[1,1,0,0,1,1,0,0,1],
          'J':['Y','N','N','Y','Y','N','N','Y','N'],
          'K':['E','E','E','E','F','F','F','F','E'],
          'L':[1,1,0,0,1,1,0,0,1],
          })

选定的列是:sel_cols = ['A','B','D','E','G','H','J','K'] 共 8 列。

预期输出是 value_counts() 的条形图,这些列中的每一列都排列在具有 3 列的图中的子图中。根据选择的列数决定行数,这里是 8,所以 3 行。

给定 OP 的示例数据:

df = pd.DataFrame({'A':['Y','N','N','Y','Y','N','N','Y','N'],'B':['E','E','E','E','F','F','F','F','E'],'C':[1,1,0,0,1,1,0,0,1],'D':['P','Q','R','S','P','Q','R','P','Q'],'E':['E','E','E','E','F','F','G','G','G'],'F':[1,1,0,0,1,1,0,0,1],'G':['N','N','N','N','Y','N','N','Y','N'],'H':['G','G','G','E','F','F','G','F','E'],'I':[1,1,0,0,1,1,0,0,1],'J':['Y','N','N','Y','Y','N','N','Y','N'],'K':['E','E','E','E','F','F','F','F','E'],'L':[1,1,0,0,1,1,0,0,1]})
sel_cols = list('ABDEGHJK')
data = df[sel_cols].apply(pd.value_counts)

我们可以用几种方式绘制 data 的列(为了简单起见):

  1. DataFrame.plotsubplots 参数
  2. seaborn.catplot
  3. 遍历plt.subplots

1。 DataFrame.plotsubplots 参数

设置 subplots=True 所需的 layout 尺寸。未使用的子图将被自动禁用:

data.plot.bar(subplots=True, layout=(3, 3), figsize=(8, 6),
              sharex=False, sharey=True, legend=False)
plt.tight_layout()


2。 seaborn.catplot

melt 将数据转换为 长格式 (即每列 1 个变量,每行 1 个观察值)和传递给 seaborn.catplot:

import seaborn as sns

melted = data.melt(var_name='var', value_name='count', ignore_index=False).reset_index()
sns.catplot(data=melted, kind='bar', x='index', y='count',
            col='var', col_wrap=3, sharex=False)


3。循环 plt.subplots

zip 成对迭代的列和轴。使用 ax 参数将每一列放置到其相应的子图中。

如果网格大小大于列数(例如,3*3 > 8),请使用 set_axis_off:

禁用剩余轴
fig, axes = plt.subplots(3, 3, figsize=(8, 8), constrained_layout=True, sharey=True)

# plot each col onto one ax
for col, ax in zip(data.columns, axes.flat):
    data[col].plot.bar(ax=ax, rot=0)
    ax.set_title(col)
    
# disable leftover axes
for ax in axes.flat[data.columns.size:]:
    ax.set_axis_off()

替代 的答案,我尝试在没有 seaborn 的情况下使用 Matplotlib 和 for 循环来做到这一点。

认为对于一些想要通过格式和其他参数来特定控制子图的人来说可能更好,那么这是另一种方式:

fig = plt.figure(1,figsize=(16,12))
for i, col in enumerate(sel_cols,1):
    fig.add_subplot(3,4,i,)
    data[col].value_counts().plot(kind='bar',ax=plt.gca())
    plt.title(col)
plt.tight_layout()
plt.show(1)

plt.subplot 激活一个子图,而 plt.gca() 指向活动的子图。