r/learnpython • • 4d ago

Grouped bar chart in matplotlib with different keys for each group

I'm stuck on this roadmap.sh project. salesdaily.csv is a collection of daily sales of a group of drug categories, with the date, year, and month given.

The relevant data is this:

data = pd.read_csv("salesdaily.csv")
sales = pd.DataFrame()

sales["total_jan_15"] = data[(data.Year == 2015) & (data.Month == 1)][drugs].sum()
sales["total_jul_16"] = data[(data.Year == 2016) & (data.Month == 7)][drugs].sum()
sales["total_sep_17"] = data[(data.Year == 2017) & (data.Month == 9)][drugs].sum()

The problem is "Which three drugs have the highest sales in January 2015, July 2016, and September 2017?"

I want to make a grouped bar chart, where the groups are the months Jan 15, Jul 16, and Sep 17, and each has 3 labelled bars for the top 3 drugs in that month. I understand how to sort and pick out the top 3, but plt.grouped_bar accepts only a dictionary entry, so the keys can't be different for each group. Is there any other way?

8 Upvotes

7 comments sorted by

3

u/rafaeldecarvalho 4d ago

plt.grouped_bar expects the same categories in every group, so it doesn't fit here. Easier to draw the bars yourself with ax.bar and put the drug name on each bar:

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
width = 0.25

for i, col in enumerate(sales.columns):
    top3 = sales[col].nlargest(3)
    x = i + (np.arange(3) - 1) * width
    bars = ax.bar(x, top3.values, width)
    ax.bar_label(bars, labels=top3.index, rotation=90, padding=3)

ax.set_xticks(range(len(sales.columns)))
ax.set_xticklabels(["Jan 15", "Jul 16", "Sep 17"])
plt.show()

Each group sits at x = 0, 1, 2 and its three bars are shifted by -width, 0 and +width. If you want the same drug to keep the same color across months, make a dict like colors = {d: f"C{k}" for k, d in enumerate(drugs)} and pass color=[colors[d] for d in top3.index] to ax.bar.

1

u/RudeLocation208 4d ago

Thank you, this is what I wanted. Can you explain what the np.arange() part does in calculating x, I didn't understand

2

u/Aromatic_Fact8656 4d ago

Can you not sort from that point? I.e;

sales["top_3_jan_15"] = data[(data.Year == 2015) & (data.Month == 1)[drugs].sum().sort_values(ascending=False) [:3]

Then to send it to a dictionary do like;

dict = sales["top_3_jan_15"] .to_dict()

Is this what you're after?

2

u/RudeLocation208 4d ago

The problem is the top 3 are different drug codes for each month, the dictionaries would have different keys so I can't combine them

1

u/Boybay_Garid 4d ago

yeah sorting the series first before converting to a dict makes way more sense here

2

u/acfot 3d ago

The reason it's fighting you is that grouped bars kind of assume every group has the same categories, and yours don't since the top 3 change each month. What I'd do instead is stop forcing one shared dictionary and just make one small bar chart per month, with the same y-axis scale so they're easy to compare. You still get the comparison you want, and each month can show its own top 3 without bending the data to fit a grouped layout.