Raincloud Plot

Clinical Drug Efficacy Raincloud

Comparing treatment response rates across clinical trial arms.

Output
Clinical Drug Efficacy Raincloud
Python
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats

np.random.seed(777)

# Response score (0-100)
placebo = np.random.normal(35, 12, 100)
low_dose = np.random.normal(52, 14, 95)
high_dose = np.random.normal(68, 11, 90)

placebo = np.clip(placebo, 5, 70)
low_dose = np.clip(low_dose, 20, 85)
high_dose = np.clip(high_dose, 40, 95)

F_stat, p_value = stats.f_oneway(placebo, low_dose, high_dose)

BG_COLOR = "#0d1117"
COLOR_SCALE = ["#888888", "#F5B027", "#6CF527"]

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

y_data = [placebo, low_dose, high_dose]
positions = [0, 1, 2]
labels = ["Placebo", "Low Dose\n(50mg)", "High Dose\n(100mg)"]

for h in [30, 50, 70]:
    ax.axhline(h, color='#333333', ls=(0, (5, 5)), alpha=0.5, zorder=0)

# Efficacy threshold
ax.axhline(y=50, color='#22c55e', ls='--', alpha=0.7, lw=2)
ax.text(2.55, 50, "Clinical\nThreshold", color='#22c55e', fontsize=9, va='center')

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("#27D3F5")
    pc.set_linewidth(2)

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

for i, (y, color) in enumerate(zip(y_data, COLOR_SCALE)):
    x_jitter = np.array([i] * len(y)) + stats.t(df=6, scale=0.045).rvs(len(y))
    ax.scatter(x_jitter, y, s=55, color=color, alpha=0.55, zorder=2)

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.32], [mean, mean], ls="dashdot", color="white", zorder=3, lw=1.5)
    ax.text(i + 0.34, mean, f"μ={mean:.1f}", fontsize=11, va="center", color='white',
            bbox=dict(facecolor=BG_COLOR, edgecolor=color, boxstyle="round,pad=0.2", lw=2))

# P-value brackets
tick = 3
ax.plot([0, 0, 1, 1], [88-tick, 88, 88, 88-tick], c="white", lw=1.5)
ax.text(0.5, 90, "p<0.001***", fontsize=10, va="bottom", ha="center", color='#F5D327')
ax.plot([0, 0, 2, 2], [95-tick, 95, 95, 95-tick], c="white", lw=1.5)
ax.text(1, 97, "p<0.001***", fontsize=10, va="bottom", ha="center", color='#F5D327')

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

ax.set_xticks(positions)
ax.set_xticklabels(labels, size=11, color='white')
ax.set_ylabel("Treatment Response Score", size=14, color='white', fontweight='bold')

ax.set_title("Clinical Drug Efficacy Trial Results", fontsize=14, color="white", fontweight="bold", pad=20)


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

Matplotlib

Category

Statistical

Did this help you?

Support PyLucid to keep it free & growing

Support