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.
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()
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!