Skip to content

Weighting

weighting

Feature-weight statistics and the compact score representation (spec §6.6-6.7).

Compact scores are a classification-only ablation/option; benchmark default is the full Z(X). A flag contributes to the score of its enriched class.

point_biserial

point_biserial(x: ndarray, y: ndarray) -> float

Absolute Pearson correlation between numerical x and binary y.

Source code in src/flaggam/weighting.py
def point_biserial(x: np.ndarray, y: np.ndarray) -> float:
    """Absolute Pearson correlation between numerical x and binary y."""
    if np.std(x) == 0.0 or np.std(y) == 0.0:
        return 0.0
    return float(abs(np.corrcoef(x, y)[0, 1]))

correlation_ratio

correlation_ratio(x: ndarray, y: ndarray) -> float

Correlation ratio eta of numerical x across the groups defined by y.

Source code in src/flaggam/weighting.py
def correlation_ratio(x: np.ndarray, y: np.ndarray) -> float:
    """Correlation ratio eta of numerical x across the groups defined by y."""
    grand = x.mean()
    ss_total = float(((x - grand) ** 2).sum())
    if ss_total == 0.0:
        return 0.0
    ss_between = sum(len(x[y == c]) * (x[y == c].mean() - grand) ** 2 for c in np.unique(y))
    return float(np.sqrt(ss_between / ss_total))

cramers_v

cramers_v(x: ndarray, y: ndarray) -> float

Cramer's V between categorical x and target labels y.

Source code in src/flaggam/weighting.py
def cramers_v(x: np.ndarray, y: np.ndarray) -> float:
    """Cramer's V between categorical x and target labels y."""
    table = pd.crosstab(pd.Series(x), pd.Series(y)).to_numpy(dtype=float)
    n = table.sum()
    if n == 0 or min(table.shape) < 2:
        return 0.0
    expected = np.outer(table.sum(1), table.sum(0)) / n
    with np.errstate(divide="ignore", invalid="ignore"):
        chi2 = np.nansum((table - expected) ** 2 / expected)
    k = min(table.shape) - 1
    return float(np.sqrt(chi2 / (n * k)))

feature_weights

feature_weights(X: DataFrame, y: ndarray, task: str, numerical: list[str]) -> dict[str, float]

Per-feature association weight on non-missing training rows.

Source code in src/flaggam/weighting.py
def feature_weights(
    X: pd.DataFrame, y: np.ndarray, task: str, numerical: list[str]
) -> dict[str, float]:
    """Per-feature association weight on non-missing training rows."""
    weights: dict[str, float] = {}
    for col in X.columns:
        obs = X[col].notna().to_numpy()
        if obs.sum() < 2:
            weights[col] = 0.0
            continue
        xv, yv = X[col].to_numpy()[obs], y[obs]
        if col in numerical:
            xv = xv.astype(float)
            weights[col] = point_biserial(xv, yv) if task == "binary" else correlation_ratio(xv, yv)
        else:
            weights[col] = cramers_v(xv, yv)
    return weights

compact_scores

compact_scores(Z: spmatrix, bases: list[Basis], classes: ndarray, weights: dict[str, float] | None) -> ndarray

Per-class (optionally feature-weighted) sums of triggered flags: (n, K).

Source code in src/flaggam/weighting.py
def compact_scores(
    Z: sparse.spmatrix,
    bases: list[Basis],
    classes: np.ndarray,
    weights: dict[str, float] | None,
) -> np.ndarray:
    """Per-class (optionally feature-weighted) sums of triggered flags: (n, K)."""
    Zd = np.asarray(Z.todense(), dtype=float)
    out = np.zeros((Zd.shape[0], len(classes)))
    class_index = {c: k for k, c in enumerate(classes)}
    for j, b in enumerate(bases):
        if b.kind not in _FLAG_KINDS or b.enriched_class not in class_index:
            continue
        w = 1.0 if weights is None else weights.get(b.feature, 0.0)
        out[:, class_index[b.enriched_class]] += w * Zd[:, j]
    return out