again / core /estimation /inference /ci_deviation.py
Beam2513's picture
Upload 127 files
798602c verified
import numpy as np
from scipy.stats import chi2, norm
from scipy.stats import iqr, median_abs_deviation
# ============================================================
# Analytic Confidence Intervals
# ============================================================
def ci_deviation_analytic(
*,
data,
alpha,
estimator,
):
n = len(data)
if estimator == "Deviation (1 ddof)":
return _ci_std_chi2(data, alpha)
if estimator == "Range (bias corrected)":
return _ci_sigma_from_range(data, alpha)
if estimator == "IQR (bias corrected)":
return _ci_iqr_asymptotic(data, alpha)
if estimator == "MAD (bias corrected)":
return _ci_mad_asymptotic(data, alpha)
if estimator == "AAD (bias corrected)":
return _ci_aad_asymptotic(data, alpha)
raise ValueError(f"Unknown deviation estimator: {estimator}")
# -------------------------------
# χ² CI for σ (std, ddof=1)
# -------------------------------
def _ci_std_chi2(data, alpha):
n = len(data)
s = np.std(data, ddof=1)
num = s * np.sqrt(n - 1)
lo = num / np.sqrt(chi2.ppf(1 - alpha / 2, n - 1))
hi = num / np.sqrt(chi2.ppf(alpha / 2, n - 1))
return lo, hi
# -------------------------------
# Range-based CI (bias corrected)
# -------------------------------
def _ci_sigma_from_range(data, alpha):
n = len(data)
R = np.max(data) - np.min(data)
d2_n = d2(n)
d3_n = d3(n)
z = norm.ppf(1 - alpha / 2)
denom_lo = d2_n + z * d3_n
denom_hi = d2_n - z * d3_n
if denom_hi <= 0:
raise ValueError("Invalid configuration: denominator ≤ 0")
return R / denom_lo, R / denom_hi
# -------------------------------
# IQR (asymptotic)
# -------------------------------
def _ci_iqr_asymptotic(data, alpha):
n = len(data)
IQR = iqr(data)
w = 2 * norm.ppf(0.75)
k = np.sqrt(np.pi / (2 * np.exp(-norm.ppf(0.75) ** 2)))
z = norm.ppf(1 - alpha / 2)
return (
IQR / (w + z * k / np.sqrt(n)),
IQR / (w - z * k / np.sqrt(n)),
)
# -------------------------------
# MAD (asymptotic)
# -------------------------------
def _ci_mad_asymptotic(data, alpha):
n = len(data)
MAD = median_abs_deviation(data)
w = norm.ppf(0.75)
k = np.sqrt(np.pi / (8 * np.exp(-norm.ppf(0.75) ** 2)))
z = norm.ppf(1 - alpha / 2)
return (
MAD / (w + z * k / np.sqrt(n)),
MAD / (w - z * k / np.sqrt(n)),
)
# -------------------------------
# AAD (asymptotic)
# -------------------------------
def _ci_aad_asymptotic(data, alpha):
n = len(data)
AAD = np.mean(np.abs(data - np.mean(data)))
w = np.sqrt(2 / np.pi)
k = np.sqrt(1 - 2 / np.pi)
z = norm.ppf(1 - alpha / 2)
return (
AAD / (w + z * k / np.sqrt(n)),
AAD / (w - z * k / np.sqrt(n)),
)
# ============================================================
# Bootstrap Confidence Intervals
# ============================================================
def ci_deviation_bootstrap(
*,
data,
alpha,
B,
estimator,
):
"""
Bootstrap CI for deviation estimators.
"""
n = len(data)
if estimator == "Deviation (1 ddof)":
boot = np.array([
np.std(np.random.choice(data, n, replace=True), ddof=1)
for _ in range(B)
])
return np.quantile(boot, [alpha / 2, 1 - alpha / 2])
if estimator == "Range (bias corrected)":
boot = np.array([
np.max(b := np.random.choice(data, n, replace=True)) - np.min(b)
for _ in range(B)
])
return np.quantile(boot, [alpha / 2, 1 - alpha / 2]) / d2(n)
if estimator == "IQR (bias corrected)":
boot = np.array([
iqr(np.random.choice(data, n, replace=True))
for _ in range(B)
])
return np.quantile(boot, [alpha / 2, 1 - alpha / 2]) / (
2 * norm.ppf(0.75)
)
if estimator == "MAD (bias corrected)":
boot = np.array([
median_abs_deviation(np.random.choice(data, n, replace=True))
for _ in range(B)
])
return np.quantile(boot, [alpha / 2, 1 - alpha / 2]) / norm.ppf(0.75)
if estimator == "AAD (bias corrected)":
boot = np.array([
np.mean(np.abs(
(b := np.random.choice(data, n, replace=True))
- np.mean(b)
))
for _ in range(B)
])
return np.quantile(boot, [alpha / 2, 1 - alpha / 2]) * np.sqrt(np.pi / 2)
raise ValueError(f"Unknown deviation estimator: {estimator}")
# ============================================================
# Bias-correction constants (monolith-compatible)
# ============================================================
def d2(n):
table = {
2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326, 6: 2.534,
7: 2.704, 8: 2.847, 9: 2.970, 10: 3.078,
11: 3.173, 12: 3.258, 13: 3.336, 14: 3.407,
15: 3.472, 16: 3.532, 17: 3.588, 18: 3.640,
19: 3.689, 20: 3.735, 21: 3.778, 22: 3.819,
23: 3.858, 24: 3.895, 25: 3.931,
}
return table[n]
def d3(n):
table = {
2: 0.852, 3: 0.888, 4: 0.880, 5: 0.864, 6: 0.848,
7: 0.833, 8: 0.820, 9: 0.808, 10: 0.797,
11: 0.787, 12: 0.778, 13: 0.770, 14: 0.763,
15: 0.756, 16: 0.750, 17: 0.744, 18: 0.739,
19: 0.734, 20: 0.729, 21: 0.724, 22: 0.720,
23: 0.716, 24: 0.712, 25: 0.708,
}
return table[n]