quant-experiments
Design and execute quantitative experiments in Jupyter notebooks with rich markdown narrative and visualizations. Use this skill whenever the user wants to: run statistical tests or hypothesis testing
SKILL.md.claude/skills/quant-experiments/SKILL.md
GitHubQuantitative Experiments in Jupyter — Skill Guide
Overview
This skill produces end-to-end Jupyter notebooks for quantitative experiments. The output style is markdown-narrative-first with rich visualizations: every analysis section is preceded by a markdown cell explaining the why, every result is followed by a markdown cell interpreting the so what, and every key finding is illustrated with a well-labeled, annotated plot.
Notebook Structure (Always Follow This)
1. # Experiment Title
[markdown] Objective, hypothesis, scope, expected outcome
2. ## 🔧 Setup & Imports
[code] All imports, seeds, config constants
[markdown] Brief note on key libraries and why they're used
3. ## 📊 Data / Data Generation
[markdown] Describe the data source or generative process
[code] Load or simulate data
[code] Descriptive statistics + distribution plot
[markdown] Key observations from the data
4. ## 📐 Methodology
[markdown] Explain the chosen method with LaTeX formula
[markdown] State assumptions and how they'll be verified
[code] Assumption checks with diagnostic plots
5. ## 🧪 Experiment / Analysis
[markdown] Walk through each analysis step in plain language
[code] Core analysis (clean, well-commented)
[code] Intermediate visualizations after each major step
[markdown] Narrative of what intermediate results suggest
6. ## 📈 Results
[code] Results table (pandas Styler)
[code] Primary results visualization (always required)
[markdown] Narrative interpretation of what the chart shows
7. ## ✅ Conclusion
[markdown] APA-style statistical summary line
[markdown] Plain-language decision and takeaway
[markdown] Limitations and suggested next steps
8. ## 🔍 Appendix (optional)
Sensitivity analysis, robustness checks, extra plots
Markdown Cell Rules
- Before every code block: 1–3 sentence explanation of what the code does and why
- After every visualization: 2–4 sentence interpretation ("The distribution is right-skewed, suggesting…")
- Use
>blockquotes for key findings:> **Finding:** The treatment group shows a 12% lift (p < .001) - Use emoji section headers to improve scannability
Visualization Standards
Global Figure Setup (always in Setup cell)
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
sns.set_theme(style="whitegrid", palette="deep", font="DejaVu Sans")
mpl.rcParams.update({
'figure.dpi': 150,
'figure.figsize': (10, 6),
'axes.spines.top': False,
'axes.spines.right': False,
'axes.titlesize': 14,
'axes.titleweight': 'bold',
'axes.labelsize': 12,
'xtick.labelsize': 10,
'ytick.labelsize': 10,
'legend.fontsize': 10,
'figure.titlesize': 16,
})
PALETTE = sns.color_palette("deep")
Required Plots by Experiment Type
| Experiment | Required plots |
|---|---|
| Hypothesis test | Distribution KDE comparison + effect size forest plot |
| Monte Carlo | Outcome histogram with percentile bands + convergence trace |
| A/B test | Conversion rate bars with CI + power curve |
| Time series | Decomposition (4-panel) + forecast with CI band |
| Optimization | Objective landscape + convergence curve |
| ML experiment | CV score violin plot + mean±CI bar + feature importance |
Plot Template (use for every figure)
fig, ax = plt.subplots(figsize=(10, 6))
# --- your plot code ---
ax.set_title('Descriptive Title', pad=15)
ax.set_xlabel('X Label (units)')
ax.set_ylabel('Y Label (units)')
ax.legend(framealpha=0.9)
# Annotate key finding directly on chart
ax.annotate('Key insight here',
xy=(x_pos, y_pos), xytext=(x_pos + dx, y_pos + dy),
arrowprops=dict(arrowstyle='->', color='gray'),
fontsize=10, color='#333333')
plt.tight_layout()
plt.show()
Multi-panel Layout (preferred for results sections)
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle('Experiment Results Overview', fontsize=16, fontweight='bold')
# panel 1: raw data / distributions
# panel 2: main test result / finding
# panel 3: effect size / confidence intervals
plt.tight_layout()
Experiment Types
1. Hypothesis Testing
Markdown narrative pattern:
"We test whether [X differs from Y] using [test] because [normality check result / n]. H₀: μ₁ = μ₂ | H₁: μ₁ ≠ μ₂ (two-tailed, α = 0.05)"
Key elements:
- State H₀ / H₁ in LaTeX in a markdown cell
- Normality:
scipy.stats.shapiro(n<50) orscipy.stats.normaltest - Variance:
scipy.stats.levene; plot violin/box side-by-side - Report: test statistic, p-value, Cohen's d, 95% CI
- APA string:
f"t({df}) = {t:.3f}, p = {p:.3f}, d = {d:.3f}, 95% CI [{lo:.3f}, {hi:.3f}]"
Required plots: Overlapping KDE + rug plot with group means marked; effect size with CI bar
2. Monte Carlo Simulations
Markdown narrative pattern:
"We simulate [process] N=100,000 times to estimate [quantity]. The generative model is: $$X_t = \mu + \sigma \epsilon_t, \quad \epsilon_t \sim \mathcal{N}(0,1)$$ Vectorized NumPy is used for performance (~50× faster than loops)."
Key elements:
- LaTeX process equation in markdown before code
- Vectorized NumPy (
np.random.default_rng) - Report: mean, std, 5th / 25th / 75th / 95th percentiles
- Convergence plot to verify N is sufficient
Required plots:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# Left: outcome distribution with shaded percentile bands
ax1.hist(results, bins=80, color=PALETTE[0], alpha=0.7, density=True)
for pct, ls in [(5,'--'), (25,':'), (75,':'), (95,'--')]:
ax1.axvline(np.percentile(results, pct), color='red', ls=ls, lw=1.2,
label=f'{pct}th %ile')
ax1.set_title('Simulation Outcome Distribution')
# Right: convergence trace
running_mean = np.cumsum(results) / np.arange(1, len(results)+1)
ax2.plot(running_mean, color=PALETTE[1], lw=1.2)
ax2.axhline(results.mean(), color='red', ls='--', label='Converged mean')
ax2.set_title('Convergence of Running Mean')
ax2.set_xlabel('Simulation #'); ax2.set_ylabel('Running Mean')
3. A/B Testing & Causal Inference
Markdown narrative pattern:
"Required sample size (MDE=[X]%, power=80%, α=5%) is N=[n] per group. We have N=[actual]; the test is [adequately/under] powered."
Sub-types and libraries:
| Goal | Method | Library |
|---|---|---|
| Proportion test | Z-test | statsmodels.stats.proportion |
| Continuous metric | Welch's t-test | scipy.stats |
| Bayesian A/B | Beta-binomial | analytical |
| Uplift / CATE | Meta-learners | causalml |
| Observational | Propensity score matching | causalinference |
| Panel data | Difference-in-Differences | linearmodels |
Always include: MDE calc, power curve plot, segment breakdown table (Simpson's paradox guard)
MDE formula in markdown: $$n = \frac{(z_{\alpha/2} + z_\beta)^2 \cdot 2\sigma^2}{\delta^2}$$
Required plots: Conversion rate bars with CI error bars; Bayesian posterior overlay if Bayesian
4. Numerical Optimization
Markdown narrative pattern:
"We minimize f(x) = [expression] subject to [constraints]. The landscape is visualized first to identify the number of local minima before choosing a solver."
Key elements:
- Always visualize objective landscape before running optimizer
- Track convergence via callback: log f(x*) per iteration
- Compare methods if relevant (gradient-based vs. evolutionary)
- Report: optimal x*, f(x*), iterations to converge, wall-clock time
Standard pattern:
from scipy.optimize import minimize, differential_evolution
# 1. Visualize landscape
x_grid = np.linspace(*bounds, 300)
ax.plot(x_grid, [objective(x) for x in x_grid], lw=2)
ax.set_title('Objective Function Landscape')
# 2. Optimize with convergence tracking
history = []
result = minimize(objective, x0, method='L-BFGS-B',
bounds=[bounds],
callback=lambda xk: history.append(objective(xk)))
# 3. Convergence plot
ax2.plot(history, marker='o', markersize=3, lw=1.5)
ax2.set_title('Convergence Trace')
ax2.set_xlabel('Iteration'); ax2.set_ylabel('Objective f(x)')
Multi-method comparison table (styled):
results_df.style\
.highlight_min(subset=['f(x*)'], color='lightgreen')\
.highlight_min(subset=['Runtime (s)'], color='lightyellow')\
.format({'f(x*)': '{:.6f}', 'Runtime (s)': '{:.3f}'})\
.set_caption('Optimizer Comparison')
5. Time Series & Forecasting
Markdown narrative pattern:
"We decompose the series into trend, seasonal, and residual components, then test for stationarity (ADF). Model order is guided by ACF/PACF plots before fitting [ARIMA/ETS/Prophet]."
Key elements:
- Decomposition:
statsmodels.tsa.seasonal.seasonal_decompose - Stationarity: ADF test (
statsmodels.tsa.stattools.adfuller) - ACF/PACF plots to guide model order (
statsmodels.graphics.tsaplots) - Always show forecast with confidence interval band
- Metrics: MAE, RMSE, MAPE, directional accuracy
Required plots:
# 4-panel decomposition
fig, axes = plt.subplots(4, 1, figsize=(12, 14), sharex=True)
fig.suptitle('Time Series Decomposition', fontsize=16, fontweight='bold')
decomp = seasonal_decompose(series, model='additive', period=period)
for ax, data, label in zip(axes,
[series, decomp.trend, decomp.seasonal, decomp.resid],
['Observed', 'Trend', 'Seasonal', 'Residual']):
ax.plot(data, lw=1.5)
ax.set_ylabel(label, fontsize=11)
# Forecast with CI band
fig2, ax = plt.subplots(figsize=(12, 5))
ax.plot(train.index, train, label='Train', lw=1.5)
ax.plot(test.index, test, label='Actual', lw=1.5)
ax.plot(forecast.index, forecast['mean'], label='Forecast', ls='--', lw=2)
ax.fill_between(forecast.index,
forecast['mean_ci_lower'], forecast['mean_ci_upper'],
alpha=0.2, label='95% CI')
ax.set_title('Forecast vs. Actuals')
ax.legend()
6. Machine Learning Experiments
Markdown narrative pattern:
"We compare [N] models using [K]-fold stratified CV on [metric]. Statistical significance of pairwise differences is assessed with Wilcoxon signed-rank test on the per-fold scores."
Key elements:
- Stratified K-fold; report mean ± std per model
- Wilcoxon / Friedman test across folds — never compare point estimates alone
- Learning curves for best model
- Feature importance or SHAP values
Required plots:
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
fig.suptitle('ML Experiment Results', fontsize=16, fontweight='bold')
# Panel 1: CV score violin + strip (shows full distribution)
sns.violinplot(data=cv_scores_df, ax=axes[0], palette='deep', inner='box')
sns.stripplot(data=cv_scores_df, ax=axes[0], color='black', alpha=0.4, size=3)
axes[0].set_title('CV Score Distributions')
# Panel 2: Mean ± 95% CI horizontal bar
axes[1].barh(model_names, means, xerr=1.96*stds/np.sqrt(K),
color=PALETTE[:len(model_names)], capsize=5, alpha=0.85)
axes[1].set_title('Mean CV Score ± 95% CI')
# Panel 3: Feature importances (top 15)
feat_imp.sort_values().tail(15).plot(kind='barh', ax=axes[2], color=PALETTE[0], alpha=0.85)
axes[2].set_title('Top 15 Feature Importances')
plt.tight_layout()
Standard Imports Cell
# === Core ===
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
from scipy import stats
# === Stats & Experiments ===
import statsmodels.api as sm
from statsmodels.stats.power import TTestIndPower
# === Display ===
from IPython.display import display, Markdown, Latex
from tabulate import tabulate
# === Visualization Theme ===
sns.set_theme(style="whitegrid", palette="deep")
mpl.rcParams.update({
'figure.dpi': 150, 'figure.figsize': (10, 6),
'axes.spines.top': False, 'axes.spines.right': False,
'axes.titlesize': 14, 'axes.titleweight': 'bold', 'axes.labelsize': 12,
})
PALETTE = sns.color_palette("deep")
# === Reproducibility ===
SEED = 42
np.random.seed(SEED)
print(f"NumPy {np.__version__} | Pandas {pd.__version__} | Seaborn {sns.__version__}")
Reporting Checklist
Before finalizing any notebook:
- Every code section has a markdown cell before (explaining what and why)
- Every visualization has a markdown cell after (interpreting what it shows)
- Key findings called out with
> **Finding:** ...blockquotes - Random seed set and documented
- All assumptions explicitly tested with plots
- Effect size alongside p-value; confidence intervals on all estimates
- All figures: axis labels, title, legend, annotations
- Results table styled with pandas Styler
- Conclusion in plain language with limitations
Reference Files
references/statistical_tests_guide.md— Test selection decision tree + assumptionsreferences/effect_sizes.md— Effect size formulas, code, and interpretation benchmarksreferences/causal_inference_patterns.md— RCT, DiD, PSM, Bayesian A/B patternsreferences/timeseries_optimization.md— Time series model selection + optimization algorithms
Read the relevant reference when deeper guidance on test selection, causal design, model order, or optimization algorithm choice is needed.