Spaces:
Running on Zero
Running on Zero
Download scripts/analysis/check_discharge_quantile_distribution.py from ageraustine/River_Network: direct link, hf CLI and curl.
- Browser
- Download file 4.47 kB
-
https://huggingface.co/spaces/ageraustine/River_Network/resolve/main/scripts/analysis/check_discharge_quantile_distribution.py
- Command line
-
hf download hf://spaces/ageraustine/River_Network/scripts/analysis/check_discharge_quantile_distribution.py
-
curl -L -o check_discharge_quantile_distribution.py https://huggingface.co/spaces/ageraustine/River_Network/resolve/main/scripts/analysis/check_discharge_quantile_distribution.py
4.47 kB
| """ | |
| Computes the real empirical quantile distribution of discharge for | |
| every real gauged station -- built to answer a real, specific question | |
| before deciding how to re-weight or restructure the model's predicted | |
| quantiles: where do real near-zero/low-flow, normal-flow, and peak | |
| events actually fall for each real station, rather than assuming the | |
| current quantile set [0.5, 0.9, 0.95, 0.99] is well-matched to the | |
| real data's actual shape. | |
| Reuses build_discharge_timeseries directly (the same real, established | |
| loading path train_spatiotemporal_gnn.py itself uses) and the same | |
| real per-basin filtering fix check_dry_valleys.py needed -- | |
| build_discharge_timeseries returns every real gauged station across | |
| BOTH basins regardless of which nodes_df is passed in, confirmed | |
| directly in that earlier diagnostic. | |
| Usage: | |
| python -m scripts.analysis.check_discharge_quantile_distribution --data-root datasets | |
| """ | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| QUANTILE_LEVELS = [0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99] | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Check the real empirical discharge quantile distribution per station") | |
| 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.") | |
| parser.add_argument("--end-date", type=str, default="2026-12-31") | |
| 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) | |
| # Real per-basin filtering, same fix check_dry_valleys.py needed: | |
| # build_discharge_timeseries returns every real gauged station | |
| # across BOTH basins, not filtered by the nodes_df passed in. | |
| this_basin_codes = set(nodes_df["station_code"]) & set(wide.columns) | |
| wide = wide[sorted(this_basin_codes)] | |
| for station_code in wide.columns: | |
| series = wide[station_code].dropna() | |
| n_real = len(series) | |
| if n_real == 0: | |
| continue | |
| real_quantiles = series.quantile(QUANTILE_LEVELS) | |
| row = {"basin": basin, "station_code": station_code, "n_real": n_real} | |
| for q_level, q_value in zip(QUANTILE_LEVELS, real_quantiles): | |
| row[f"Q{int(q_level*100):02d}"] = float(q_value) | |
| rows.append(row) | |
| if not rows: | |
| print("No real stations found in either basin -- nothing to report.") | |
| return | |
| result_df = pd.DataFrame(rows) | |
| print("=" * 100) | |
| print("Real empirical discharge quantile distribution, per real gauged station (m3/s)") | |
| print("=" * 100) | |
| print(result_df.to_string(index=False)) | |
| print() | |
| print("=" * 100) | |
| print("Real skew/spread ratios -- how far the tails sit from the real median, per station") | |
| print("=" * 100) | |
| for _, row in result_df.iterrows(): | |
| q01, q50, q99 = row["Q01"], row["Q50"], row["Q99"] | |
| low_ratio = q50 / q01 if q01 > 0 else float("inf") | |
| high_ratio = q99 / q50 if q50 > 0 else float("inf") | |
| print(f"{row['station_code']} ({row['basin']}): Q50/Q01={low_ratio:>8.1f}x, " | |
| f"Q99/Q50={high_ratio:>8.1f}x (real Q01={q01:.2f}, Q50={q50:.2f}, Q99={q99:.2f})") | |
| print() | |
| print("=" * 100) | |
| print("Where does this project's CURRENT model quantile set [0.5, 0.9, 0.95, 0.99] actually land?") | |
| print("=" * 100) | |
| print("No real LOW-tail quantile exists in the current model output at all -- Q50 is the lowest " | |
| "quantile predicted, meaning there is no dedicated, explicit low-flow signal in the model's " | |
| "own output structure, only whatever Q50 happens to learn implicitly.") | |
| if __name__ == "__main__": | |
| main() | |