station_validation
station_validation
Module: station_validation.py
Independent validation of bias-corrected precipitation products against weather station observations.
This module compares gridded bias-corrected products (e.g., LSEQM, LSEQMDL) against point-scale daily precipitation measurements from BMKG weather stations. This provides an independent ground-truth validation that is separate from the QA framework (which compares corrected IMERG vs CPC).
The validation workflow: 1. Load station locations and daily precipitation observations 2. Extract gridded product values at station locations (nearest grid cell) 3. Compute per-station verification metrics using compute_pixel_metrics() 4. Multi-threshold categorical verification (WMO/TD-No. 1485 compliant) 5. Aggregate results into summary statistics
Key design decisions: - Reuses compute_pixel_metrics() from metrics.py for consistency - Reuses load_station_locations() from station_density.py (DRY) - Handles BMKG missing data sentinel (8888.0) and empty cells - Station data date format: DD-MM-YYYY - Multi-threshold verification follows WMO/WGNE standard contingency table metrics: POD, FAR, CSI, FBI, ETS, HSS, HK
References
- WMO (2023), Guidelines for the WMO Evaluation of Records of Weather and Climate Extremes, WMO-No. 1317.
- WMO (2009), Recommendations for the Verification and Intercomparison of QPFs and PQPFs from Operational NWP Models (Revision 2), WMO/TD-No. 1485, WWRP 2009-1.
- WMO (2008), Guide to Meteorological Instruments and Methods of Observation, WMO-No. 8.
- WMO (2018), Guide to Climatological Practices, WMO-No. 100.
- Ebert, E. (2007), Methods for verifying satellite precipitation estimates.
- Wilks, D. S. (2011), Statistical Methods in the Atmospheric Sciences, 3rd ed., Academic Press.
Functions
| Name | Description |
|---|---|
| compute_multi_threshold_metrics | Compute WMO-compliant categorical verification at multiple precipitation |
| compute_station_metrics | Compute per-station validation metrics. |
| extract_gridded_at_stations | Extract gridded product timeseries at station locations using nearest |
| extract_metrics_at_stations | Extract pixel-level gridded metrics at station locations. |
| extract_qa_at_stations | Extract pixel-level gridded QA values at station locations. |
| load_station_observations | Load daily precipitation observations from a BMKG station data CSV. |
| merge_station_metadata | Merge per-station metrics with station location metadata, including |
| plot_station_scatter | Generate a scatter plot of observed vs. predicted daily precipitation |
| plot_station_timeseries | Generate a daily precipitation dot plot for a single station over time. |
| run_station_validation | End-to-end station validation pipeline. |
| save_station_validation | Save per-station validation results to CSV. |
| summarize_by_group | Summarize station validation metrics grouped by region or province. |
| summarize_multi_threshold | Summarize multi-threshold metrics across all stations. |
| summarize_station_metrics | Aggregate per-station metrics into summary statistics. |
compute_multi_threshold_metrics
station_validation.compute_multi_threshold_metrics(
obs_df,
gridded_df,
thresholds=None,
)Compute WMO-compliant categorical verification at multiple precipitation thresholds for each station.
For each station and each threshold, builds the 2x2 contingency table and derives POD, FAR, CSI, FBI, ETS, HSS, and HK following WMO/TD-No. 1485 (WWRP 2009-1).
Thresholds where a station has fewer than MIN_EVENTS_FOR_VERIFICATION observed events are flagged with NaN (per WMO/TD-1485 guidance).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| obs_df | pandas.DataFrame | Station observations (DatetimeIndex, WMO station ID columns). | required |
| gridded_df | pandas.DataFrame | Gridded values at stations (DatetimeIndex, WMO station ID columns). | required |
| thresholds | sequence of float | Precipitation thresholds in mm/day. Default: WMO_THRESHOLDS (1, 5, 10, 20, 50, 100, 150). |
None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Per-station multi-threshold metrics. Rows = station WMO IDs, Columns = {metric}_{threshold}mm (e.g., pod_20mm, csi_50mm, ets_100mm). Also includes n_valid_days. |
compute_station_metrics
station_validation.compute_station_metrics(obs_df, gridded_df, threshold=1.0)Compute per-station validation metrics.
For each station column present in both DataFrames, extracts the overlapping time period, aligns, and calls compute_pixel_metrics() from metrics.py.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| obs_df | pandas.DataFrame | Station observations (DatetimeIndex, station ID columns). | required |
| gridded_df | pandas.DataFrame | Gridded values at stations (DatetimeIndex, station ID columns). | required |
| threshold | float | Wet-day threshold in mm/day (default 1.0). | 1.0 |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Per-station metrics table. Rows = stations (WMO IDs), Columns = 31 metric names from METRIC_NAMES. Includes additional columns: ‘n_valid_days’, ‘station_name’, ‘lon’, ‘lat’ (if station metadata is later merged). |
extract_gridded_at_stations
station_validation.extract_gridded_at_stations(gridded_da, station_df)Extract gridded product timeseries at station locations using nearest grid cell.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| gridded_da | xarray.DataArray | Gridded precipitation product with dims (time, lat, lon). | required |
| station_df | pandas.DataFrame | Station locations with ‘Lon’, ‘Lat’, ‘ID_WMO’ columns (from load_station_locations). | required |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | DataFrame with DatetimeIndex and WMO station ID columns containing gridded product values at nearest grid cell. |
extract_metrics_at_stations
station_validation.extract_metrics_at_stations(
metrics_file,
station_df,
metric_names=None,
)Extract pixel-level gridded metrics at station locations.
Opens a metrics NetCDF file (output of run_metrics_pipeline), extracts variable values at each station’s nearest grid cell, and returns a DataFrame.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| metrics_file | str | Path to a metrics NetCDF file (2-D variables: lat × lon). | required |
| station_df | pandas.DataFrame | Station locations with ‘ID_WMO’, ‘Lon’, ‘Lat’ columns. | required |
| metric_names | list of str | Variables to extract. If None, extracts all data variables. | None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Index = WMO station IDs, columns = metric names. |
extract_qa_at_stations
station_validation.extract_qa_at_stations(qa_file, station_df, var_names=None)Extract pixel-level gridded QA values at station locations.
Opens a QA NetCDF file (output of run_qa_pipeline), extracts variable values at each station’s nearest grid cell. Handles both numeric (score) and categorical (category) variables.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| qa_file | str | Path to a QA NetCDF file (2-D variables: lat × lon). | required |
| station_df | pandas.DataFrame | Station locations with ‘ID_WMO’, ‘Lon’, ‘Lat’ columns. | required |
| var_names | list of str | Variables to extract. If None, extracts all data variables. | None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Index = WMO station IDs, columns = QA variable names. |
load_station_observations
station_validation.load_station_observations(
station_data_file,
station_location_file=None,
)Load daily precipitation observations from a BMKG station data CSV.
Handles: - Date parsing (DD-MM-YYYY format) - Missing value replacement (8888.0 -> NaN, empty -> NaN) - Optional filtering to only stations present in the location file
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| station_data_file | str | Path to CSV with columns: ID, Date, JD, then WMO station ID columns. | required |
| station_location_file | str | If provided, filters to only stations found in the location CSV. | None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | DataFrame with DatetimeIndex and WMO station ID columns (as int) containing daily precipitation in mm. Shape: (n_days, n_stations). |
merge_station_metadata
station_validation.merge_station_metadata(metrics_df, station_df)Merge per-station metrics with station location metadata, including optional Region and Province columns.
If the station location CSV contains ‘Region’ and/or ‘Province’ columns, they are included. If ‘Province’ exists but ‘Region’ does not, region is inferred from config.REGION_MAPPING.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| metrics_df | pandas.DataFrame | Per-station metrics (index = WMO station IDs). | required |
| station_df | pandas.DataFrame | Station metadata from load_station_locations(). |
required |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Metrics with metadata columns prepended: Station, Lon, Lat, Elevation, and optionally Region, Province. |
plot_station_scatter
station_validation.plot_station_scatter(
obs_df,
gridded_dict,
station_id,
station_df=None,
threshold=1.0,
figsize=(12, 5),
)Generate a scatter plot of observed vs. predicted daily precipitation for a single station, with one panel per correction method.
This is an on-demand function - the user specifies which station to plot, avoiding mass generation of figures.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| obs_df | pandas.DataFrame | Station observations (DatetimeIndex, WMO station ID columns). | required |
| gridded_dict | dict of {str: pandas.DataFrame} | Extracted gridded values keyed by method name (e.g., {‘LS’: df, ‘LSEQM’: df, ‘LSEQMDL’: df}). | required |
| station_id | int | WMO station ID to plot. | required |
| station_df | pandas.DataFrame | Station metadata for title annotation. | None |
| threshold | float | Wet-day threshold line (default 1.0 mm). | 1.0 |
| figsize | tuple | Figure size (width, height) in inches. | (12, 5) |
Returns
| Name | Type | Description |
|---|---|---|
| matplotlib.figure.Figure | The generated figure (also displayed via plt.show). |
plot_station_timeseries
station_validation.plot_station_timeseries(
obs_df,
gridded_dict,
station_id,
station_df=None,
thresholds=(5, 10, 20, 50, 100),
figsize=(16, 6),
)Generate a daily precipitation dot plot for a single station over time.
Each dataset (observed + gridded products) is plotted as semi-transparent dots along the time axis. Horizontal dashed lines mark WMO rainfall intensity thresholds for visual context.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| obs_df | pandas.DataFrame | Station observations (DatetimeIndex, WMO station ID columns). | required |
| gridded_dict | dict of {str: pandas.DataFrame} | Extracted gridded values keyed by method name (e.g., {‘LS’: df, ‘LSEQM’: df, ‘LSEQMDL’: df}). | required |
| station_id | int | WMO station ID to plot. | required |
| station_df | pandas.DataFrame | Station metadata for title annotation. | None |
| thresholds | tuple of float | Precipitation thresholds (mm/day) for horizontal reference lines. Default: (5, 10, 20, 50, 100). | (5, 10, 20, 50, 100) |
| figsize | tuple | Figure size (width, height) in inches. | (16, 6) |
Returns
| Name | Type | Description |
|---|---|---|
| matplotlib.figure.Figure | The generated figure (also displayed via plt.show). |
run_station_validation
station_validation.run_station_validation(
gridded_da,
station_location_file,
station_data_file,
threshold=1.0,
)End-to-end station validation pipeline.
Chains: load locations -> load observations -> extract gridded at stations -> compute metrics -> summarize.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| gridded_da | xarray.DataArray | Bias-corrected gridded precipitation product with dims (time, lat, lon). | required |
| station_location_file | str | Path to station location CSV. | required |
| station_data_file | str | Path to station observation data CSV. | required |
| threshold | float | Wet-day threshold in mm/day (default 1.0). | 1.0 |
Returns
| Name | Type | Description |
|---|---|---|
| tuple of (pandas.DataFrame, pandas.DataFrame, pandas.DataFrame) | (per_station_metrics, summary_stats, station_locations) |
save_station_validation
station_validation.save_station_validation(
metrics_df,
station_df,
output_file,
method_name='LSEQMDL',
)Save per-station validation results to CSV.
Merges metrics with station metadata (name, lon, lat, elevation) and saves as CSV.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| metrics_df | pandas.DataFrame | Per-station metrics (index = WMO station IDs). | required |
| station_df | pandas.DataFrame | Station metadata with ID_WMO, Station, Lon, Lat, Elevation. | required |
| output_file | str | Output file path (.csv). | required |
| method_name | str | Name of correction method for metadata. | 'LSEQMDL' |
Returns
| Name | Type | Description |
|---|---|---|
| str | Path to saved file. |
summarize_by_group
station_validation.summarize_by_group(metrics_df, group_col, key_metrics=None)Summarize station validation metrics grouped by region or province.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| metrics_df | pandas.DataFrame | Per-station metrics with a group column (e.g., ‘Region’ or ‘Province’) already merged via merge_station_metadata(). |
required |
| group_col | str | Column name to group by (e.g., ‘Region’, ‘Province’). | required |
| key_metrics | list of str | Metric column names to summarize. Default: a representative set of continuous and categorical metrics. | None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Summary table: index = group name, columns = metric statistics (count, median, mean, std). Groups are sorted alphabetically. |
summarize_multi_threshold
station_validation.summarize_multi_threshold(mt_df, thresholds=None)Summarize multi-threshold metrics across all stations.
Produces a compact table: rows = thresholds, columns = metric medians (and IQR). This is the primary output for comparing how skill degrades with increasing precipitation intensity.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| mt_df | pandas.DataFrame | Per-station multi-threshold metrics from compute_multi_threshold_metrics(). |
required |
| thresholds | sequence of float | Thresholds to include. Default: WMO_THRESHOLDS. |
None |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Summary table with index = threshold (mm), columns = metric statistics (e.g., pod_median, csi_median, ets_p25). |
summarize_station_metrics
station_validation.summarize_station_metrics(metrics_df)Aggregate per-station metrics into summary statistics.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| metrics_df | pandas.DataFrame | Per-station metrics from compute_station_metrics(). |
required |
Returns
| Name | Type | Description |
|---|---|---|
| pandas.DataFrame | Summary statistics (count, mean, median, std, min, max) for each metric across all stations. Rows = statistic names, Columns = metric names. |