Home > Mobile >  Stacked pie chart - how to set inner piechart labels dependent on outer piechart
Stacked pie chart - how to set inner piechart labels dependent on outer piechart

Time:11-12

I have a stacked pie chart with labels in percentage. The outer pie chart has labels as I want them to be; however, the labels for the inner chart should be based on where it falls under the outer pie chart. For example, in the image below, the percentage labels of the inner pie chart should sum up to 100% for each color of the outer pie chart. Currently, for the red outer pie chart, the inner pie chart has 11.0%, 12.0%, 7.0% which is the percentage of itself that is not taking the outer chart into account. I'd want them to display something like 35%, 38%, 27% and same for green and yellow colors of the outer pie chart where the inner chart labels are dependent upon the outer pie chart color.

enter image description here

Here's a reproducible example

import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt

options = ['RED','YELLOW','GREEN']
color_list1 = []
color_list2 = []

for i in np.arange(100):
    color1 = random.choice(options)
    color2 = random.choice(options)
    color_list1.append(color1)
    color_list2.append(color2)

test_df = pd.DataFrame(list(zip(color_list1, color_list2)), columns =['Outer_Color', 'Inner_Color'])

outer = pd.DataFrame(test_df.groupby('Outer_Color').size())
inner = pd.DataFrame(test_df.groupby(['Outer_Color', 'Inner_Color']).size())
inner_labels = inner.index.get_level_values(1)


my_pal = {"RED": "r", "YELLOW": "yellow", "GREEN":"g"}

fig, ax = plt.subplots(figsize=(14,7))
size = 0.3

ax.pie(outer.values.flatten(), radius=1,
#        labels=outer.index,
       autopct='%1.1f%%', pctdistance=0.85, # labeldistance=0.2,
       wedgeprops=dict(width=size, edgecolor='w'),
       colors=[my_pal[key] for key in outer.index])

ax.pie(inner.values.flatten(), radius=1-size, 
#        labels = inner_labels,
       autopct='%1.1f%%', pctdistance=0.7, # labeldistance=1.2,
       wedgeprops=dict(width=size, edgecolor='w'),
       colors=[my_pal[key] for key in inner_labels])

plt.show()

CodePudding user response:

An approach you can take is to calculate the percentage of the inner wedge relative to the outer wedge and make new labels to then pass when you do ax.pie. For example:

# Construct inner labels
    inner_str_label = []  # new labels storage
    n_inside = 3  # number of inner wedges per outer wedge
    for idx, outer_value in enumerate(outer.values.flatten()):
        # Get inner values for each outer wedge
        for inner_value in inner.values.flatten()[idx*n_inside:idx*n_inside n_inside]:
            # format for only 1 decimal place
            new_label = f"{'{0:.1f}'.format(inner_value/outer_value*100)}%"
            inner_str_label.append(new_label)

Now we can pass the new labels in ax.pie using labels:

import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt


if __name__ == '__main__':

    options = ['RED', 'YELLOW', 'GREEN']
    color_list1 = []
    color_list2 = []

    for i in np.arange(100):
        color1 = random.choice(options)
        color2 = random.choice(options)
        color_list1.append(color1)
        color_list2.append(color2)

    test_df = pd.DataFrame(list(zip(color_list1, color_list2)), columns =['Outer_Color', 'Inner_Color'])

    outer = pd.DataFrame(test_df.groupby('Outer_Color').size())
    inner = pd.DataFrame(test_df.groupby(['Outer_Color', 'Inner_Color']).size())

    my_pal = {"RED": "r", "YELLOW": "yellow", "GREEN": "g"}

    fig, ax = plt.subplots(figsize=(14, 7))
    size = 0.3

    ax.pie(outer.values.flatten(), radius=1,
           #        labels=outer.index,
           autopct='%1.1f%%', pctdistance=0.85,  # labeldistance=0.2,
           wedgeprops=dict(width=size, edgecolor='w'),
           colors=[my_pal[key] for key in outer.index])

    # Construct inner labels
    inner_str_label = []  # new labels storage
    n_inside = 3  # number of inner wedges per outer wedge
    for idx, outer_value in enumerate(outer.values.flatten()):
        # Get inner values for each outer wedge
        for inner_value in inner.values.flatten()[idx*n_inside:idx*n_inside n_inside]:
            # format for only 1 decimal place
            new_label = f"{'{0:.1f}'.format(inner_value/outer_value*100)}%"
            inner_str_label.append(new_label)

    wedges, labels = ax.pie(inner.values.flatten(), radius=1-size, labels=inner_str_label, labeldistance=0.8,
                            wedgeprops=dict(width=size, edgecolor='w'),
                            colors=[my_pal[key] for key in inner.index.get_level_values(1)])

    # Fix inner label position
    for string_label in labels:
        string_label.update({"rotation": 0, "horizontalalignment": "center", "verticalalignment": "center"})

    plt.show()

fig_example

Note that this is assuming each outer wedge has the same amount of inner wedges. If the number of inner wedges was irregular, the approach would be similar but you set the number of inner wedges per outer wedges in a list:

    # Construct inner labels
    inner_str_label = []  # new labels storage
    n_inside_list = [2, 3, 2]  # number of inner wedges per outer wedge
    for idx, outer_value in enumerate(outer.values.flatten()):
        # Get inner values for each outer wedge
        n_inside = n_inside_list[idx]
        for inner_value in inner.values.flatten()[idx*n_inside:idx*n_inside n_inside]:
            # format for only 1 decimal place
            new_label = f"{'{0:.1f}'.format(inner_value/outer_value*100)}%"
            inner_str_label.append(new_label)

Hope this helps - cheers!

  • Related