Raincloud Plot

Exam Scores by Study Method

Student exam performance across different study methodologies

Output
Exam Scores by Study Method
Python
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats

np.random.seed(18)
BG_COLOR = '#ffffff'
TEXT_COLOR = '#1f2937'
GRID_COLOR = '#e5e7eb'
COLOR_SCALE = ['#F5276C', '#6CF527', '#27D3F5', '#F5B027']

# Data: Exam scores (0-100) by study method
traditional = np.random.normal(72, 12, 50)
spaced_rep = np.random.normal(82, 8, 48)
active_recall = np.random.normal(85, 7, 52)
passive_review = np.random.normal(68, 14, 45)
y_data = [traditional, spaced_rep, active_recall, passive_review]
labels = ['Traditional', 'Spaced Rep.', 'Active Recall', 'Passive']

F_stat, p_value = stats.f_oneway(*y_data)
positions = list(range(len(y_data)))

fig, ax = plt.subplots(figsize=(10, 6), facecolor=BG_COLOR)
ax.set_facecolor(BG_COLOR)

violins = ax.violinplot(y_data, positions=positions, widths=0.5, 
                         bw_method="silverman", showmeans=False, 
                         showmedians=False, showextrema=False)
for pc in violins["bodies"]:
    pc.set_facecolor("none")
    pc.set_edgecolor(TEXT_COLOR)
    pc.set_linewidth(1.8)

bp = ax.boxplot(y_data, positions=positions, showfliers=False, showcaps=False,
                medianprops=dict(linewidth=3, color='#C82909'),
                whiskerprops=dict(linewidth=2, color='#9ca3af'),
                boxprops=dict(linewidth=2, color='#9ca3af'))

for i, (y, color) in enumerate(zip(y_data, COLOR_SCALE)):
    x_jitter = np.array([i] * len(y)) + stats.t(df=6, scale=0.04).rvs(len(y))
    ax.scatter(x_jitter, y, s=50, color=color, alpha=0.6, zorder=2, edgecolors='white', linewidths=0.5)

means = [y.mean() for y in y_data]
for i, (mean, color) in enumerate(zip(means, COLOR_SCALE)):
    ax.scatter(i, mean, s=180, color='#C82909', zorder=5, edgecolors='white', linewidths=2)
    ax.plot([i, i + 0.28], [mean, mean], ls="dashdot", color=TEXT_COLOR, zorder=3, lw=1.5)
    ax.text(i + 0.3, mean, f"μ={mean:.0f}%", fontsize=10, va="center", color=TEXT_COLOR,
            bbox=dict(facecolor=BG_COLOR, edgecolor=color, boxstyle="round,pad=0.15", lw=2))

eta_sq = 0.31
stats_text = f"ANOVA: F={F_stat:.1f}, p<0.001, η²={eta_sq:.2f}"
bbox = dict(facecolor=BG_COLOR, edgecolor='#5314E6', boxstyle="round,pad=0.3", lw=2)
ax.text(0.5, 1.02, stats_text, transform=ax.transAxes, fontsize=11, color=TEXT_COLOR,
        ha='center', va='bottom', fontfamily='monospace', bbox=bbox)

ax.set_ylabel('Exam Score (%)', fontsize=12, color=TEXT_COLOR, fontweight='500')
ax.set_title('Exam Scores by Study Method', fontsize=14, color=TEXT_COLOR, fontweight='bold', pad=30)
ax.set_xticks(positions)
ax.set_xticklabels(labels)
ax.tick_params(colors=TEXT_COLOR, labelsize=10)
for spine in ax.spines.values():
    spine.set_color(GRID_COLOR)
ax.yaxis.grid(True, color=GRID_COLOR, linewidth=0.5, alpha=0.7)
ax.set_axisbelow(True)
ax.set_ylim(30, 110)

plt.tight_layout()
plt.show()
Library

Matplotlib

Category

Statistical

Did this help you?

Support PyLucid to keep it free & growing

Support