如何按Hue/图例组为分组条形图添加组内百分比标注及问题修正
Fixing Per-Hue Group Percentage Annotations in Seaborn Grouped Bar Plot
I see the issue with your red bars (with_client_nmbr=False) not showing correct percentages—your current code isn't properly linking each bar to its respective hue group's total. Let's break down the problem and fix it:
What Went Wrong
In your original code, you loop through ax.containers to set group_total, but you don't actually pair each container's total with its bars. The second loop over all patches ends up using the last assigned group_total (which is df_t, the total for with_client_nmbr=True) for every bar—so even the red bars are using the blue group's total to calculate percentages.
The Fix
We need to:
- Calculate the total count for each hue group directly from your aggregated dataframe (cleaner than separate queries).
- Iterate over each container (each container represents one hue group), get that group's total, then annotate every bar in that container with the correct percentage relative to its own group's total.
Full Corrected Code
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # Sample data np.random.seed(365) rows = 100000 data = {'Call_ID': np.random.normal(10000, 8000, size=rows).astype(int), 'with_client_nmbr': np.random.choice([False, True], size=rows, p=[.17, .83]), 'Type_of_Caller': np.random.choice(['Agency', 'EE', 'ER'], size=rows, p=[.06, .77, .17])} all_call = pd.DataFrame(data) # Aggregate data df_agg = all_call.groupby(['Type_of_Caller','with_client_nmbr'])['Call_ID'].nunique().reset_index() # Create grouped bar plot ax = sns.barplot(x='Type_of_Caller', y='Call_ID', hue='with_client_nmbr', data=df_agg, palette=['orangered', 'skyblue']) # Calculate total count for each hue group (False and True) group_totals = df_agg.groupby('with_client_nmbr')['Call_ID'].sum().to_dict() # Annotate each bar with percentage relative to its hue group's total for container in ax.containers: # Get the hue value from the container's label (convert back to boolean if needed) hue_label = container.get_label() hue_val = eval(hue_label) if isinstance(hue_label, str) else hue_label current_total = group_totals[hue_val] for p in container: height = p.get_height() x_pos = p.get_x() + p.get_width() / 2 y_pos = p.get_y() + height * 1.02 # Slightly above the bar ax.annotate(f'{(height / current_total):.1%}', (x_pos, y_pos), ha='center', fontsize=9) plt.show()
Key Improvements
- Cleaner Total Calculation: Using
df_agg.groupby('with_client_nmbr')['Call_ID'].sum()directly computes each hue group's total without redundant queries. - Container-Patched Pairing: By iterating over each container (hue group) first, we ensure every bar in that container uses the correct group total for percentage calculation.
- Label Handling: Converting the container's string label back to a boolean matches the original data type in
group_totals.
内容的提问来源于stack exchange,提问作者UnicornKKYY
相关产品推荐
相关产品推荐

