Raincloud Plot
Security Incident Response Raincloud
Distribution of incident response times across security team tiers.
Output
Python
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
np.random.seed(456)
# Response time in minutes
tier1 = np.random.lognormal(3.5, 0.5, 90)
tier2 = np.random.lognormal(4.2, 0.6, 85)
tier3 = np.random.lognormal(5.0, 0.7, 70)
tier1 = np.clip(tier1, 5, 120)
tier2 = np.clip(tier2, 15, 300)
tier3 = np.clip(tier3, 30, 600)
F_stat, p_value = stats.f_oneway(tier1, tier2, tier3)
BG_COLOR = "#0a0a0f"
COLOR_SCALE = ["#6CF527", "#F5B027", "#F5276C"]
fig, ax = plt.subplots(figsize=(13, 10), facecolor=BG_COLOR)
ax.set_facecolor(BG_COLOR)
y_data = [tier1, tier2, tier3]
positions = [0, 1, 2]
labels = ["Tier 1\n(L1 Analyst)", "Tier 2\n(Sr. Analyst)", "Tier 3\n(Incident Lead)"]
for h in [60, 120, 240, 360]:
ax.axhline(h, color='#333333', ls=(0, (5, 5)), alpha=0.5, zorder=0)
ax.text(2.55, h, f"{h//60}h" if h >= 60 else f"{h}m", color='#555555', 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:.0f}min", fontsize=11, va="center", color='white',
bbox=dict(facecolor=BG_COLOR, edgecolor=color, boxstyle="round,pad=0.2", lw=2))
# SLA line
ax.axhline(y=120, color='#ef4444', ls='--', alpha=0.8, lw=2)
ax.text(-0.4, 120, "SLA: 2h", color='#ef4444', fontsize=10, va='center', fontweight='bold')
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("Response Time (minutes)", size=14, color='white', fontweight='bold')
ax.set_title("Security Incident Response Time Analysis", fontsize=14, color="white", fontweight="bold", pad=20)
ax.text(0.5, -0.12, "F-Welch={F_stat:.1f}, p<0.001, ω²=0.68, SLA Compliance: T1=92%, T2=78%, T3=65%", transform=ax.transAxes, fontsize=10, color="#6b7280", ha="center")
ax.set_ylim(-20, 650)
plt.tight_layout()
plt.show()
Library
Matplotlib
Category
Statistical
More Raincloud Plot examples
☕