| """R2 metrics for the Wunder Fund RNN Challenge. |
| |
| The authoritative local metric mirrors ``competition_package.utils``: |
| ``sklearn.metrics.r2_score`` is computed independently per feature and then |
| averaged. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| from sklearn.metrics import r2_score |
|
|
|
|
| def _validate_shapes(y_true: np.ndarray, y_pred: np.ndarray) -> None: |
| if y_true.shape != y_pred.shape: |
| raise ValueError( |
| f"Shape mismatch: y_true {y_true.shape} vs y_pred {y_pred.shape}" |
| ) |
| if y_true.ndim != 2: |
| raise ValueError(f"Expected 2D arrays, got y_true.ndim={y_true.ndim}") |
|
|
|
|
| def compute_r2_score(y_true: np.ndarray, y_pred: np.ndarray) -> float: |
| """Compute mean per-feature R2 using sklearn, matching the starter pack.""" |
| return float(np.mean(list(compute_r2_per_feature(y_true, y_pred).values()))) |
|
|
|
|
| def compute_r2_per_feature( |
| y_true: np.ndarray, |
| y_pred: np.ndarray, |
| feature_names: list[str] | None = None, |
| ) -> dict[str, float]: |
| """Compute sklearn R2 independently for each output feature.""" |
| _validate_shapes(y_true, y_pred) |
| n_features = y_true.shape[1] |
| if feature_names is None: |
| feature_names = [f"feature_{i}" for i in range(n_features)] |
| if len(feature_names) != n_features: |
| raise ValueError("feature_names length must match prediction width") |
|
|
| return { |
| feature_names[i]: float(r2_score(y_true[:, i], y_pred[:, i])) |
| for i in range(n_features) |
| } |
|
|
|
|
| def compute_manual_r2_score( |
| y_true: np.ndarray, |
| y_pred: np.ndarray, |
| epsilon: float = 1e-8, |
| ) -> float: |
| """Legacy epsilon-stabilized R2 helper for diagnostics only.""" |
| _validate_shapes(y_true, y_pred) |
| scores = [] |
| for i in range(y_true.shape[1]): |
| ss_res = np.sum((y_true[:, i] - y_pred[:, i]) ** 2) |
| ss_tot = np.sum((y_true[:, i] - y_true[:, i].mean()) ** 2) |
| scores.append(1 - (ss_res / (ss_tot + epsilon))) |
| return float(np.mean(scores)) |
|
|