Daily snow-station archive (global_snow_networks)#

global_snow_networks pre-downloads daily SWE and snow depth for every station across its networks whose daily record its probe has verified, and publishes three things: a normalized inventory of every station, periodic snow courses included; the observations as a chunked Zarr store on its GitHub Pages site; and one CSV per daily station bundled into a single archive of about 28 MB. All are rebuilt daily.

Three routes: source="github-pages-zarr" (default) reads the store and fetches only the chunks a request touches — under a megabyte for one water year of every station, or for one station’s whole record — and falls back to the snapshot bundle if Pages cannot be read; source="github-tarball" downloads the bundle attached to the latest snapshot release (each has a Zenodo DOI) once into the package cache and reads every wanted CSV out of it, which is the route for a result that should cite a fixed snapshot; source="github-csv" fetches one CSV per station. None needs an account, not even for the Norwegian stations. The archive holds SWE and snow depth only.

The figures show every daily station coloured by the length of its record, one water year of peak SWE against elevation for all of them, and one station’s complete daily record.

import matplotlib.pyplot as plt
import pandas as pd

import easysnowdata as esd

print(f"easysnowdata {esd.__version__}")
easysnowdata 0.3.9.dev1+gd97689c2f

Where the product comes from.

for src in esd.catalog.get("snow-station-archive").sources:
    print(f"{src.id:20} {src.title:45} {', '.join(src.requires) or 'no account'}")
github-pages-zarr    global_snow_networks chunked archive (Pages)  no account
github-tarball       global_snow_networks snapshot bundle (latest release) no account
github-csv           global_snow_networks per-station CSVs         no account

The inventory is one HTTP request, and daily_or_better is the probe’s verdict: a station counts as daily only once daily values have actually been retrieved from it. Most records start around 1980; the oldest are the NWS Cooperative Observer sites AWDB mirrors, with snow depth from the 1890s.

inv_gdf = esd.stations.archive.inventory(daily_only=True)
print(inv_gdf["network"].value_counts().to_string())
span = inv_gdf["latest_record_date"] - inv_gdf["earliest_record_date"]
inv_gdf["record_length"] = pd.cut(
    span.dt.days / 365.25,
    [0, 15, 30, 45, 200],
    labels=["under 15 years", "15 to 30 years", "30 to 45 years", "over 45 years"],
)
inv_gdf["record_length"] = (
    inv_gdf["record_length"].cat.add_categories("no dates").fillna("no dates")
)
oldest_gdf = inv_gdf.nsmallest(3, "earliest_record_date")
print(oldest_gdf[["name", "network", "earliest_record_date"]].to_string())
network
awdb       1219
fmi         902
slf         303
cdec        189
databc      148
alberta      84
snih         35
nve          32
yukon        17
ceaza        17
                               name network earliest_record_date
code
FMI-100971      Helsinki Kaisaniemi     fmi           1892-05-01
354003_OR_COOP   Hood River Exp Stn    awdb           1896-12-01
0546_AK_COOP     Barrow WSO Airport    awdb           1901-09-03

Every probe-verified daily station, by length of record. The largest class is over 45 years: SNOTEL and the CCSS pillows were built out around 1980 and their daily archives begin there. An ordered categorical keeps the legend in that order. Two hundred stations, nearly all CDEC, carry the daily flag but no record dates yet.

print(inv_gdf["record_length"].value_counts().to_string())
ax = esd.plotting.points(
    inv_gdf,
    column="record_length",
    legend_label="record length",
    markersize=6,
    figsize=(12, 5),
    title=f"{len(inv_gdf)} daily snow stations",
)
2946 daily snow stations
record_length
over 45 years     896
under 15 years    818
15 to 30 years    630
30 to 45 years    602
no dates            0
/home/runner/work/easysnowdata/easysnowdata/docs/gallery/stations/plot_station_archive.py:66: GeographicAxesWarning: The data are in a geographic CRS (EPSG:4326), so one axis unit is a degree, not a metre. The axes use a latitude-corrected aspect (1/cos(13.3°) = 1.03) so shapes are not stretched; for a true equal-area grid load the product with crs='utm'.
  ax = esd.plotting.points(

One water year everywhere. Each station’s series is cut to the window before the dense grid is built, so this is much cheaper than the whole archive.

obs_ds = esd.stations.archive.load(time="2023-10/2024-09")
print(obs_ds)
<xarray.Dataset> Size: 43MB
Dimensions:               (station: 3074, time: 366)
Coordinates: (12/31)
  * station               (station) <U16 197kB '0280_AK_COOP' ... 'WWC_CA_MSNT'
    network               (station) <U11 135kB 'awdb' 'awdb' ... 'cdec' 'awdb'
    name                  (station) <U79 971kB 'Anchorage Wscmo AP' ... 'West...
    client                (station) <U11 135kB 'awdb' 'awdb' ... 'cdec' 'awdb'
    network_code          (station) <U8 98kB 'COOP' 'COOP' ... 'CCSS' 'MSNT'
    network_name          (station) <U25 307kB '' '' '' '' '' ... '' '' '' '' ''
    ...                    ...
    metadata_fetched_at   (station) <U19 234kB '2026-11-07 00:00:00' ... '202...
    station_id            (station) <U16 197kB '0280:AK:COOP' ... 'WWC:CA:MSNT'
    hemisphere            (station) <U8 98kB 'northern' ... 'northern'
  * time                  (time) datetime64[ns] 3kB 2023-10-01 ... 2024-09-30
    water_year            (station, time) int64 9MB 2024 2024 2024 ... 2024 2024
    dowy                  (station, time) int64 9MB 1 2 3 4 ... 363 364 365 366
Data variables:
    swe                   (station, time) float64 9MB nan nan nan ... 0.0 0.0
    snwd                  (station, time) float64 9MB 0.0 0.0 0.0 ... nan nan
Attributes:
    source:                github-pages-zarr
    source_id:             github-pages-zarr
    source_title:          global_snow_networks chunked archive (Pages)
    source_url:            https://egagli.github.io/global_snow_networks/arch...
    product_id:            snow-station-archive
    title:                 Daily snow-station archive (global snow networks)
    data_citation:         Gagliano, E. Global snow networks: a documented in...
    license:               Per contributing network; see each network's produ...
    easysnowdata_version:  0.3.9.dev1+gd97689c2f
    interval:              daily

The archive passes the networks’ values through unfiltered. A few stations report a peak of ten metres or more of water in a single day, which is a sensor fault rather than snow; find them before they set the axis.

peak_da = obs_ds["swe"].max(dim="time")
suspect_da = peak_da.where(peak_da > 300, drop=True)
print(suspect_da.to_series().round(0).to_string())
station
75.93.0        1435.0
DPO_CA_MSNT     995.0
MB3_CA_MSNT    2073.0

Peak SWE against elevation, one point per station, coloured by network, with the faulty values clipped off the top. The same peak sits a kilometre lower in the maritime Cascades and coast ranges than in the Sierra Nevada or the continental interior.

fig, ax = plt.subplots(figsize=(8, 5))
for network in sorted(set(obs_ds["network"].values.tolist())):
    rows_da = obs_ds["network"] == network
    ax.scatter(
        obs_ds["elevation_m"][rows_da],
        peak_da[rows_da],
        s=10,
        alpha=0.6,
        label=str(network),
    )
ax.set_xlabel(esd.plotting.label(obs_ds["elevation_m"], name="elevation", units="m"))
ax.set_ylabel(esd.plotting.label(obs_ds["swe"], name="peak SWE, water year 2024"))
ax.set_title("Peak snow water equivalent against elevation")
ax.set_ylim(0, 300)
ax.grid(True, color="0.85", linewidth=0.6)
ax.set_axisbelow(True)
ax.legend(title="network", fontsize=8, frameon=False, loc="upper left")
fig.tight_layout()
Peak snow water equivalent against elevation

One station’s whole record, from the per-station CSV route: a single request and no bundle. Paradise on Mount Rainier begins in October 1980, which is where most of the archive begins.

paradise_ds = esd.stations.archive.load("679_WA_SNTL", source="github-csv")
ax = esd.plotting.timeseries(
    paradise_ds["swe"].squeeze("station"),
    title="Paradise (1570 m): the whole daily record",
)
Paradise (1570 m): the whole daily record

For anything the archive does not hold (precipitation, temperature, wind, quality flags, sub-daily data, or today’s observation) go to the networks themselves with easysnowdata.stations.load().