import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Define the number of cases and data points per case
num_cases = 40
data_points_per_case = 10
# Initialize an empty DataFrame to store the data
data = pd.DataFrame()
# Generate data for each specified case
np.random.seed(42) # For reproducibility
for i in range(1, num_cases + 1):
if i % 10 in [1, 2, 3, 7, 8]: # Cases in [0, 6)
case_data = np.random.uniform(0, 6, data_points_per_case)
elif i % 10 in [4, 5, 6, 9, 0]: # Cases in (-6, 0]
case_data = np.random.uniform(-6, 0, data_points_per_case)
data[f'S{i:02}'] = case_data
# Color settings
purple_color = '#5654a2'
red_color = 'red'
light_purple_color = '#827dce'
# Define case classes and groups
case_classes = {
'A': ['S01', 'S02', 'S03', 'S04', 'S05'],
'B': ['S06', 'S07'],
'C': ['S08', 'S09', 'S10'],
'D': ['S11', 'S12', 'S13', 'S14'],
'E': ['S15', 'S16', 'S17', 'S18', 'S19', 'S20'],
'F': ['S21', 'S22', 'S23', 'S24', 'S25'],
'G': ['S26', 'S27'],
'H': ['S28', 'S29', 'S30'],
'I': ['S31', 'S32', 'S33', 'S34'],
'J': ['S35', 'S36', 'S37', 'S38', 'S39', 'S40']
}
group_classes = {
'G1': ['A', 'B'],
'G2': ['C', 'D'],
'G3': ['E'],
'G4': ['F', 'G'],
'G5': ['H', 'I'],
'G6': ['J']
}
# Map case classes to groups
case_to_group = {case: group for group, classes in group_classes.items() for cls in classes for case in case_classes[cls]}
case_to_class = {case: cls for cls, cases in case_classes.items() for case in cases}
# Define group colors for xticks
group_colors = {
'G1': 'blue',
'G2': 'green',
'G3': 'orange',
'G4': 'purple',
'G5': 'brown',
'G6': 'pink'
}
# Prepare data for circular barplot
values = data.mean() # Using mean values of the cases
categories = data.columns
# Number of variables we're plotting
num_vars = len(categories)
# Compute angle for each category with a 30° gap between S01 and S40
gap_deg = 30
gap_angle = np.deg2rad(gap_deg + 10)
total_angle = 2 * np.pi - gap_angle
angles = np.linspace(total_angle + gap_angle / 2, gap_angle / 2, num_vars, endpoint=False).tolist()
# Plotting
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(polar=True))
# Add background colors for classes
class_colors = {
'A': 'lightgray',
'B': 'white',
'C': 'lightgray',
'D': 'white',
'E': 'lightgray',
'F': 'white',
'G': 'lightgray',
'H': 'white',
'I': 'lightgray',
'J': 'white'
}
for i, (angle, case) in enumerate(zip(angles, categories)):
cls = case_to_class[case]
color = class_colors[cls]
ax.bar(angle, 12, width=total_angle / num_vars, color=color, edgecolor='none', alpha=0.5, bottom=-6, zorder=0)
# Make the plot
for i, (angle, value, case) in enumerate(zip(angles, values, categories)):
if int(case[1:]) % 10 in [1, 2, 3, 7, 8]: # Cases in [0, 6)
color = light_purple_color
edgecolor = purple_color
else: # Cases in (-6, 0]
color = 'lightcoral'
edgecolor = red_color
ax.bar(angle, value, width=total_angle / num_vars, color=color, edgecolor=edgecolor, alpha=0.7, zorder=1)
# Add labels
y_ticks = [ -6, -4, -2, 0, 2, 4, 6]
ax.set_yticks(y_ticks)
ax.set_yticklabels([f'{tick}' for tick in y_ticks], fontsize=10)
ax.set_xticks(angles)
ax.set_xticklabels(categories, fontsize=10)
# Customize labels color based on group
for label, angle in zip(ax.get_xticklabels(), angles):
case = label.get_text()
group = case_to_group[case] # Get the group from the case
label.set_color(group_colors[group])
# Add class and group labels
displayed_classes = set()
displayed_groups = set()
for angle, case in zip(angles, categories):
case_class = case_to_class[case]
group = case_to_group[case]
if case_class not in displayed_classes:
ax.text(angle, max(y_ticks) + 1.5, case_class, horizontalalignment='center', size=10, color=group_colors[group], weight='semibold')
displayed_classes.add(case_class)
if group not in displayed_groups:
ax.text(angle, max(y_ticks) + 3, group, horizontalalignment='center', size=12, color=group_colors[group], weight='semibold')
displayed_groups.add(group)
# Set the start angle to make S01 at 0.5 * np.pi and leave 30° gap
ax.set_theta_offset(0.5 * np.pi + gap_angle / 2)
ax.set_thetamin(gap_deg * 2 / 5) # Start angle in degrees
ax.set_thetamax(360 - gap_deg * 2 / 5) # End angle in degrees
# Set a minimum radius to create an empty inner ring
ax.set_ylim(-10, 6)
# plt.title('Circular Barplot for 40 Cases by Group')
plt.show()