Spaces:
Sleeping
Sleeping
File size: 4,288 Bytes
f6dac2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """Day-9 tests — bootstrap estimators."""
from __future__ import annotations
import numpy as np
import pandas as pd
import pytest
from src.bootstrap import (
BootstrapCI,
cluster_bootstrap_did,
cluster_bootstrap_mean_car,
stationary_bootstrap,
)
def _make_did_panel(
delta: float = 0.01, n_treated: int = 8, n_controls: int = 8, n_per: int = 60
) -> pd.DataFrame:
rng = np.random.default_rng(0)
rows: list[dict[str, float | int | str]] = []
treated_ids = [f"T{i}" for i in range(n_treated)]
control_ids = [f"C{i}" for i in range(n_controls)]
for tk in treated_ids + control_ids:
for t in range(-n_per // 2, n_per // 2):
ret = rng.normal(0, 0.005)
if tk in treated_ids and t >= 0:
ret += delta
rows.append({"ticker": tk, "t": t, "ret": ret})
return pd.DataFrame(rows)
def test_bootstrap_ci_has_required_fields() -> None:
ci = BootstrapCI(
metric="x", scope="y", point=0.5, lower=0.4, upper=0.6, n_replications=100
)
assert ci.metric == "x" and ci.scope == "y"
assert 0.4 <= ci.point <= 0.6
assert ci.lower_pct == 5.0 and ci.upper_pct == 95.0
def test_cluster_bootstrap_did_recovers_delta() -> None:
panel = _make_did_panel(delta=0.01, n_treated=8, n_controls=8, n_per=80)
res = cluster_bootstrap_did(
panel,
treated={f"T{i}" for i in range(8)},
controls={f"C{i}" for i in range(8)},
metric_name="did_delta",
scope="toy",
n_reps=200, # small for unit test speed
seed=42,
)
assert res.point == pytest.approx(0.01, abs=3e-3)
# CI should contain the true δ at 5–95
assert res.lower < 0.01 < res.upper
def test_cluster_bootstrap_did_ci_excludes_zero_when_signal_strong() -> None:
panel = _make_did_panel(delta=0.02, n_treated=10, n_controls=10, n_per=100)
res = cluster_bootstrap_did(
panel,
treated={f"T{i}" for i in range(10)},
controls={f"C{i}" for i in range(10)},
metric_name="did_delta",
scope="toy_strong",
n_reps=300,
seed=0,
)
# δ=0.02 with σ=0.005 → strong signal, 5–95 CI should not cover 0
assert res.lower > 0
def test_stationary_bootstrap_recovers_mean() -> None:
rng = np.random.default_rng(123)
x = rng.normal(loc=0.05, scale=0.01, size=300)
res = stationary_bootstrap(
pd.Series(x),
fn=np.mean,
metric_name="mean",
scope="toy",
n_reps=500,
mean_block_len=5,
seed=0,
)
assert res.point == pytest.approx(0.05, abs=2e-3)
assert res.lower < 0.05 < res.upper
def test_stationary_bootstrap_rejects_too_short_series() -> None:
with pytest.raises(ValueError, match="too short"):
stationary_bootstrap(
pd.Series([0.0]),
fn=np.mean,
metric_name="m",
scope="s",
n_reps=10,
seed=0,
)
def test_cluster_bootstrap_mean_car_point_equals_sample_mean() -> None:
"""Bootstrap point estimate is the sample mean by definition."""
rng = np.random.default_rng(7)
cars = pd.Series(rng.normal(loc=0.02, scale=0.05, size=300))
res = cluster_bootstrap_mean_car(
cars, metric_name="car", scope="t1", n_reps=500, seed=0
)
assert res.point == pytest.approx(float(cars.mean()), abs=1e-12)
# CI brackets the point estimate
assert res.lower < res.point < res.upper
# CI width is on order of 2·SE(mean) ≈ 2·0.05/√300 ≈ 0.006 → 90% CI ~0.01
assert 0.001 < (res.upper - res.lower) < 0.05
def test_cluster_bootstrap_mean_car_rejects_empty() -> None:
with pytest.raises(ValueError, match="empty"):
cluster_bootstrap_mean_car(
pd.Series([np.nan, np.nan]),
metric_name="m",
scope="s",
n_reps=10,
)
def test_cluster_bootstrap_did_rejects_empty_sets() -> None:
panel = _make_did_panel(delta=0.0, n_treated=4, n_controls=4, n_per=50)
with pytest.raises(ValueError, match="empty"):
cluster_bootstrap_did(
panel,
treated=set(),
controls={f"C{i}" for i in range(4)},
metric_name="m",
scope="s",
n_reps=10,
)
|