How to edit the legend in a multiindex stacked bar plot and bar colours?

Viewed 65

I'm trying to make a graph that would look roughly like this.

enter image description here

Here's some sample data:

data = {
    "date": ["2020-07-01", "2020-07-01", "2020-07-01", "2020-08-01", "2020-08-01", "2020-08-01", "2020-09-01",
             "2020-09-01", "2020-09-01", "2020-10-01", "2020-10-01", "2020-10-01", "2020-11-01", "2020-11-01",
             "2020-11-01", "2020-12-01", "2020-12-01", "2020-12-01", "2021-01-01", "2021-01-01", "2021-01-01",
             "2021-02-01", "2021-02-01", "2021-02-01", "2021-03-01", "2021-03-01", "2021-03-01", "2021-04-01",
             "2021-04-01", "2021-04-01", "2021-05-01", "2021-05-01", "2021-05-01", "2021-06-01", "2021-06-01",
             "2021-06-01"],
    "col2": ["A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C",
             "A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C", "A", "B", "C"],
    "col3": [24, 45, 0.0, 18, 47, 0.0, 31, 50, 0.0, 21, 40, 565, 34, 64, 220, 724, 3598, 1493,
             286, 127, 185, 37, 94, 233, 22, 18, 170, 111, 45, 186, 17, 106, 114, 12, 50, 122]
}

I use this code to make the multiindex dataframe, I also need a single datetime index for the x axis formatting.

df = pd.DataFrame(data)
df["date"] = pd.to_datetime(df["date"])
df = df.groupby(["date", "col2"]).sum()
df_single_index = df.groupby("date").sum()

This is the code I have to make most of the graph:

fig, ax = plt.subplots(figsize=(11.96, 4.42))
df.unstack().plot(kind='bar', ax=ax, stacked=True, zorder=3)

ax.set_xticks(range(df_single_index.index.size))
ax.set_xticklabels([date.strftime('%b\n%Y') if date.year != df_single_index.index[idx - 1].year
                    else date.strftime('%b') for idx, date in enumerate(df_single_index.index)])
ax.figure.autofmt_xdate(rotation=0, ha='center')
ax.tick_params(axis='x', which="major", bottom=False)

x_locator = FixedLocator(np.arange(-0.5, (len(df_single_index) + 0.5), 1))
ax.tick_params(axis='x', which="minor", direction="out", length=15, width=1)
ax.xaxis.set_minor_locator(x_locator)

plt.grid(which="major", axis="y", zorder=0)

enter image description here

Most works fine, except the legend doesn't look good.

Adding this code fixed the legend and changed the colours of the legend:

colors = {"A": (1, 0.75, 0), "B": (0.498, 0.498, 0.498), "C": (0, 0.7, 0.76)}
labels = list(colors.keys())
handles = [plt.Rectangle((0, 0), 1, 1, color=colors[label]) for label in labels]
plt.legend(handles, labels)

enter image description here

But now the bars don't match the legend and adding the colour dictionary as a parameter in the graph only throws a KeyError. I can't figure out how else to go about it. Any advice will be appreciated.

1 Answers

IIUC:

You can try via droplevel() and rename_axis():

fig, ax = plt.subplots(figsize=(11.96, 4.42))
df.unstack().droplevel(0,1).rename_axis(columns=None).plot(kind='bar', ax=ax, stacked=True, zorder=3)
ax.set_xticks(range(df_single_index.index.size))
ax.set_xticklabels([date.strftime('%b\n%Y') if date.year != df_single_index.index[idx - 1].year
                    else date.strftime('%b') for idx, date in enumerate(df_single_index.index)])
ax.figure.autofmt_xdate(rotation=0, ha='center')
ax.tick_params(axis='x', which="major", bottom=False)

x_locator = FixedLocator(np.arange(-0.5, (len(df_single_index) + 0.5), 1))
ax.tick_params(axis='x', which="minor", direction="out", length=15, width=1)
ax.xaxis.set_minor_locator(x_locator)

plt.grid(which="major", axis="y", zorder=0)

enter image description here

Related