Veki
Veki

Reputation: 541

Creating subplots through a loop from a dataframe

Case:

  1. I receive a dataframe with (say 50) columns.
  2. I extract the necessary columns from that dataframe using a condition.
  3. So we have a list of selected columns of our dataframe now. (Say this variable is sel_cols)
  4. I need a bar chart for each of these columns value_counts().
  5. And I need to arrange all these bar charts in 3 columns, and varying number of rows based on number of columns selected in sel_cols.

So, if say 8 columns were selected, I want the figure to have 3 columns and 3 rows, with last subplot empty or just 8 subplots in 3x3 matrix if that is possible.

I could generate each chart separately using following code:

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

plt.show() inside the loop so that each chart is shown and not just the last one.

I also tried appending these charts to a list this way:

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

I could convert this list into an numpy array through reshape() but then it will have to be perfectly divisible into that shape. So 8 chart objects will not be reshaped into 3x3 array.

Then I tried creating the subplots first in this way:

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

This way I would get the subplots, but I get two problems:

I tried this:

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

But this only plots the last subplot with the last column. All other subplots stays blank.

How to do this with minimal lines of code, possibly without any need for human verification of the final subplots placements?

You may use this sample dataframe:

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],
          })

Selected columns are: sel_cols = ['A','B','D','E','G','H','J','K'] Total 8 columns.

Expected output is bar charts for value_counts() of each of these columns arranged in subplots in a figure with 3 columns. Rows to be decided based on number of columns selected, here 8 so 3 rows.

Upvotes: 1

Views: 4161

Answers (2)

tdy
tdy

Reputation: 41327

Given OP's sample data:

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)

We can plot the columns of data in several ways (in order of simplicity):

  1. DataFrame.plot with subplots param
  2. seaborn.catplot
  3. Loop through plt.subplots

1. DataFrame.plot with subplots param

Set subplots=True with the desired layout dimensions. Unused subplots will be auto-disabled:

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


2. seaborn.catplot

melt the data into long-form (i.e., 1 variable per column, 1 observation per row) and pass it to 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. Loop through plt.subplots

zip the columns and axes to iterate in pairs. Use the ax param to place each column onto its corresponding subplot.

If the grid size is larger than the number of columns (e.g., 3*3 > 8), disable the leftover axes with 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()

Upvotes: 2

Veki
Veki

Reputation: 541

Alternative to the answer by tdy, I tried to do it without seaborn using Matplotlib and a for loop.

Figured it might be better for some who want specific control over subplots with formatting and other parameters, then this is another way:

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 activates a subplot, while plt.gca() points to the active subplot.

Upvotes: 0

Related Questions