Raincloud Plot

Programming Language Benchmark Raincloud

Elegant comparison of execution times across programming languages with statistical annotations.

Output
Programming Language Benchmark Raincloud
Python
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats

np.random.seed(42)

# Execution time in milliseconds for a benchmark task
python_times = np.random.lognormal(4.5, 0.4, 80)
java_times = np.random.lognormal(3.2, 0.35, 85)
rust_times = np.random.lognormal(2.0, 0.3, 75)

python_times = np.clip(python_times, 30, 400)
java_times = np.clip(java_times, 10, 100)
rust_times = np.clip(rust_times, 3, 30)

# ANOVA
F_stat, p_value = stats.f_oneway(python_times, java_times, rust_times)

# Colors
BG_COLOR = "#0a0a0f"
GREY_LIGHT = "#444444"
COLOR_SCALE = ["#F5276C", "#27D3F5", "#6CF527"]

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

y_data = [python_times, java_times, rust_times]
positions = [0, 1, 2]
labels = ["Python", "Java", "Rust"]

# Horizontal reference lines
for h in [50, 100, 200, 300]:
    ax.axhline(h, color=GREY_LIGHT, ls=(0, (5, 5)), alpha=0.5, zorder=0)

# Violins
violins = ax.violinplot(y_data, positions=positions, widths=0.45, 
                         bw_method="silverman", showmeans=False, 
                         showmedians=False, showextrema=False)
for pc in violins["bodies"]:
    pc.set_facecolor("none")
    pc.set_edgecolor("white")
    pc.set_linewidth(1.8)
    pc.set_alpha(0.9)

# Boxplots
bp = ax.boxplot(y_data, positions=positions, showfliers=False, showcaps=False,
                medianprops=dict(linewidth=3, color='#F5D327'),
                whiskerprops=dict(linewidth=2, color='#888888'),
                boxprops=dict(linewidth=2, color='#888888'))

# Jittered points
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=60, color=color, alpha=0.5, zorder=2)

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

# Comparison brackets
tick_len = 8
# Python vs Java
ax.plot([0, 0, 1, 1], [380-tick_len, 380, 380, 380-tick_len], c="white", lw=1.5)
ax.text(0.5, 385, "p < 0.001 ***", fontsize=10, va="bottom", ha="center", color='#F5D327')
# Python vs Rust
ax.plot([0, 0, 2, 2], [410-tick_len, 410, 410, 410-tick_len], c="white", lw=1.5)
ax.text(1, 415, "p < 0.001 ***", fontsize=10, va="bottom", ha="center", color='#F5D327')

# Styling
ax.spines["right"].set_visible(False)
ax.spines["top"].set_visible(False)
ax.spines["left"].set_color(GREY_LIGHT)
ax.spines["bottom"].set_color(GREY_LIGHT)
ax.tick_params(colors='#888888', length=0)

xlabels = [f"{l}\n(n={len(y_data[i])})" for i, l in enumerate(labels)]
ax.set_xticks(positions)
ax.set_xticklabels(xlabels, size=12, color='white')
ax.set_ylabel("Execution Time (ms)", size=14, color='white', fontweight='bold')

# Title with stats
ax.set_title("Programming Language Performance Benchmark", fontsize=14, color="white", fontweight="bold", pad=20)
ax.text(0.5, -0.12, "F-Welch={F_stat:.1f}, p={p_value:.2e}, ω²=0.89, n=240", transform=ax.transAxes, fontsize=10, color="#6b7280", ha="center")

ax.set_ylim(0, 450)
plt.tight_layout()
plt.show()
Library

Matplotlib

Category

Statistical

Did this help you?

Support PyLucid to keep it free & growing

Support