Generate the data and import packages¶
First, we need to create the data. I'll start by defining it as a dictionary and then convert it into a pandas DataFrame, since pandas is commonly used in many projects for data manipulation.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
color_dict = {
"Norway": "#2B314D",
"Denmark": "#A54836",
"Sweden": "#5375D4",
}
xy_label_color, datalabels_color = "#101628", "#757C85"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries": ["Denmark", "Denmark", "Norway", "Norway", "Sweden", "Sweden"],
"sites": [4, 10, 5, 8, 13, 15],
}
df = pd.DataFrame(data)
df["ctry_code"] = df.countries.astype(str).str[:2].astype(str).str.upper()
df["year_lbl"] = "'" + df["year"].astype(str).str[-2:].astype(str)
#custom sort
sort_order_dict = {"Denmark": 1, "Sweden": 2, "Norway": 3, 2004: 4, 2022: 5}
df = df.sort_values(
by=["countries", "year"],
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
| year | countries | sites | ctry_code | year_lbl | color | |
|---|---|---|---|---|---|---|
| 0 | 2004 | Denmark | 4 | DE | '04 | #A54836 |
| 1 | 2022 | Denmark | 10 | DE | '22 | #A54836 |
| 4 | 2004 | Sweden | 13 | SW | '04 | #5375D4 |
| 5 | 2022 | Sweden | 15 | SW | '22 | #5375D4 |
| 2 | 2004 | Norway | 5 | NO | '04 | #2B314D |
| 3 | 2022 | Norway | 8 | NO | '22 | #2B314D |
Plot the chart¶
To plot this chart, we are going to use the thetagrids of a polar plot.
fig, axes = plt.subplots(nrows=df.countries.nunique(), ncols=df.year.nunique(), figsize=(6, 6), subplot_kw=dict(polar=True))
fig.tight_layout(pad=3.0)
for (_, row), ax in zip(df.iterrows(), axes.ravel()):
angles = np.arange(0, 2 * np.pi, 2 * np.pi / row.sites) #define the spines per chart by dividing a circle by the site number
ax.set_thetagrids(np.degrees(angles))
ax.xaxis.grid(
linewidth = 2,
color = row.color) #style the angular grid
ax.set_xlabel( #add the site data labels
row.sites,
color = datalabels_color,
size = 12, )
#style the plots
ax.set_theta_offset(np.pi / row.sites) #rotate the stating angle
ax.spines["polar"].set_visible(False) #hide the frame (in polar chart)
ax.tick_params( labelbottom = False, labelleft = False)
ax.yaxis.grid(False) #hide the radial grid
Add the labels only at the top and to the left¶
This is a very neat trick:
You can of course loop over each axes, it is what we do with axes.ravel(), but the trick is that you can be specific on which axes you want to include in your loop, so:
- For the top ones only we do axes[0] to get the first two axes horizontally,
- and for the left ones we do axes[:, 0] to get the first two axes vertically.
# add the country labels
for ax, country in zip(axes[:, 0], df.countries.unique()):
ax.set_ylabel(
country,
rotation = 0,
size = 12,
color = xy_label_color,
labelpad =53)
# add the year labels
for ax, year in zip(axes[0], df.year.unique()):
ax.set_title(
year,
x = 0.5,
y = 1.2,
color = xy_label_color
)
fig