from __future__ import annotations import math import random def wilson_interval(successes: int, n: int, confidence: float = 0.95) -> tuple[float, float]: if n <= 0: return 0.0, 0.0 z = 1.959963984540054 phat = successes / n denom = 1 + z * z / n center = (phat + z * z / (2 * n)) / denom margin = z * math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n) / denom return max(0.0, center - margin), min(1.0, center + margin) def bootstrap_ci(values: list[float], iterations: int = 2000, confidence: float = 0.95) -> tuple[float, float]: if not values: return 0.0, 0.0 if len(values) == 1: return values[0], values[0] means = [] for _ in range(iterations): sample = [random.choice(values) for _ in range(len(values))] means.append(sum(sample) / len(sample)) means.sort() alpha = (1 - confidence) / 2 lo_idx = max(0, int(alpha * len(means))) hi_idx = min(len(means) - 1, int((1 - alpha) * len(means)) - 1) return means[lo_idx], means[hi_idx] def task_bootstrap_ci(task_values: list[float], iterations: int = 2000, confidence: float = 0.95) -> tuple[float, float]: return bootstrap_ci(task_values, iterations=iterations, confidence=confidence) def paired_bootstrap_delta( left: list[float], right: list[float], iterations: int = 2000, confidence: float = 0.95 ) -> dict: if not left or not right or len(left) != len(right): return {"delta": 0.0, "ci_low": 0.0, "ci_high": 0.0} deltas = [] paired = list(zip(left, right)) for _ in range(iterations): sample = [random.choice(paired) for _ in range(len(paired))] diffs = [l - r for l, r in sample] deltas.append(sum(diffs) / len(diffs)) deltas.sort() alpha = (1 - confidence) / 2 lo_idx = max(0, int(alpha * len(deltas))) hi_idx = min(len(deltas) - 1, int((1 - alpha) * len(deltas)) - 1) observed = sum(l - r for l, r in paired) / len(paired) return { "delta": observed, "ci_low": deltas[lo_idx], "ci_high": deltas[hi_idx], }