102 – Waffle charts by category
In this tutorial I will show you how to create Waffle charts using Python and Matplotlib. For more matplotlib charts, check out the gallery:

This is what we will create in matplotlib:

Import the packages
We will need the following packages:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import decimal
Generate the data
We could actually go from numpy to matplotlib, but most data projects use pandas to transform the data, so I am using a pandas dataframe as the starting point.
color_dict = {"Norway": "#2B314D", "Denmark": "#A54836", "Sweden": "#5375D4", }
xy_ticklabel_color, xy_label_color, dot_color ='#101628',"#757C85", "#E2E2E2"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : ["Sweden", "Sweden", "Denmark", "Denmark", "Norway", "Norway"],
"sites": [13,15,4,10,5,8]
}
df= pd.DataFrame(data)
#custom sort
sort_order_dict = {"Denmark":2, "Sweden":3, "Norway":1, 2004:5, 2022:4}
df = df.sort_values(by=['year','countries',], key=lambda x: x.map(sort_order_dict))
#map the colors of a dict to a dataframe
df['color']= df.countries.map(color_dict)
df['sub_total'] = df.groupby('year')['sites'].transform('sum')
df['pct_group'] = 100* df['sites'] / df.sub_total
df['pct_group'] = df['pct_group'].astype(float).round(1)
# use decimal library to round up .5 values to add to 100
df['pct_group'] = df['pct_group'].apply(
lambda x: decimal.Decimal(x).to_integral_value(rounding=decimal.ROUND_HALF_UP)
)
df
year | countries | sites | color | sub_total | pct_group | |
---|---|---|---|---|---|---|
5 | 2022 | Norway | 8 | #2B314D | 33 | 24 |
3 | 2022 | Denmark | 10 | #A54836 | 33 | 30 |
1 | 2022 | Sweden | 15 | #5375D4 | 33 | 46 |
4 | 2004 | Norway | 5 | #2B314D | 22 | 23 |
2 | 2004 | Denmark | 4 | #A54836 | 22 | 18 |
0 | 2004 | Sweden | 13 | #5375D4 | 22 | 59 |
We need to create the basis for the waffle:
#create a 10 by 10 matrix by creating x and y coordinates
X= np.repeat(np.arange(1,11),10)
Y = np.tile(np.arange(1,11),10)
#create the colors
dot_colors = []
for color, pct in zip(df.color, df['pct_group'].astype(int)):
dot_colors.append([color] * pct + [dot_color]* (100-pct))
Add the variables
years = df.year.unique()
countries = df.countries
unique_countries = df.countries.unique()
colors = df.color.unique()
Plot the chart
fig, axes = plt.subplots(nrows= len(years),ncols=len(unique_countries),figsize=(8,6), facecolor = "#FFFFFF")
fig.subplots_adjust(hspace=.5)
for ax, pct, country, dot_color, color in zip(axes.ravel(), df['pct_group'], countries, dot_colors, df.color):
ax.scatter(Y, X, s= 30, marker="o", c= dot_color )
ax.set_title(country, color= xy_label_color,size=10, y = 1.2)
ax.set_xlabel(f'{pct}%', color= color, weight = "bold", size=18,)
ax.xaxis.set_label_position('top')
ax.tick_params(axis='both', which='both',length=0)
ax.set_xticks([])
ax.set_yticks([])
ax.spines[['top', 'left', 'right','bottom']].set_visible(False)
The result:

Was this helpful?
Reader Interactions