3 · CONUS404 Climate — Spatial Patterns & Water-Balance Trends

This notebook builds a long-term climate and water-balance record for the three South-Texas HUC-8 watersheds from CONUS404 — a 4-km hourly reanalysis of the conterminous US (NCAR/USGS, WRF model), here used at its monthly aggregation. CONUS404 gives a physically consistent, closed land-surface water budget on a single grid that covers all three watersheds — including the Mexican portion of the Lower Rio Grande that US-only stream/water-quality products miss.

The pipeline is:

  1. read the watershed boundaries from notebook 1;
  2. open the CONUS404 monthly Zarr store (anonymous, cloud-hosted) and clip it to our area as a gridded datacube (saved to zarr — the storage format for raster data);
  3. summarize per watershed: water-year totals/means and a simple water balance (P − ET − Q), with long-term trends (Mann–Kendall + Sen’s slope);
  4. summarize per grid cell: climatology and per-cell trends, to see spatial patterns and how they change over time;
  5. map and chart the results interactively.

Storage convention. Gridded data (datacubes) are saved as zarr; tabular summaries are saved as parquet (+ CSV). The ~80 MB raw monthly cube is git-ignored and regenerated from the cloud; the small derived products (climatology/trend grids, per-watershed tables) are committed.

Step 1 — Imports and setup

Show code
import geopandas as gpd
import pyproj
import xarray as xr
import rioxarray  # noqa: F401  (registers the .rio accessor for reprojection)

import holoviews as hv
import hvplot.pandas  # noqa: F401  (registers .hvplot on DataFrames)
import hvplot.xarray  # noqa: F401  (registers .hvplot on DataArrays)
import geoviews as gv

from _helpers import (
    init_session,
    show,
    save_datacube,
    categorical_colors,
    make_legend_clickable,
    conus404_monthly_grid,
    zonal_by_huc8,
    water_year,
    mk_sen_trend,
    pixel_trend,
)

gv.extension("bokeh")
S = init_session()

# Stable watershed order + consistent data colors across all figures.
WATERSHED_ORDER = ["South Laguna Madre", "Los Olmos", "Lower Rio Grande"]
WATERSHED_COLORS = categorical_colors(WATERSHED_ORDER)
USGS API key loaded.

Step 2 — Watersheds and the CONUS404 variables

We reuse the HUC-8 boundaries saved by notebook 1. The table lists the 11 CONUS404 variables we aggregate — the fluxes that make up the water balance, the storage states, and the temperature/humidity forcing. Monthly flux variables are monthly totals (mm).

Show code
boundaries_path = S.data_dir / "spatial" / "huc8_watersheds.parquet"
if not boundaries_path.exists():
    raise FileNotFoundError(
        f"{boundaries_path} not found — run notebook 1 (1_usgs_hydrofabric) first."
    )
watersheds_gdf = gpd.read_parquet(boundaries_path)

import pandas as pd  # noqa: E402  (kept local to this descriptive cell)

conus404_variables = pd.DataFrame(
    [
        ("PREC_ACC_NC", "Precipitation", "mm/month", "flux"),
        ("ACETLSM", "Total evapotranspiration", "mm/month", "flux"),
        ("ACRUNSB", "Surface runoff", "mm/month", "flux"),
        ("ACRUNSF", "Subsurface runoff", "mm/month", "flux"),
        ("RECH", "Water-table recharge", "mm/month", "flux"),
        ("SMOIS", "Soil moisture (surface layer)", "m³/m³", "storage"),
        ("SNOW", "Snow water equivalent", "mm", "storage"),
        ("CANWAT", "Canopy water", "mm", "storage"),
        ("T2", "Air temperature (2 m)", "K", "forcing"),
        ("TD2", "Dewpoint (2 m)", "K", "forcing"),
        ("Q2", "Water-vapor mixing ratio (2 m)", "kg/kg", "forcing"),
    ],
    columns=["variable", "description", "units", "role"],
)
show(conus404_variables, height=320)
variable description units role
0 PREC_ACC_NC Precipitation mm/month flux
1 ACETLSM Total evapotranspiration mm/month flux
2 ACRUNSB Surface runoff mm/month flux
3 ACRUNSF Subsurface runoff mm/month flux
4 RECH Water-table recharge mm/month flux
5 SMOIS Soil moisture (surface layer) m³/m³ storage
6 SNOW Snow water equivalent mm storage
7 CANWAT Canopy water mm storage
8 T2 Air temperature (2 m) K forcing
9 TD2 Dewpoint (2 m) K forcing
10 Q2 Water-vapor mixing ratio (2 m) kg/kg forcing

Step 3 — Monthly gridded datacube

conus404_monthly_grid opens the monthly Zarr store, reprojects the watersheds to the model grid, clips the 11 variables to our bounding box, and saves the result as a zarr datacube. It loads the saved cube if it already exists (fast); otherwise it fetches from the cloud (slow, a few minutes) and saves. The cube has dimensions (time, y, x) over 540 months (water years 1980–2024).

Show code
grid_path = S.data_dir / "conus404" / "conus404_monthly_grid.zarr"
grid = conus404_monthly_grid(watersheds_gdf, grid_path)
grid = grid.assign_coords(water_year=("time", water_year(grid["time"].values)))
grid_crs = pyproj.CRS.from_cf(grid["crs"].attrs)
print(
    f"datacube {dict(grid.sizes)} | {str(grid.time.values[0])[:7]}{str(grid.time.values[-1])[:7]} "
    f"| grid CRS: {grid_crs.name}"
)
fetching CONUS404 monthly cube (11 vars) from OSN…
saved datacube {'time': 540, 'y': 43, 'x': 78} → conus404_monthly_grid.zarr
datacube {'time': 540, 'y': 43, 'x': 78} | 1979-10 → 2024-09 | grid CRS: unknown
/home/runner/work/Thornforest/Thornforest/.pixi/envs/default/lib/python3.13/site-packages/zarr/api/asynchronous.py:231: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(

Step 4 — Per-watershed water-year balance

We take an area-weighted zonal mean of the cube over each watershed (xvec + exactextract, using fractional cell coverage), then aggregate to the water year (Oct 1 – Sep 30, labeled by the ending year): fluxes are summed to annual totals (mm/yr), storage/forcing are averaged. Total runoff Q = surface + subsurface; the simple balance P − ET − Q has a residual equal to the change in stored water (soil + recharge).

Show code
FLUX_SUM = ["PREC_ACC_NC", "ACETLSM", "ACRUNSB", "ACRUNSF", "RECH"]
STATE_MEAN = ["SMOIS", "SNOW", "CANWAT", "T2", "TD2", "Q2"]

monthly = zonal_by_huc8(grid, watersheds_gdf)
monthly["water_year"] = water_year(monthly["date"])

grouped = monthly.groupby(["huc8", "name", "water_year"])
wy = grouped[FLUX_SUM].sum().join(grouped[STATE_MEAN].mean())
wy["n_months"] = grouped.size()
wy = wy.reset_index()

complete = wy["n_months"] == 12  # keep only full water years (all do, but guard anyway)
print(f"dropping {int((~complete).sum())} incomplete water year(s)")
wy = wy[complete].copy()

wy = wy.rename(
    columns={
        "PREC_ACC_NC": "precip_mm", "ACETLSM": "et_mm",
        "ACRUNSB": "surf_runoff_mm", "ACRUNSF": "subsurf_runoff_mm", "RECH": "recharge_mm",
        "SMOIS": "soil_moisture_m3m3", "SNOW": "swe_mm", "CANWAT": "canopy_mm", "Q2": "q2_kgkg",
    }
)
wy["runoff_mm"] = wy["surf_runoff_mm"] + wy["subsurf_runoff_mm"]
wy["t2_degc"] = wy["T2"] - 273.15
wy["td2_degc"] = wy["TD2"] - 273.15
wy["balance_mm"] = wy["precip_mm"] - wy["et_mm"] - wy["runoff_mm"]
wy = wy.drop(columns=["T2", "TD2"]).sort_values(["name", "water_year"]).reset_index(drop=True)

wy = wy[[
    "huc8", "name", "water_year", "precip_mm", "et_mm", "runoff_mm",
    "surf_runoff_mm", "subsurf_runoff_mm", "recharge_mm", "balance_mm",
    "soil_moisture_m3m3", "swe_mm", "canopy_mm", "t2_degc", "td2_degc", "q2_kgkg", "n_months",
]]

wy_path = S.data_dir / "conus404" / "conus404_wateryear_by_huc8.parquet"
wy_path.parent.mkdir(parents=True, exist_ok=True)
wy.to_parquet(wy_path)
wy.to_csv(wy_path.with_suffix(".csv"), index=False)
print(f"saved {len(wy)} water-year rows → {wy_path.name} (+ .csv)")
show(wy.round(3))
/home/runner/work/Thornforest/Thornforest/.pixi/envs/default/lib/python3.13/site-packages/osgeo/osr.py:424: FutureWarning: Neither osr.UseExceptions() nor osr.DontUseExceptions() has been explicitly called. In GDAL 4.0, exceptions will be enabled by default.
  warnings.warn(
/home/runner/work/Thornforest/Thornforest/.pixi/envs/default/lib/python3.13/site-packages/exactextract/exact_extract.py:346: RuntimeWarning: Spatial reference system of input features does not exactly match raster.
  warnings.warn(
dropping 0 incomplete water year(s)
saved 135 water-year rows → conus404_wateryear_by_huc8.parquet (+ .csv)
huc8 name water_year precip_mm et_mm runoff_mm surf_runoff_mm subsurf_runoff_mm recharge_mm balance_mm soil_moisture_m3m3 swe_mm canopy_mm t2_degc td2_degc q2_kgkg n_months
0 13090001 Los Olmos 1980 437.447 429.863 -1.348 -7.106 5.759 55.496 8.932 0.168 0.000 0.006 23.374 14.891 0.012 12
1 13090001 Los Olmos 1981 1004.354 804.585 144.113 138.314 5.799 107.373 55.656 0.236 0.000 0.015 22.032 16.447 0.013 12
2 13090001 Los Olmos 1982 474.413 554.093 33.892 32.039 1.852 99.504 -113.572 0.192 0.000 0.008 22.964 15.990 0.013 12
3 13090001 Los Olmos 1983 721.314 632.252 26.308 24.943 1.365 65.131 62.755 0.205 0.000 0.011 22.413 15.569 0.012 12
4 13090001 Los Olmos 1984 441.700 513.995 16.650 15.776 0.874 61.185 -88.945 0.187 0.000 0.008 22.477 14.196 0.011 12
5 13090001 Los Olmos 1985 622.097 594.872 15.085 13.871 1.214 47.621 12.140 0.199 0.000 0.010 22.917 16.359 0.013 12
6 13090001 Los Olmos 1986 533.685 565.369 20.051 19.257 0.795 56.885 -51.735 0.192 0.000 0.007 23.481 15.997 0.012 12
7 13090001 Los Olmos 1987 969.992 729.565 100.303 98.375 1.928 79.271 140.124 0.227 0.000 0.015 22.091 15.829 0.012 12
8 13090001 Los Olmos 1988 540.147 562.546 16.877 15.897 0.980 76.691 -39.277 0.196 0.000 0.008 22.621 15.227 0.012 12
9 13090001 Los Olmos 1989 408.162 532.824 -3.983 -4.763 0.780 47.093 -120.679 0.183 0.000 0.007 23.393 15.704 0.012 12
10 13090001 Los Olmos 1990 510.336 497.276 -9.838 -10.652 0.813 33.269 22.898 0.183 0.000 0.008 23.243 15.537 0.012 12
11 13090001 Los Olmos 1991 544.476 556.849 1.593 0.674 0.918 32.632 -13.965 0.181 0.000 0.007 23.758 15.620 0.012 12
12 13090001 Los Olmos 1992 658.190 646.271 42.337 41.319 1.018 46.218 -30.418 0.209 0.000 0.012 22.788 16.303 0.013 12
13 13090001 Los Olmos 1993 554.930 549.904 20.133 19.086 1.047 47.126 -15.107 0.191 0.000 0.011 22.993 15.943 0.012 12
14 13090001 Los Olmos 1994 448.228 455.685 -4.429 -5.490 1.060 36.753 -3.028 0.176 0.000 0.007 23.102 15.370 0.012 12
15 13090001 Los Olmos 1995 497.865 526.217 -0.084 -0.991 0.907 28.652 -28.268 0.184 0.000 0.007 23.766 16.022 0.013 12
16 13090001 Los Olmos 1996 367.871 368.548 -8.019 -8.781 0.762 25.335 7.341 0.165 0.000 0.006 22.952 14.358 0.012 12
17 13090001 Los Olmos 1997 537.460 547.259 13.459 12.677 0.782 30.224 -23.258 0.191 0.000 0.009 22.707 15.481 0.012 12
18 13090001 Los Olmos 1998 476.216 459.967 19.194 18.410 0.784 39.784 -2.945 0.183 0.000 0.009 23.275 14.974 0.012 12
19 13090001 Los Olmos 1999 458.337 464.770 -3.910 -4.612 0.703 27.481 -2.524 0.177 0.000 0.007 23.823 16.040 0.013 12
20 13090001 Los Olmos 2000 365.504 434.740 -8.898 -9.615 0.717 21.866 -60.338 0.168 0.000 0.005 23.690 15.208 0.012 12
21 13090001 Los Olmos 2001 658.608 553.926 19.923 18.896 1.027 24.519 84.759 0.199 0.000 0.011 22.324 15.579 0.012 12
22 13090001 Los Olmos 2002 495.020 503.183 23.930 22.909 1.021 36.874 -32.093 0.180 0.000 0.007 22.993 14.801 0.012 12
23 13090001 Los Olmos 2003 852.201 665.520 136.290 133.301 2.989 98.379 50.391 0.219 0.000 0.012 22.614 16.213 0.013 12
24 13090001 Los Olmos 2004 664.381 684.027 71.753 69.791 1.961 85.514 -91.398 0.211 0.000 0.011 22.820 16.179 0.013 12
25 13090001 Los Olmos 2005 427.000 478.764 -0.234 -0.910 0.675 51.142 -51.529 0.180 0.001 0.011 23.577 15.736 0.012 12
26 13090001 Los Olmos 2006 445.878 427.096 -8.454 -9.096 0.642 34.924 27.236 0.165 0.000 0.006 23.723 14.959 0.012 12
27 13090001 Los Olmos 2007 962.420 751.422 148.796 143.346 5.450 84.503 62.202 0.225 0.000 0.013 22.448 16.170 0.013 12
28 13090001 Los Olmos 2008 684.914 568.222 93.016 89.552 3.463 102.966 23.676 0.185 0.000 0.008 23.173 15.201 0.012 12
29 13090001 Los Olmos 2009 310.027 438.430 14.858 14.078 0.779 88.566 -143.261 0.170 0.000 0.005 23.620 14.904 0.012 12
30 13090001 Los Olmos 2010 804.032 649.818 58.664 56.800 1.864 65.829 95.549 0.212 0.000 0.011 22.262 15.450 0.012 12
31 13090001 Los Olmos 2011 171.638 328.806 -0.280 -0.681 0.402 62.356 -156.889 0.145 0.000 0.003 23.571 14.057 0.012 12
32 13090001 Los Olmos 2012 471.773 464.948 -7.463 -8.533 1.070 35.601 14.288 0.173 0.000 0.007 23.682 15.460 0.012 12
33 13090001 Los Olmos 2013 578.446 482.433 -3.626 -4.596 0.970 31.183 99.639 0.176 0.000 0.009 23.388 15.133 0.012 12
34 13090001 Los Olmos 2014 532.667 545.662 40.094 38.853 1.241 51.783 -53.089 0.198 0.000 0.010 22.265 15.061 0.012 12
35 13090001 Los Olmos 2015 973.406 764.289 231.200 220.332 10.868 147.994 -22.083 0.235 0.000 0.013 22.153 16.584 0.013 12
36 13090001 Los Olmos 2016 580.589 615.371 26.357 24.488 1.868 99.078 -61.138 0.196 0.000 0.008 23.745 16.529 0.013 12
37 13090001 Los Olmos 2017 556.786 522.124 8.604 7.281 1.323 62.153 26.059 0.182 0.000 0.007 24.486 16.486 0.013 12
38 13090001 Los Olmos 2018 592.534 535.306 8.879 7.692 1.188 48.553 48.349 0.189 0.001 0.016 23.532 15.980 0.013 12
39 13090001 Los Olmos 2019 525.984 596.495 27.372 26.458 0.914 51.423 -97.883 0.205 0.000 0.010 22.969 16.163 0.013 12
40 13090001 Los Olmos 2020 716.119 608.484 75.931 70.756 5.175 67.250 31.704 0.187 0.000 0.008 23.962 16.300 0.013 12
41 13090001 Los Olmos 2021 497.319 523.771 17.429 16.110 1.318 63.880 -43.880 0.173 0.000 0.006 23.210 15.212 0.012 12
42 13090001 Los Olmos 2022 403.106 464.259 -0.574 -1.417 0.842 51.004 -60.578 0.179 0.000 0.005 23.714 15.353 0.012 12
43 13090001 Los Olmos 2023 475.864 460.069 7.311 6.230 1.082 39.790 8.484 0.172 0.000 0.010 24.064 15.634 0.012 12
44 13090001 Los Olmos 2024 551.537 514.228 16.531 15.713 0.818 37.612 20.778 0.200 0.000 0.011 24.016 16.587 0.013 12
45 13090002 Lower Rio Grande 1980 535.514 424.839 13.066 -46.110 59.176 137.246 97.608 0.278 0.000 0.004 23.580 17.943 0.014 12
46 13090002 Lower Rio Grande 1981 973.233 739.429 82.401 6.270 76.131 131.423 151.403 0.329 0.000 0.008 22.696 18.552 0.014 12
47 13090002 Lower Rio Grande 1982 434.578 518.475 -22.827 -43.175 20.348 88.975 -61.070 0.294 0.000 0.004 23.402 18.703 0.014 12
48 13090002 Lower Rio Grande 1983 793.900 615.714 69.578 32.010 37.567 93.682 108.608 0.305 0.000 0.006 22.974 17.924 0.014 12
49 13090002 Lower Rio Grande 1984 692.710 558.325 52.233 15.542 36.691 109.788 82.153 0.300 0.000 0.006 22.788 17.313 0.013 12
50 13090002 Lower Rio Grande 1985 606.113 573.714 9.050 -24.488 33.538 79.376 23.349 0.302 0.000 0.006 23.378 18.762 0.014 12
51 13090002 Lower Rio Grande 1986 405.219 468.051 -27.207 -47.484 20.277 57.984 -35.626 0.285 0.000 0.004 23.912 18.533 0.014 12
52 13090002 Lower Rio Grande 1987 1009.105 680.891 139.595 88.455 51.140 104.217 188.619 0.322 0.000 0.008 22.685 18.111 0.014 12
53 13090002 Lower Rio Grande 1988 543.329 537.274 1.293 -25.748 27.041 86.903 4.762 0.299 0.000 0.005 22.995 17.859 0.014 12
54 13090002 Lower Rio Grande 1989 569.476 528.424 19.341 -12.403 31.744 77.860 21.712 0.291 0.000 0.004 23.816 18.451 0.014 12
55 13090002 Lower Rio Grande 1990 640.095 558.671 18.490 -17.781 36.271 59.829 62.934 0.300 0.000 0.004 23.541 18.417 0.014 12
56 13090002 Lower Rio Grande 1991 650.458 557.851 30.398 -1.150 31.548 62.549 62.208 0.292 0.000 0.004 24.140 18.589 0.014 12
57 13090002 Lower Rio Grande 1992 776.619 611.462 91.827 35.027 56.799 112.631 73.330 0.311 0.000 0.006 23.221 18.526 0.014 12
58 13090002 Lower Rio Grande 1993 949.597 667.188 176.167 79.278 96.889 189.769 106.242 0.316 0.000 0.007 23.401 18.763 0.014 12
59 13090002 Lower Rio Grande 1994 462.105 514.093 -40.570 -68.559 27.989 77.795 -11.418 0.296 0.000 0.004 23.383 18.155 0.014 12
60 13090002 Lower Rio Grande 1995 660.017 572.580 16.705 -8.410 25.115 67.916 70.732 0.295 0.000 0.005 24.132 18.841 0.014 12
61 13090002 Lower Rio Grande 1996 527.883 457.625 15.164 -14.242 29.406 81.653 55.094 0.288 0.000 0.004 23.133 17.659 0.014 12
62 13090002 Lower Rio Grande 1997 750.108 606.045 82.634 41.209 41.425 107.458 61.429 0.308 0.000 0.006 23.229 18.423 0.014 12
63 13090002 Lower Rio Grande 1998 634.030 511.521 51.234 11.223 40.011 117.211 71.275 0.294 0.000 0.005 23.537 18.061 0.014 12
64 13090002 Lower Rio Grande 1999 623.518 553.074 24.261 -10.289 34.549 86.896 46.183 0.294 0.000 0.004 24.164 18.875 0.014 12
65 13090002 Lower Rio Grande 2000 479.484 468.786 -8.126 -34.837 26.711 58.425 18.825 0.283 0.000 0.003 24.039 18.273 0.014 12
66 13090002 Lower Rio Grande 2001 450.785 450.634 -26.965 -44.020 17.055 40.993 27.116 0.285 0.000 0.005 23.196 17.896 0.014 12
67 13090002 Lower Rio Grande 2002 519.597 399.424 6.051 -12.751 18.802 46.825 114.122 0.276 0.000 0.004 23.552 17.659 0.014 12
68 13090002 Lower Rio Grande 2003 890.627 604.843 188.745 138.324 50.421 158.894 97.040 0.310 0.000 0.006 23.207 18.441 0.014 12
69 13090002 Lower Rio Grande 2004 735.398 656.136 58.266 -2.365 60.631 131.479 20.997 0.311 0.000 0.005 23.455 18.467 0.014 12
70 13090002 Lower Rio Grande 2005 517.202 497.627 0.042 -23.610 23.652 57.545 19.532 0.289 0.001 0.004 24.009 18.433 0.014 12
71 13090002 Lower Rio Grande 2006 461.283 453.814 -19.691 -39.074 19.383 56.105 27.160 0.280 0.000 0.003 24.049 18.066 0.014 12
72 13090002 Lower Rio Grande 2007 869.763 625.319 95.987 55.453 40.534 82.168 148.457 0.308 0.000 0.006 23.337 18.450 0.014 12
73 13090002 Lower Rio Grande 2008 795.111 589.110 109.684 55.399 54.286 119.137 96.317 0.296 0.000 0.004 23.763 18.194 0.014 12
74 13090002 Lower Rio Grande 2009 479.881 490.488 5.039 -32.587 37.625 104.836 -15.646 0.285 0.000 0.003 24.022 18.108 0.014 12
75 13090002 Lower Rio Grande 2010 1084.528 699.038 192.623 97.691 94.931 178.230 192.867 0.321 0.000 0.007 22.822 18.008 0.014 12
76 13090002 Lower Rio Grande 2011 275.488 430.214 -48.981 -68.269 19.289 101.809 -105.746 0.275 0.000 0.003 23.793 17.622 0.014 12
77 13090002 Lower Rio Grande 2012 479.677 460.896 -20.284 -40.667 20.383 53.159 39.065 0.281 0.000 0.004 24.288 18.347 0.014 12
78 13090002 Lower Rio Grande 2013 650.069 465.931 33.842 11.845 21.997 70.472 150.296 0.282 0.000 0.005 23.944 17.935 0.014 12
79 13090002 Lower Rio Grande 2014 703.843 579.848 85.340 46.722 38.617 111.908 38.655 0.310 0.000 0.008 22.689 17.761 0.014 12
80 13090002 Lower Rio Grande 2015 900.074 722.253 115.124 61.240 53.884 117.076 62.696 0.329 0.000 0.008 23.010 18.649 0.014 12
81 13090002 Lower Rio Grande 2016 775.728 657.942 78.945 22.025 56.921 133.878 38.841 0.311 0.000 0.005 24.169 19.148 0.015 12
82 13090002 Lower Rio Grande 2017 529.201 536.535 -16.526 -47.176 30.650 71.056 9.192 0.291 0.000 0.003 24.938 19.275 0.015 12
83 13090002 Lower Rio Grande 2018 572.789 499.358 -9.722 -33.311 23.588 52.733 83.153 0.290 0.000 0.006 23.938 18.543 0.014 12
84 13090002 Lower Rio Grande 2019 690.825 563.182 72.537 27.060 45.477 101.696 55.107 0.302 0.000 0.006 23.549 18.605 0.014 12
85 13090002 Lower Rio Grande 2020 828.381 626.728 97.638 40.845 56.793 113.721 104.014 0.303 0.000 0.005 24.326 19.174 0.015 12
86 13090002 Lower Rio Grande 2021 654.179 579.884 43.147 -0.669 43.816 111.047 31.148 0.296 0.000 0.004 23.670 18.262 0.014 12
87 13090002 Lower Rio Grande 2022 614.787 546.836 21.850 -25.710 47.559 97.169 46.101 0.297 0.000 0.004 23.936 18.392 0.014 12
88 13090002 Lower Rio Grande 2023 592.687 515.588 54.509 17.747 36.763 106.107 22.589 0.290 0.000 0.005 24.407 18.778 0.015 12
89 13090002 Lower Rio Grande 2024 589.740 494.980 22.263 -9.272 31.535 66.973 72.498 0.302 0.000 0.006 24.394 19.088 0.015 12
90 12110208 South Laguna Madre 1980 666.055 413.650 97.239 -18.355 115.594 188.276 155.166 0.294 0.000 0.004 23.459 18.008 0.014 12
91 12110208 South Laguna Madre 1981 990.242 693.987 172.849 -2.847 175.696 175.873 123.406 0.346 0.000 0.007 22.669 18.531 0.014 12
92 12110208 South Laguna Madre 1982 419.475 471.372 9.649 -38.118 47.767 97.848 -61.546 0.310 0.000 0.004 23.287 18.674 0.014 12
93 12110208 South Laguna Madre 1983 854.830 575.065 120.300 32.436 87.863 115.131 159.465 0.323 0.000 0.006 22.865 17.965 0.014 12
94 12110208 South Laguna Madre 1984 694.234 516.719 69.650 4.831 64.818 105.372 107.866 0.315 0.000 0.005 22.682 17.323 0.013 12
95 12110208 South Laguna Madre 1985 631.438 538.247 50.190 -20.754 70.944 87.692 43.000 0.320 0.000 0.005 23.315 18.792 0.014 12
96 12110208 South Laguna Madre 1986 423.390 441.044 -0.702 -36.975 36.273 48.427 -16.951 0.305 0.000 0.004 23.824 18.634 0.014 12
97 12110208 South Laguna Madre 1987 1035.879 625.101 192.945 72.141 120.805 138.588 217.833 0.338 0.000 0.007 22.663 18.100 0.014 12
98 12110208 South Laguna Madre 1988 593.988 473.804 39.833 -16.481 56.314 77.772 80.352 0.313 0.000 0.005 22.918 17.835 0.014 12
99 12110208 South Laguna Madre 1989 552.202 500.754 35.231 -14.502 49.733 67.983 16.217 0.306 0.000 0.004 23.719 18.503 0.014 12
100 12110208 South Laguna Madre 1990 625.022 502.473 37.518 -12.508 50.027 59.166 85.030 0.313 0.000 0.004 23.465 18.440 0.014 12
101 12110208 South Laguna Madre 1991 797.603 562.103 90.859 20.444 70.415 72.729 144.641 0.316 0.000 0.004 23.970 18.752 0.014 12
102 12110208 South Laguna Madre 1992 761.370 576.720 114.257 23.155 91.102 117.823 70.393 0.329 0.000 0.006 23.136 18.548 0.014 12
103 12110208 South Laguna Madre 1993 961.696 569.999 218.130 83.119 135.011 195.173 173.567 0.325 0.000 0.006 23.375 18.721 0.014 12
104 12110208 South Laguna Madre 1994 506.774 492.974 -2.830 -60.010 57.180 76.043 16.630 0.314 0.000 0.004 23.286 18.186 0.014 12
105 12110208 South Laguna Madre 1995 745.750 563.142 81.697 4.956 76.741 87.581 100.911 0.319 0.000 0.004 23.973 18.929 0.015 12
106 12110208 South Laguna Madre 1996 490.585 422.799 22.956 -24.335 47.292 69.908 44.829 0.302 0.000 0.003 23.018 17.741 0.014 12
107 12110208 South Laguna Madre 1997 838.835 576.524 146.555 43.316 103.240 146.799 115.756 0.327 0.000 0.006 23.153 18.497 0.014 12
108 12110208 South Laguna Madre 1998 658.462 486.156 79.234 8.966 70.268 110.311 93.073 0.312 0.000 0.004 23.425 18.120 0.014 12
109 12110208 South Laguna Madre 1999 771.899 577.748 96.820 16.763 80.057 117.124 97.331 0.319 0.000 0.004 23.994 19.062 0.015 12
110 12110208 South Laguna Madre 2000 409.618 431.998 -1.473 -40.834 39.361 58.208 -20.907 0.299 0.000 0.003 23.993 18.403 0.014 12
111 12110208 South Laguna Madre 2001 444.597 386.400 3.225 -30.025 33.250 35.963 54.972 0.300 0.000 0.004 23.112 17.914 0.014 12
112 12110208 South Laguna Madre 2002 622.883 373.796 97.509 49.476 48.033 78.637 151.579 0.294 0.000 0.004 23.426 17.799 0.014 12
113 12110208 South Laguna Madre 2003 949.093 607.574 206.099 69.354 136.744 230.884 135.420 0.334 0.000 0.006 23.039 18.498 0.014 12
114 12110208 South Laguna Madre 2004 812.458 631.183 115.181 -4.280 119.461 140.846 66.093 0.332 0.000 0.005 23.339 18.540 0.014 12
115 12110208 South Laguna Madre 2005 575.133 516.838 23.192 -31.552 54.744 70.143 35.104 0.313 0.004 0.004 23.845 18.620 0.014 12
116 12110208 South Laguna Madre 2006 491.029 446.376 11.065 -31.563 42.629 47.951 33.588 0.299 0.000 0.003 23.944 18.121 0.014 12
117 12110208 South Laguna Madre 2007 922.391 581.261 153.145 68.644 84.501 104.630 187.985 0.327 0.000 0.006 23.295 18.500 0.014 12
118 12110208 South Laguna Madre 2008 887.051 559.325 181.169 74.716 106.453 159.949 146.557 0.316 0.000 0.004 23.696 18.324 0.014 12
119 12110208 South Laguna Madre 2009 465.844 477.641 12.377 -47.815 60.192 110.812 -24.174 0.305 0.000 0.003 23.939 18.171 0.014 12
120 12110208 South Laguna Madre 2010 1028.766 638.269 181.641 61.027 120.614 139.473 208.856 0.337 0.000 0.006 22.787 18.039 0.014 12
121 12110208 South Laguna Madre 2011 223.661 351.794 -25.758 -50.903 25.145 80.487 -102.376 0.278 0.000 0.002 23.723 17.675 0.014 12
122 12110208 South Laguna Madre 2012 433.160 388.246 1.833 -32.735 34.568 37.119 43.081 0.293 0.000 0.004 24.266 18.360 0.014 12
123 12110208 South Laguna Madre 2013 552.671 380.203 29.484 -10.134 39.618 37.083 142.984 0.291 0.000 0.004 23.880 17.937 0.014 12
124 12110208 South Laguna Madre 2014 724.411 515.064 100.534 36.805 63.730 95.154 108.812 0.322 0.000 0.006 22.630 17.624 0.014 12
125 12110208 South Laguna Madre 2015 1010.676 663.800 234.374 67.901 166.473 195.953 112.502 0.345 0.000 0.007 23.009 18.636 0.014 12
126 12110208 South Laguna Madre 2016 819.694 597.432 128.455 7.255 121.201 161.973 93.807 0.324 0.000 0.004 24.148 19.176 0.015 12
127 12110208 South Laguna Madre 2017 522.220 484.604 15.327 -36.017 51.345 61.162 22.289 0.305 0.000 0.003 24.901 19.298 0.015 12
128 12110208 South Laguna Madre 2018 573.721 455.317 21.626 -24.557 46.183 43.653 96.778 0.306 0.000 0.007 23.881 18.569 0.014 12
129 12110208 South Laguna Madre 2019 679.079 495.400 71.858 9.831 62.027 85.066 111.820 0.317 0.000 0.005 23.542 18.628 0.014 12
130 12110208 South Laguna Madre 2020 897.859 596.957 140.689 49.168 91.522 104.738 160.213 0.322 0.000 0.004 24.247 19.186 0.015 12
131 12110208 South Laguna Madre 2021 885.302 576.617 189.225 53.101 136.124 197.178 119.460 0.316 0.000 0.004 23.484 18.436 0.014 12
132 12110208 South Laguna Madre 2022 608.483 515.403 50.338 -36.074 86.412 108.379 42.742 0.313 0.000 0.004 23.872 18.453 0.014 12
133 12110208 South Laguna Madre 2023 626.101 488.990 68.831 4.253 64.578 102.178 68.280 0.306 0.000 0.005 24.313 18.874 0.015 12
134 12110208 South Laguna Madre 2024 582.583 465.591 42.110 -13.899 56.009 73.424 74.882 0.318 0.000 0.005 24.334 19.116 0.015 12

Long-term averages per watershed

Mean annual water balance over the full record — a check that the budget is sensible (precipitation ≈ evapotranspiration + runoff in this semi-arid region, the small remainder going to soil/groundwater storage).

Show code
balance_means = (
    wy.groupby("name")[["precip_mm", "et_mm", "runoff_mm", "recharge_mm", "balance_mm", "t2_degc"]]
    .mean()
    .reindex(WATERSHED_ORDER)
    .round(1)
)
show(balance_means.reset_index())
name precip_mm et_mm runoff_mm recharge_mm balance_mm t2_degc
0 South Laguna Madre 683.7 515.7 82.8 104.8 85.3 23.5
1 Los Olmos 566.8 546.1 31.9 57.5 -11.2 23.2
2 Lower Rio Grande 652.9 552.7 43.0 95.0 57.2 23.6

Step 7 — Maps

We reproject the small grid to latitude/longitude and draw it over the watershed outlines on a light basemap. Climatology maps use sequential colors; trend maps use a diverging color scale centered on zero (so red/blue shows drying/wetting or cooling/warming).

Show code
def spatial_map(da2d, cmap, title, clabel, diverging=False):
    """Reproject one (y, x) grid to EPSG:4326 and render a tiled quadmesh map with the
    watershed outlines on top. Diverging maps are symmetric about zero."""
    import numpy as np

    da = da2d.rio.write_crs(grid_crs).rio.reproject("EPSG:4326")
    clim = None
    if diverging:
        vmax = float(np.nanmax(np.abs(da.values)))
        clim = (-vmax, vmax)
    quad = da.hvplot.quadmesh(
        x="x", y="y", geo=True, tiles="CartoLight", cmap=cmap, clim=clim,
        frame_width=380, data_aspect=1, clabel=clabel, title=title,
        xlabel="", ylabel="", colorbar=True,
    )
    return quad * gv.Path(watersheds_gdf).opts(color="black", line_width=1.2)

Climatology — mean annual precipitation, ET, and temperature

Show code
climatology_maps = (
    spatial_map(climatology["precip_mm"], "blues", "Mean annual precipitation", "mm/yr")
    + spatial_map(climatology["et_mm"], "YlOrBr", "Mean annual ET", "mm/yr")
    + spatial_map(climatology["t2_degc"], "plasma", "Mean annual temperature", "°C")
).cols(2)
climatology_maps

Step 8 — Charts

Annual water balance per watershed

Precipitation, evapotranspiration, and runoff by water year. Click a legend entry to hide/show a line.

Show code
BALANCE_SERIES = {"precip_mm": "Precipitation", "et_mm": "ET", "runoff_mm": "Runoff"}
SERIES_COLORS = categorical_colors(list(BALANCE_SERIES.values()))

balance_panels = []
for name in WATERSHED_ORDER:
    sub = wy[wy["name"] == name]
    overlay = hv.Overlay([
        sub.hvplot.line(x="water_year", y=col, label=label).opts(color=SERIES_COLORS[label])
        for col, label in BALANCE_SERIES.items()
    ])
    balance_panels.append(overlay.opts(title=name, ylabel="mm / water year", xlabel="water year"))

balance_fig = hv.Layout(balance_panels).cols(1).opts(
    hv.opts.Overlay(frame_width=720, frame_height=210, legend_position="right",
                    active_tools=["pan"], hooks=[make_legend_clickable]),
)
balance_fig

Warming signal — mean annual temperature

Show code
temp_fig = wy.hvplot.line(
    x="water_year", y="t2_degc", by="name",
    color=[WATERSHED_COLORS[n] for n in WATERSHED_ORDER],
    frame_width=720, frame_height=300,
    ylabel="mean temperature (°C)", xlabel="water year",
    title="Mean annual 2 m air temperature", legend="right",
).opts(active_tools=["pan"], hooks=[make_legend_clickable])
temp_fig

Trend rates (Sen’s slope) per watershed

Per-year rate of change for each water-balance term. Hover for the Mann–Kendall direction and p-value; bars with p < 0.05 are statistically significant.

Show code
trend_fig = trends.hvplot.bar(
    x="variable", y="slope_per_year", by="name",
    color=[WATERSHED_COLORS[n] for n in WATERSHED_ORDER],
    hover_cols=["trend", "p_value", "significant"],
    frame_width=760, frame_height=360, rot=40,
    ylabel="Sen's slope (per year)", xlabel="",
    title="Long-term trend rates by watershed (Sen's slope)", legend="top_right",
).opts(active_tools=[])
trend_fig

What’s next

The gridded cube (zarr), per-cell climatology/trend grids (zarr), and per-watershed water-year & trend tables (parquet/CSV) are saved under data/conus404/. Next steps could compare these modeled balances against the USGS gauge records discovered in notebook 2, or bring in additional CONUS404 variables (snow processes, radiation) for a fuller energy/water budget.

Back to top