Screening statistics for rule discovery.
These are screening tools, not confirmatory inference (Zhao & Welsch,
arXiv:2605.31189).
Two-proportion test falls back to Fisher's exact test when any expected
cell count is below 5 (documented decision, spec §6.3).
compute_min_support
compute_min_support(n_train: int) -> int
Minimum tail/level support: min(200, max(20, ceil(0.02 * n_train))).
Source code in src/flaggam/screening.py
| def compute_min_support(n_train: int) -> int:
"""Minimum tail/level support: min(200, max(20, ceil(0.02 * n_train)))."""
return min(200, max(20, math.ceil(0.02 * n_train)))
|
bh_adjust
bh_adjust(p_values: ndarray) -> ndarray
Benjamini-Hochberg adjusted p-values (within one feature's candidates).
Source code in src/flaggam/screening.py
| def bh_adjust(p_values: np.ndarray) -> np.ndarray:
"""Benjamini-Hochberg adjusted p-values (within one feature's candidates)."""
p = np.asarray(p_values, dtype=float)
m = p.size
order = np.argsort(p)
ranked = p[order] * m / np.arange(1, m + 1)
ranked = np.minimum.accumulate(ranked[::-1])[::-1]
adj = np.empty(m, dtype=float)
adj[order] = np.clip(ranked, 0.0, 1.0)
return adj
|
two_proportion_test
two_proportion_test(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float
Two-sided two-proportion z-test; Fisher's exact if any expected count < 5.
Source code in src/flaggam/screening.py
| def two_proportion_test(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float:
"""Two-sided two-proportion z-test; Fisher's exact if any expected count < 5."""
if n_tail == 0 or n_base == 0:
return 1.0
if _expected_counts(k_tail, n_tail, k_base, n_base) < 5.0:
table = [[k_tail, n_tail - k_tail], [k_base, n_base - k_base]]
return float(stats.fisher_exact(table, alternative="two-sided")[1])
p1, p0 = k_tail / n_tail, k_base / n_base
pooled = (k_tail + k_base) / (n_tail + n_base)
se = math.sqrt(pooled * (1.0 - pooled) * (1.0 / n_tail + 1.0 / n_base))
if se == 0.0:
return 1.0
z = (p1 - p0) / se
return float(2.0 * stats.norm.sf(abs(z)))
|
chi_square_test
chi_square_test(y_tail: ndarray, y_base: ndarray) -> float
Pearson chi-square p-value on the 2 x K (region x class) table.
Source code in src/flaggam/screening.py
| def chi_square_test(y_tail: np.ndarray, y_base: np.ndarray) -> float:
"""Pearson chi-square p-value on the 2 x K (region x class) table."""
classes = np.union1d(np.unique(y_tail), np.unique(y_base))
table = np.array(
[[np.sum(y_tail == c) for c in classes], [np.sum(y_base == c) for c in classes]]
)
keep = table.sum(axis=0) > 0
table = table[:, keep]
if table.shape[1] < 2 or table.sum(axis=1).min() == 0:
return 1.0
return float(stats.chi2_contingency(table, correction=False)[1])
|
welch_t_test
welch_t_test(a: ndarray, b: ndarray) -> float
Two-sided Welch t-test p-value; 1.0 when a side is degenerate.
Source code in src/flaggam/screening.py
| def welch_t_test(a: np.ndarray, b: np.ndarray) -> float:
"""Two-sided Welch t-test p-value; 1.0 when a side is degenerate."""
if len(a) < 2 or len(b) < 2:
return 1.0
res = stats.ttest_ind(a, b, equal_var=False)
return float(res.pvalue) if np.isfinite(res.pvalue) else 1.0
|
risk_difference
risk_difference(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float
Absolute difference in positive-class rate, tail vs baseline (spec §6.1).
Source code in src/flaggam/screening.py
| def risk_difference(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float:
"""Absolute difference in positive-class rate, tail vs baseline (spec §6.1)."""
return abs(k_tail / n_tail - k_base / n_base)
|
log_odds_ratio
log_odds_ratio(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float
Absolute log odds ratio with 0.5 continuity correction.
Source code in src/flaggam/screening.py
| def log_odds_ratio(k_tail: int, n_tail: int, k_base: int, n_base: int) -> float:
"""Absolute log odds ratio with 0.5 continuity correction."""
a, b = k_tail + 0.5, n_tail - k_tail + 0.5
c, d = k_base + 0.5, n_base - k_base + 0.5
return abs(math.log((a * d) / (b * c)))
|
standardized_mean_difference
standardized_mean_difference(a: ndarray, b: ndarray) -> float
Absolute standardized mean difference with pooled (average) variance.
Source code in src/flaggam/screening.py
| def standardized_mean_difference(a: np.ndarray, b: np.ndarray) -> float:
"""Absolute standardized mean difference with pooled (average) variance."""
var = (np.var(a, ddof=1) + np.var(b, ddof=1)) / 2.0
if var == 0.0:
return 0.0
return float(abs(np.mean(a) - np.mean(b)) / np.sqrt(var))
|