[image](https://i.stack.imgur.com/abcd1.png)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # just included to set plot style
sns.set_theme(style="darkgrid")
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df = pd.DataFrame(
dict(
year=[2023]*7 + [2024]*7 + [2025]*7,
day_of_week=days_of_week * 3,
avg_cardio_minutes=[20, 15, 10, 25, 30, 5, 0,
25, 20, 15, 30, 35, 10, 5,
30, 25, 20, 35, 40, 15, 10],
avg_lifting_minutes=[40, 35, 30, 45, 50, 25, 20,
45, 40, 35, 50, 55, 30, 25,
50, 45, 40, 55, 60, 35, 30]
)
)
def plot_stacked_grouped_bar_chart(df, x_major_column, x_minor_column, stack_columns,
stack_labels=None, y_label='', title='', legend_title=''):
"""
Plots a stacked grouped bar chart with the specified data and labels.
Parameters:
-----------
df : pd.DataFrame
DataFrame containing the data to plot.
x_major_column : str
Column name for the major x-axis grouping.
x_minor_column : str
Column name for the minor x-axis grouping.
stack_columns : list of str
List of column names to stack.
stack_labels : list of str, optional
List of labels for the stacked columns. If None, uses stack_columns.
y_label : str, optional
Label for the y-axis.
title : str, optional
Title of the plot.
legend_title : str, optional
Title of the legend.
"""
if stack_labels is None:
stack_labels = stack_columns
fig, ax = plt.subplots(figsize=(10, 5))
x_major_values = df[x_major_column].unique().tolist()
offset = 0
xtick_labels = []
# iterate over major x-axis groups
for i, major_value in enumerate(x_major_values):
df_major = df[df[x_major_column] == major_value]
x_minor_values = df_major[x_minor_column].unique().tolist()
x_positions = offset + np.arange(len(x_minor_values)) # the label locations
bottoms = np.zeros(len(x_minor_values))
# iterate over stack columns
for j, stack_column in enumerate(stack_columns):
heights = df_major[stack_column].values
ax.bar(x=x_positions, height=heights, color=f'C{j}', width=1, label=stack_labels[j], bottom=bottoms)
bottoms += heights
# add major x-axis label
ax.text(offset + len(x_minor_values) / 2 - 0.5, -0.2, major_value, ha='center', va='top', transform=ax.get_xaxis_transform())
# update offset and xtick labels
offset += len(x_minor_values)
xtick_labels.extend([x_minor_values])
if i < len(x_major_values) - 1:
offset += 1 # space between major groups
xtick_labels.append('') # empty label for space
ax.set_xticks(range(offset))
ax.set_xticklabels(days_of_week + [""] + days_of_week + [""] + days_of_week, rotation=45)
ax.set_ylabel(y_label)
ax.set_title(title)
# reorder legend to match stack order
handles, labels = ax.get_legend_handles_labels()
handles = handles[::-1][:len(stack_labels)]
labels = labels[::-1][:len(stack_labels)]
ax.legend(title=legend_title, handles=handles, labels=labels)
plt.show()
plot_stacked_grouped_bar_chart(df=df,
x_major_column = 'year',
x_minor_column = 'day_of_week',
stack_columns = ['avg_lifting_minutes', 'avg_cardio_minutes'],
stack_labels = ['Lifting', 'Cardio'],
y_label = 'minutes',
title = 'Average Time Working out per Day of Week',
legend_title = 'Workout Type')