Spaces:
Running on Zero
Running on Zero
Download scripts/analysis/check_dry_valleys.py from ageraustine/River_Network: direct link, hf CLI and curl.
- Browser
- Download file 5.65 kB
-
https://huggingface.co/spaces/ageraustine/River_Network/resolve/main/scripts/analysis/check_dry_valleys.py
- Command line
-
hf download hf://spaces/ageraustine/River_Network/scripts/analysis/check_dry_valleys.py
-
curl -L -o check_dry_valleys.py https://huggingface.co/spaces/ageraustine/River_Network/resolve/main/scripts/analysis/check_dry_valleys.py
5.65 kB
| """ | |
| Checks whether any real gauged station in this project's actual data | |
| ever shows true dry-valley (zero or near-zero) discharge -- a direct, | |
| factual data check, built to resolve whether zero-inflation-specific | |
| loss functions (Tweedie, dual-head zero-inflated models, etc.) are | |
| actually relevant here, rather than assuming either way from generic | |
| hydrology advice that wasn't written for this specific river system. | |
| Reuses build_discharge_timeseries directly (the same real, established | |
| loading path train_spatiotemporal_gnn.py itself uses), not a | |
| re-derived reading of the raw hydrometric files -- avoids the exact | |
| mistake the first version of check_routing_lag_distribution.py made by | |
| not reusing the real, existing function. | |
| Usage: | |
| python -m scripts.analysis.check_dry_valleys --data-root datasets | |
| """ | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Check for real dry-valley (zero/near-zero discharge) conditions") | |
| parser.add_argument("--data-root", type=Path, default=Path("datasets")) | |
| parser.add_argument("--start-date", type=str, default="2000-01-01", | |
| help="As wide a real historical window as available -- a dry-valley check " | |
| "benefits from more real history, not just the training window.") | |
| parser.add_argument("--end-date", type=str, default="2026-12-31") | |
| parser.add_argument("--near-zero-threshold-m3s", type=float, default=0.01, | |
| help="A real, small discharge (L/s equivalent: 10 L/s) below which flow is " | |
| "treated as 'near-zero' for reporting purposes -- exact zero is checked " | |
| "separately and explicitly, this threshold is only for the 'near' case.") | |
| args = parser.parse_args() | |
| try: | |
| from src.graph.dynamic_features import build_discharge_timeseries | |
| except ImportError: | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) | |
| from src.graph.dynamic_features import build_discharge_timeseries | |
| date_range = (args.start_date, args.end_date) | |
| rows = [] | |
| for basin in ["eure", "risle"]: | |
| nodes_path = args.data_root / "reach_graph" / f"{basin}_nodes_enriched.csv" | |
| if not nodes_path.exists(): | |
| print(f"{basin}: no real nodes file at {nodes_path}, skipping.") | |
| continue | |
| nodes_df = pd.read_csv(nodes_path) | |
| wide = build_discharge_timeseries(nodes_df, args.data_root / "hydrometric", date_range) | |
| # build_discharge_timeseries returns EVERY real gauged station | |
| # across BOTH basins (confirmed directly -- it never filters by | |
| # the nodes_df passed in; that filtering happens later, inside | |
| # assemble_dynamic_tensor, which this script doesn't call) -- | |
| # restricting to this basin's own real station_codes here is | |
| # required, or every station gets reported twice, once per | |
| # basin, with identical values. A real, caught mistake in an | |
| # earlier version of this exact script. | |
| this_basin_codes = set(nodes_df["station_code"]) & set(wide.columns) | |
| wide = wide[sorted(this_basin_codes)] | |
| print(f"{basin}: real discharge data for {wide.shape[1]} real gauged station(s), " | |
| f"{wide.shape[0]} real date(s) in range") | |
| for station_code in wide.columns: | |
| series = wide[station_code].dropna() | |
| n_real = len(series) | |
| if n_real == 0: | |
| rows.append({"basin": basin, "station_code": station_code, "n_real": 0, | |
| "min_m3s": np.nan, "pct_exact_zero": np.nan, | |
| "pct_near_zero": np.nan, "p01_m3s": np.nan, "p05_m3s": np.nan}) | |
| continue | |
| n_zero = int((series == 0).sum()) | |
| n_near_zero = int((series <= args.near_zero_threshold_m3s).sum()) | |
| rows.append({ | |
| "basin": basin, "station_code": station_code, "n_real": n_real, | |
| "min_m3s": float(series.min()), | |
| "pct_exact_zero": 100.0 * n_zero / n_real, | |
| "pct_near_zero": 100.0 * n_near_zero / n_real, | |
| "p01_m3s": float(series.quantile(0.01)), | |
| "p05_m3s": float(series.quantile(0.05)), | |
| }) | |
| if not rows: | |
| print("No real stations found in either basin -- nothing to report.") | |
| return | |
| result_df = pd.DataFrame(rows).sort_values("min_m3s") | |
| print() | |
| print("=" * 70) | |
| print(f"Real per-station discharge floor (near-zero threshold: {args.near_zero_threshold_m3s} m3/s)") | |
| print("=" * 70) | |
| print(result_df.to_string(index=False)) | |
| n_ever_exact_zero = int((result_df["pct_exact_zero"] > 0).sum()) | |
| n_ever_near_zero = int((result_df["pct_near_zero"] > 0).sum()) | |
| print() | |
| if n_ever_exact_zero == 0: | |
| print("No real station in this dataset ever recorded an exact-zero discharge day -- " | |
| "zero-inflation-specific losses (Tweedie, dual-head zero-inflated models) do not " | |
| "appear to match this project's actual real data, based on real evidence.") | |
| else: | |
| print(f"{n_ever_exact_zero} real station(s) recorded at least one exact-zero discharge day -- " | |
| f"real, direct evidence a zero-inflation-aware approach could be worth investigating " | |
| f"further for those specific stations.") | |
| print(f"{n_ever_near_zero} real station(s) recorded at least one real day at or below " | |
| f"{args.near_zero_threshold_m3s} m3/s.") | |
| if __name__ == "__main__": | |
| main() |