Download scripts/result.py from OneScience-Group/WoFS-StormCal: direct link, hf CLI and curl.
- Browser
- Download file 6.94 kB
-
https://huggingface.co/OneScience-Group/WoFS-StormCal/resolve/main/scripts/result.py
- Command line
-
hf download hf://OneScience-Group/WoFS-StormCal/scripts/result.py
-
curl -L -o result.py https://huggingface.co/OneScience-Group/WoFS-StormCal/resolve/main/scripts/result.py
6.94 kB
| """Compute paper verification metrics and plot performance and reliability diagrams.""" | |
| import json | |
| from pathlib import Path | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import yaml | |
| from sklearn.metrics import average_precision_score, roc_auc_score | |
| ROOT = Path(__file__).resolve().parents[1] | |
| def curves(probability, target, threshold_count): | |
| thresholds = np.linspace(0, 1, threshold_count) | |
| pod, pofd, sr, far, csi, bias = [], [], [], [], [], [] | |
| for threshold in thresholds: | |
| forecast = probability >= threshold | |
| event = target == 1 | |
| hits = np.sum(forecast & event); false_alarms = np.sum(forecast & ~event) | |
| misses = np.sum(~forecast & event); negatives = np.sum(~forecast & ~event) | |
| pod.append(hits / max(hits + misses, 1)); pofd.append(false_alarms / max(false_alarms + negatives, 1)) | |
| sr.append(hits / max(hits + false_alarms, 1)); far.append(false_alarms / max(hits + false_alarms, 1)) | |
| csi.append(hits / max(hits + false_alarms + misses, 1)); bias.append((hits + false_alarms) / max(hits + misses, 1)) | |
| return {key: np.asarray(value) for key, value in (("threshold", thresholds), ("pod", pod), ("pofd", pofd), | |
| ("sr", sr), ("far", far), ("csi", csi), ("bias", bias))} | |
| def reliability(probability, target, bins): | |
| edges = np.linspace(0, 1, bins + 1) | |
| index = np.minimum(np.digitize(probability, edges[1:-1]), bins - 1) | |
| records, component = [], 0.0 | |
| for bin_index in range(bins): | |
| mask = index == bin_index | |
| if not mask.any(): | |
| continue | |
| forecast_mean, observed_frequency = float(probability[mask].mean()), float(target[mask].mean()) | |
| component += mask.mean() * (forecast_mean - observed_frequency) ** 2 | |
| records.append({"count": int(mask.sum()), "forecast_probability": forecast_mean, | |
| "observed_frequency": observed_frequency}) | |
| return records, float(component) | |
| def metrics(probability, target, threshold_count, bins): | |
| curve = curves(probability, target, threshold_count) | |
| prevalence = float(target.mean()) | |
| auc = float(roc_auc_score(target, probability)) if 0 < target.sum() < len(target) else 0.5 | |
| aupdc = float(average_precision_score(target, probability)) if target.sum() else 0.0 | |
| positives, negatives = int(target.sum()), int(len(target) - target.sum()) | |
| minimum_aupdc = float(np.mean(np.arange(1, positives + 1) / (np.arange(1, positives + 1) + negatives))) if positives else 0.0 | |
| best = int(np.argmax(curve["csi"])); max_csi = float(curve["csi"][best]) | |
| brier = float(np.mean((probability - target) ** 2)) | |
| reference = prevalence * (1 - prevalence) | |
| records, reliability_component = reliability(probability, target, bins) | |
| return {"samples": len(target), "event_rate": prevalence, "auc": auc, "aupdc": aupdc, | |
| "minimum_aupdc": minimum_aupdc, "naupdc": (aupdc - minimum_aupdc) / max(1 - minimum_aupdc, 1e-12), | |
| "max_csi": max_csi, "ncsi": (max_csi - prevalence) / max(1 - prevalence, 1e-12), | |
| "max_csi_threshold": float(curve["threshold"][best]), "pod": float(curve["pod"][best]), | |
| "pofd": float(curve["pofd"][best]), "sr": float(curve["sr"][best]), "far": float(curve["far"][best]), | |
| "csi": max_csi, "bias": float(curve["bias"][best]), "brier_score": brier, | |
| "brier_skill_score": 1 - brier / reference if reference > 0 else 0.0, | |
| "reliability_component": reliability_component, "reliability_bins": records}, curve | |
| def main(): | |
| config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) | |
| data = np.load(ROOT / config["paths"]["inference_dir"] / "predictions.npz") | |
| if str(data["format_version"]) != config["data"]["format_version"]: | |
| raise ValueError("incompatible prediction format") | |
| probabilities, targets, groups = data["probabilities"], data["targets"], data["lead_group"] | |
| if probabilities.shape != targets.shape or probabilities.shape[1:] != (3,): | |
| raise ValueError("probabilities and targets must have shape [N,3]") | |
| hazards, group_names = data["hazards"].tolist(), data["lead_group_names"].tolist() | |
| report, all_curves = {}, {} | |
| for group_index, group_name in enumerate(group_names): | |
| report[group_name] = {} | |
| mask = groups == group_index | |
| for hazard_index, hazard in enumerate(hazards): | |
| result, curve = metrics(probabilities[mask, hazard_index], targets[mask, hazard_index], | |
| int(config["evaluation"]["threshold_count"]), int(config["evaluation"]["probability_bins"])) | |
| report[group_name][hazard] = result; all_curves[(group_name, hazard)] = curve | |
| numeric = [value for group in report.values() for hazard in group.values() for value in hazard.values() | |
| if isinstance(value, (int, float))] | |
| if not np.isfinite(numeric).all(): | |
| raise FloatingPointError("evaluation contains NaN or Inf") | |
| output = ROOT / config["paths"]["evaluation_dir"] | |
| output.mkdir(parents=True, exist_ok=True) | |
| (output / "metrics.json").write_text(json.dumps({"by_lead_group_and_hazard": report, | |
| "metric_protocol": "Flora et al. AUC, weighted-average-precision AUPDC, minimum-AUPDC NAUPDC, climatology-normalized max CSI, and Brier verification"}, indent=2) + "\n") | |
| colors = {"tornado": "#b3261e", "hail": "#2e7d32", "wind": "#1565c0"} | |
| figure, axes = plt.subplots(1, 2, figsize=(10, 4.5), sharex=True, sharey=True) | |
| for axis, group_name in zip(axes, group_names): | |
| for hazard in hazards: | |
| curve = all_curves[(group_name, hazard)] | |
| axis.plot(curve["sr"], curve["pod"], color=colors[hazard], label=hazard) | |
| axis.set(xlim=(0, 1), ylim=(0, 1), xlabel="Success ratio", ylabel="Probability of detection", title=group_name.replace("_", " ").title()) | |
| axis.grid(alpha=0.25); axis.legend() | |
| figure.tight_layout(); figure.savefig(output / "performance.png", dpi=160); plt.close(figure) | |
| figure, axes = plt.subplots(1, 2, figsize=(10, 4.5), sharex=True, sharey=True) | |
| for axis, group_name in zip(axes, group_names): | |
| axis.plot((0, 1), (0, 1), "k--", linewidth=1, label="perfect") | |
| for hazard in hazards: | |
| bins = report[group_name][hazard]["reliability_bins"] | |
| axis.plot([item["forecast_probability"] for item in bins], [item["observed_frequency"] for item in bins], | |
| marker="o", color=colors[hazard], label=hazard) | |
| axis.set(xlim=(0, 1), ylim=(0, 1), xlabel="Forecast probability", ylabel="Observed frequency", title=group_name.replace("_", " ").title()) | |
| axis.grid(alpha=0.25); axis.legend() | |
| figure.tight_layout(); figure.savefig(output / "reliability.png", dpi=160); plt.close(figure) | |
| print(f"evaluation={output.relative_to(ROOT)} groups={len(group_names)} hazards={len(hazards)}") | |
| if __name__ == "__main__": | |
| main() | |