Search code examples
loopsmatplotlibchartssubplotfigure

Plotting many pie charts using a loop to create a single figure using matplotlib


I'm having trouble converting a script I wrote to create and save 15 pie charts separately which I would like to save as a single figure with 15 subplots instead. I have tried taking fig, ax = plt.subplots(5, 3, figsize=(7, 7)) out of the loop and specifying the number of rows and columns for the plot but I get this error AttributeError: 'numpy.ndarray' object has no attribute 'pie'. This error doesn't occur if I leave that bit of code in the script as is seen below. Any help with tweaking the code below to create a single figure with 15 subplots (one for each site) would be enormously appreciated.

import pandas as pd
import matplotlib.pyplot as plt


df = pd.read_excel(path)
df_1 = df.groupby(['Site', 'group'])['Abundance'].sum().reset_index(name='site_count')

site = ['Ireland', 'England', 'France', 'Scotland', 'Italy', 'Spain',
'Croatia', 'Sweden', 'Denmark', 'Germany', 'Belgium', 'Austria', 'Poland', 'Stearman', 'Hungary']


for i in site:
    df_1b = df_1.loc[df_1['Site'] == i]
    colors = {'Dog': 'orange', 'Cat': 'cyan', 'Pig': 'darkred', 'Horse': 'lightcoral', 'Bird': 
    'grey', 'Rat': 'lightsteelblue', 'Whale': 'teal', 'Fish': 'plum', 'Shark': 'darkgreen'}

    wp = {'linewidth': 1, 'edgecolor': "black"}
    fig, ax = plt.subplots(figsize=(7, 7))
    texts, autotexts = ax.pie(df_1b['site_count'],
                                      labels=None,
                                      shadow=False,
                                      colors=[colors[key] for key in labels],
                                      startangle=90,
                                      wedgeprops=wp,
                                      textprops=dict(color="black"))

    plt.setp(autotexts, size=16)
    ax.set_title(site, size=16, weight="bold", y=0)
    plt.savefig('%s_group_diversity.png' % i, bbox_inches='tight', pad_inches=0.05, dpi=600)

Solution

  • It's hard to guess how exactly you'd like the plot to look like.

    The main changes the code below makes, are:

    • adding fig, axs = plt.subplots(nrows=5, ncols=3, figsize=(12, 18)). Here axs is a 2d array of subplots. figsize should be large enough to fit the 15 subplots.
    • df_1b['group'] is used for the labels that decide the color (it's not clear where the labels themselves should be shown, maybe in a common legend)
    • autopct='%.1f%%' is added to ax.pie(...). This shows the percentages with one decimal.
    • With autopct, ax.pie(...) now returns 3 lists: texts, autotexts, wedges. The texts are the text objects for the labels (currently empty texts), autotexts are the percentages (that are calculated "automatically"), wedges are the triangular wedges.
    • ax.set_title now uses the site name, and puts it at a negative y-value (y=0 would overlap with the pie)
    • plt.tight_layout() at the end tries to optimize the surrounding white space
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
    site = ['Ireland', 'England', 'France', 'Scotland', 'Italy', 'Spain',
            'Croatia', 'Sweden', 'Denmark', 'Germany', 'Belgium', 'Austria', 'Poland', 'Stearman', 'Hungary']
    colors = {'Dog': 'orange', 'Cat': 'cyan', 'Pig': 'darkred', 'Horse': 'lightcoral', 'Bird': 'grey',
              'Rat': 'lightsteelblue', 'Whale': 'teal', 'Fish': 'plum', 'Shark': 'darkgreen'}
    wedge_properties = {'linewidth': 1, 'edgecolor': "black"}
    
    # create some dummy test data
    df = pd.DataFrame({'Site': np.random.choice(site, 1000),
                       'group': np.random.choice(list(colors.keys()), 1000),
                       'Abundance': np.random.randint(1, 11, 1000)})
    df_1 = df.groupby(['Site', 'group'])['Abundance'].sum().reset_index(name='site_count')
    
    fig, axs = plt.subplots(nrows=5, ncols=3, figsize=(12, 18))
    for site_i, ax in zip(site, axs.flat):
        df_1b = df_1[df_1['Site'] == site_i]
        labels = df_1b['group']
        texts, autotexts, wedges = ax.pie(df_1b['site_count'],
                                          labels=None,
                                          shadow=False,
                                          colors=[colors[key] for key in labels],
                                          startangle=90,
                                          wedgeprops=wedge_properties,
                                          textprops=dict(color="black"),
                                          autopct='%.1f%%')
        plt.setp(autotexts, size=10)
        ax.set_title(site_i, size=16, weight="bold", y=-0.05)
    
    plt.tight_layout()
    plt.savefig('group_diversity.png', bbox_inches='tight', pad_inches=0.05, dpi=600)
    plt.show()
    

    grid of pie charts