Visualization of Streamflow Conditions at Streamgages
This notebook provides a demonstration of the use of hyswap python package for calculating streamflow percentiles and then visualizing streamflow conditions at multiple streamflow gages.
This example notebook relies on use of the dataretrieval package for downloading streamflow information from USGS NWIS as well as the geopandas package for mapping functionality.
[1]:
# Run commented lines below to install geopandas and mapping dependencies from within the notebook
#import sys
#!{sys.executable} -m pip install geopandas folium mapclassify
[2]:
from dataretrieval import nwis
import hyswap
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from tqdm import tqdm # used for progress bar indicators
import geopandas # has dependencies of folium and mapclassify to create maps within this notebook
import warnings
warnings.filterwarnings('ignore') # ignore warnings from dataretrieval
NOTE: The tqdm
package is used in for-loops in this notebook to show a data download progress bar, which may be informative to the user. The specification below (disable_tdqm
) determines whether this progress bar is displayed when the notebook renders. It is set to True
when rendering the notebook in the hyswap
GitHub documentation site. To see the progress bars in this notebook, set disable_tqdm=False
.
[3]:
disable_tqdm=True
Define Helper Functions
The hyswap
package provides functionality for calculating non-interpretive streamflow statistics but does not provide functionality for correcting invalid data or geospatial capabilities for mapping. Here we setup some simple helper functions we can re-use throughout the notebook to QAQC data and create maps.
[4]:
# Data QAQC function for provisional NWIS data
def qaqc_nwis_data(df, data_column_name):
#replace invalid -999999 values with NA
df[data_column_name] = df[data_column_name].replace(-999999, np.nan)
# add any additional QAQC steps needed
return df
[5]:
def create_gage_condition_map(gage_df, flow_data_col, map_schema, streamflow_data_type):
# Format date and set to str type for use in map tooltips
if flow_data_col == '00060':
gage_df['Date'] = gage_df['datetime'].dt.strftime('%Y-%m-%d %H:%M')
elif flow_data_col == '00060_Mean':
gage_df['Date'] = gage_df['datetime'].dt.strftime('%Y-%m-%d')
gage_df = gage_df.drop('datetime', axis=1)
# create colormap for map from hyswap schema
schema = hyswap.utils.retrieve_schema(map_schema)
flow_cond_cmap = schema['colors']
if 'low_color' in schema:
flow_cond_cmap = [schema['low_color']] + flow_cond_cmap
if 'high_color' in schema:
flow_cond_cmap = flow_cond_cmap + [schema['high_color']]
# if creating a drought map, set handling of non-drought flows
if map_schema in ['WaterWatch_Drought', 'NIDIS_Drought']:
gage_df['flow_cat'] = gage_df['flow_cat'].cat.add_categories('Other')
gage_df.loc[gage_df['flow_cat'].isnull(), 'flow_cat'] = 'Other'
flow_cond_cmap = flow_cond_cmap + ['#e3e0ca'] # light taupe
# set NA values to "Not Ranked" category
gage_df['flow_cat'] = gage_df['flow_cat'].cat.add_categories('Not Ranked')
gage_df.loc[gage_df['est_pct'].isna(), 'flow_cat'] = 'Not Ranked'
flow_cond_cmap = flow_cond_cmap + ['#d3d3d3'] # light grey
# renaming columns with user friendly names for map
gage_df = gage_df.rename(columns={flow_data_col:'Discharge (cfs)',
'est_pct':'Estimated Percentile',
'site_no':'USGS Gage ID',
'station_nm':'Streamgage Name',
'flow_cat':'Streamflow Category'})
# convert dataframe to geopandas GeoDataFrame
gage_df = geopandas.GeoDataFrame(gage_df,
geometry=geopandas.points_from_xy(gage_df.dec_long_va,
gage_df.dec_lat_va),
crs="EPSG:4326").to_crs("EPSG:5070")
# Create map
m = gage_df.explore(column="Streamflow Category",
cmap=flow_cond_cmap,
tooltip=["USGS Gage ID", "Streamgage Name", "Streamflow Category", "Discharge (cfs)", "Estimated Percentile", "Date"],
tiles="CartoDB Positron",
marker_kwds=dict(radius=5),
legend_kwds=dict(caption=streamflow_data_type + '<br> Streamflow Category'))
return m #returns a folium map object
Data Downloading and Processing
Utilize an example state to select streamgages for generating various flow condition maps. Certain past days selected in the notebook are relevant to using the state of Vermont (VT) as an example, but the notebook can be run for any state. Next, find all stream sites active in the last year within the state.
[6]:
#| tbl-cap: List of streamgage sites active within the last week
state = 'VT'
# Query NWIS for what streamgage sites were active within the last week
sites, _ = nwis.what_sites(stateCd=state, parameterCd='00060', period="P1W", siteType='ST')
display(sites)
agency_cd | site_no | station_nm | site_tp_cd | dec_lat_va | dec_long_va | coord_acy_cd | dec_coord_datum_cd | alt_va | alt_acy_va | alt_datum_cd | huc_cd | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | USGS | 01133000 | EAST BRANCH PASSUMPSIC RIVER NEAR EAST HAVEN, VT | ST | 44.633942 | -71.897594 | S | NAD83 | 943.34 | 0.21 | NAVD88 | 1080102 | POINT (-71.89759 44.63394) |
1 | USGS | 01134500 | MOOSE RIVER AT VICTORY, VT | ST | 44.511723 | -71.837314 | S | NAD83 | 1103.46 | 0.16 | NAVD88 | 1080102 | POINT (-71.83731 44.51172) |
2 | USGS | 01135100 | POPE BROOK TRIBUTARY (W-9), NR NORTH DANVILLE, VT | ST | 44.490611 | -72.161767 | S | NAD83 | 1720.57 | 0.08 | NAVD88 | 1080102 | POINT (-72.16177 44.49061) |
3 | USGS | 01135150 | POPE BROOK (SITE W-3) NEAR NORTH DANVILLE, VT | ST | 44.476167 | -72.124543 | S | NAD83 | 1141.03 | 0.14 | NAVD88 | 1080102 | POINT (-72.12454 44.47617) |
4 | USGS | 01135300 | SLEEPERS RIVER (SITE W-5) NEAR ST. JOHNSBURY, VT | ST | 44.435335 | -72.038429 | S | NAD83 | 641.27 | 0.01 | NAVD88 | 1080102 | POINT (-72.03843 44.43534) |
5 | USGS | 01135500 | PASSUMPSIC RIVER AT PASSUMPSIC, VT | ST | 44.365615 | -72.039261 | S | NAD83 | 487.79 | 0.12 | NAVD88 | 1080102 | POINT (-72.03926 44.36561) |
6 | USGS | 01138500 | CONNECTICUT RIVER AT WELLS RIVER, VT | ST | 44.153397 | -72.041758 | S | NAD83 | 399.37 | 0.01 | NAVD88 | 1080101 | POINT (-72.04176 44.1534) |
7 | USGS | 01139000 | WELLS RIVER AT WELLS RIVER, VT | ST | 44.150341 | -72.065091 | S | NAD83 | 505.20 | 0.12 | NAVD88 | 1080103 | POINT (-72.06509 44.15034) |
8 | USGS | 01139800 | EAST ORANGE BRANCH AT EAST ORANGE, VT | ST | 44.092842 | -72.335653 | S | NAD83 | 1177.59 | 0.07 | NAVD88 | 1080103 | POINT (-72.33565 44.09284) |
9 | USGS | 01141500 | OMPOMPANOOSUC RIVER AT UNION VILLAGE, VT | ST | 43.790086 | -72.254932 | S | NAD83 | 404.64 | 0.17 | NAVD88 | 1080103 | POINT (-72.25493 43.79009) |
10 | USGS | 01142500 | AYERS BROOK AT RANDOLPH, VT | ST | 43.934510 | -72.657882 | S | NAD83 | 630.13 | 0.13 | NAVD88 | 1080105 | POINT (-72.65788 43.93451) |
11 | USGS | 01144000 | WHITE RIVER AT WEST HARTFORD, VT | ST | 43.714236 | -72.418149 | S | NAD83 | 374.32 | 0.16 | NAVD88 | 1080105 | POINT (-72.41815 43.71424) |
12 | USGS | 01150900 | OTTAUQUECHEE RIVER NEAR WEST BRIDGEWATER, VT | ST | 43.622291 | -72.758990 | S | NAD83 | 1148.59 | 0.10 | NAVD88 | 1080106 | POINT (-72.75899 43.62229) |
13 | USGS | 01151500 | OTTAUQUECHEE RIVER AT NORTH HARTLAND, VT | ST | 43.602571 | -72.354258 | S | NAD83 | 350.27 | 0.16 | NAVD88 | 1080106 | POINT (-72.35426 43.60257) |
14 | USGS | 01153000 | BLACK RIVER AT NORTH SPRINGFIELD, VT | ST | 43.333407 | -72.514813 | S | NAD83 | 445.36 | 0.01 | NAVD88 | 1080106 | POINT (-72.51481 43.33341) |
15 | USGS | 01153550 | WILLIAMS RIVER NEAR ROCKINGHAM VT | ST | 43.191743 | -72.485089 | S | NAD83 | 303.26 | 0.01 | NAVD88 | 1080107 | POINT (-72.48509 43.19174) |
16 | USGS | 01155500 | WEST RIVER AT JAMAICA, VT | ST | 43.108967 | -72.775374 | S | NAD83 | 660.63 | 0.09 | NAVD88 | 1080107 | POINT (-72.77537 43.10897) |
17 | USGS | 01155910 | WEST RIVER BELOW TOWNSHEND DAM NEAR TOWNSHEND, VT | ST | 43.051190 | -72.700094 | S | NAD83 | 452.11 | 0.01 | NAVD88 | 1080107 | POINT (-72.70009 43.05119) |
18 | USGS | 01334000 | WALLOOMSAC RIVER NEAR NORTH BENNINGTON, VT | ST | 42.912856 | -73.256498 | S | NAD83 | 525.44 | 0.01 | NAVD88 | 2020003 | POINT (-73.2565 42.91286) |
19 | USGS | 04280000 | POULTNEY RIVER BELOW FAIR HAVEN, VT | ST | 43.624232 | -73.311501 | S | NAD83 | 102.37 | 0.10 | NAVD88 | 4150401 | POINT (-73.3115 43.62423) |
20 | USGS | 04282000 | OTTER CREEK AT CENTER RUTLAND, VT | ST | 43.603679 | -73.013163 | S | NAD83 | 474.35 | 0.01 | NAVD88 | 4150402 | POINT (-73.01316 43.60368) |
21 | USGS | 04282500 | OTTER CREEK AT MIDDLEBURY, VT | ST | 44.013115 | -73.167895 | S | NAD83 | 335.36 | 0.16 | NAVD88 | 4150402 | POINT (-73.1679 44.01311) |
22 | USGS | 04282525 | NEW HAVEN RIVER @ BROOKSVILLE, NR MIDDLEBURY, VT | ST | 44.061725 | -73.170674 | S | NAD83 | 239.29 | 0.10 | NAVD88 | 4150402 | POINT (-73.17067 44.06172) |
23 | USGS | 04282581 | EAST BR DEAD CREEK NEAR BRIDPORT, VT | ST | 44.028611 | -73.320639 | 5 | NAD83 | 99.48 | 0.09 | NAVD88 | 4150402 | POINT (-73.32064 44.02861) |
24 | USGS | 04282586 | WEST BR DEAD CREEK NEAR ADDISON, VT | ST | 44.049167 | -73.354167 | 5 | NAD83 | 100.56 | 0.07 | NAVD88 | 4150402 | POINT (-73.35417 44.04917) |
25 | USGS | 04282629 | LITTLE OTTER CK AT MONKTON RD, NR FERRISBURGH, VT | ST | 44.185725 | -73.184869 | 5 | NAD83 | 236.62 | 0.06 | NAVD88 | 4150402 | POINT (-73.18487 44.18572) |
26 | USGS | 04282650 | LITTLE OTTER CREEK AT FERRISBURG, VT | ST | 44.198110 | -73.249012 | S | NAD83 | 146.81 | 0.10 | NAVD88 | 4150402 | POINT (-73.24901 44.19811) |
27 | USGS | 04282780 | LEWIS CREEK AT NORTH FERRISBURG, VT | ST | 44.249220 | -73.228457 | S | NAD83 | 117.60 | 0.10 | NAVD88 | 4150402 | POINT (-73.22846 44.24922) |
28 | USGS | 04282795 | LAPLATTE RIVER AT SHELBURNE FALLS, VT | ST | 44.370051 | -73.216237 | S | NAD83 | 147.40 | 0.11 | NAVD88 | 4150403 | POINT (-73.21624 44.37005) |
29 | USGS | 04285500 | NORTH BRANCH WINOOSKI RIVER AT WRIGHTSVILLE, VT | ST | 44.299503 | -72.578721 | S | NAD83 | 549.04 | 0.08 | NAVD88 | 4150403 | POINT (-72.57872 44.2995) |
30 | USGS | 04286000 | WINOOSKI RIVER AT MONTPELIER, VT | ST | 44.256726 | -72.593443 | S | NAD83 | 499.87 | 0.16 | NAVD88 | 4150403 | POINT (-72.59344 44.25673) |
31 | USGS | 04287000 | DOG RIVER AT NORTHFIELD FALLS, VT | ST | 44.182561 | -72.640665 | S | NAD83 | 602.42 | 0.16 | NAVD88 | 4150403 | POINT (-72.64067 44.18256) |
32 | USGS | 04288000 | MAD RIVER NEAR MORETOWN, VT | ST | 44.277280 | -72.742616 | S | NAD83 | 543.67 | 0.16 | NAVD88 | 4150403 | POINT (-72.74262 44.27728) |
33 | USGS | 04288225 | W BRANCH LITTLE R ABV BINGHAM FALLS NEAR STOWE... | ST | 44.523386 | -72.771788 | S | NAD83 | 1357.94 | 0.09 | NAVD88 | 4150403 | POINT (-72.77179 44.52339) |
34 | USGS | 04288230 | RANCH BROOK AT RANCH CAMP, NEAR STOWE, VT | ST | 44.503941 | -72.781788 | S | NAD83 | 1296.26 | 0.14 | NAVD88 | 4150403 | POINT (-72.78179 44.50394) |
35 | USGS | 04288295 | LITTLE RIVER NEAR STOWE, VT | ST | 44.442500 | -72.724444 | 5 | NAD83 | 608.06 | 0.01 | NAVD88 | 4150403 | POINT (-72.72444 44.4425) |
36 | USGS | 04289000 | LITTLE RIVER NEAR WATERBURY, VT | ST | 44.370333 | -72.768730 | S | NAD83 | 427.62 | 0.09 | NAVD88 | 4150403 | POINT (-72.76873 44.37033) |
37 | USGS | 04290500 | WINOOSKI RIVER NEAR ESSEX JUNCTION, VT | ST | 44.478939 | -73.138738 | S | NAD83 | 191.17 | 0.10 | NAVD88 | 4150403 | POINT (-73.13874 44.47894) |
38 | USGS | 04292000 | LAMOILLE RIVER AT JOHNSON, VT | ST | 44.622829 | -72.676231 | S | NAD83 | 506.10 | 0.16 | NAVD88 | 4150405 | POINT (-72.67623 44.62283) |
39 | USGS | 04292500 | LAMOILLE RIVER AT EAST GEORGIA, VT | ST | 44.679215 | -73.072637 | S | NAD83 | 288.90 | 0.10 | NAVD88 | 4150405 | POINT (-73.07264 44.67921) |
40 | USGS | 04292750 | MILL RIVER AT GEORGIA SHORE RD, NR ST ALBANS, VT | ST | 44.779767 | -73.143747 | S | NAD83 | 117.95 | 0.16 | NAVD88 | 4150405 | POINT (-73.14375 44.77977) |
41 | USGS | 04292810 | JEWETT BROOK AT VT 38, NEAR ST. ALBANS, VT | ST | 44.856111 | -73.150833 | S | NAD83 | 111.53 | 0.09 | NAVD88 | 4150405 | POINT (-73.15083 44.85611) |
42 | USGS | 04293000 | MISSISQUOI RIVER NEAR NORTH TROY, VT | ST | 44.972823 | -72.385386 | S | NAD83 | 581.38 | 0.13 | NAVD88 | 4150407 | POINT (-72.38539 44.97282) |
43 | USGS | 04293500 | MISSISQUOI RIVER NEAR EAST BERKSHIRE, VT | ST | 44.960046 | -72.696521 | S | NAD83 | 402.51 | 0.10 | NAVD88 | 4150407 | POINT (-72.69652 44.96005) |
44 | USGS | 04294000 | MISSISQUOI RIVER AT SWANTON, VT | ST | 44.916709 | -73.128465 | S | NAD83 | 106.90 | 0.05 | NAVD88 | 4150407 | POINT (-73.12846 44.91671) |
45 | USGS | 04294140 | ROCK RIVER NEAR HIGHGATE CENTER, VT | ST | 44.963056 | -72.991944 | S | NAD83 | 229.47 | 0.09 | NAVD88 | 4150407 | POINT (-72.99194 44.96306) |
46 | USGS | 04294300 | PIKE RIVER AT EAST FRANKLIN, NR ENOSBURG FALLS... | ST | 45.002822 | -72.833469 | S | NAD83 | 397.54 | 0.13 | NAVD88 | 4150407 | POINT (-72.83347 45.00282) |
47 | USGS | 04296000 | BLACK RIVER AT COVENTRY, VT | ST | 44.868936 | -72.270104 | S | NAD83 | 709.74 | 0.10 | NAVD88 | 4150500 | POINT (-72.2701 44.86894) |
48 | USGS | 04296280 | BARTON RIVER NEAR COVENTRY, VT | ST | 44.871389 | -72.200556 | S | NAD83 | 679.69 | 0.10 | NAVD88 | 4150500 | POINT (-72.20056 44.87139) |
49 | USGS | 04296500 | CLYDE RIVER AT NEWPORT, VT | ST | 44.940324 | -72.189268 | S | NAD83 | 681.82 | 0.09 | NAVD88 | 4150500 | POINT (-72.18927 44.94032) |
50 | USGS | 434928072192701 | Elizabeth Mine PTS discharge nr S Strafford, VT | ST-DCH | 43.824322 | -72.324219 | S | NAD83 | 1074.00 | 1.60 | NAVD88 | 1080104 | POINT (-72.32422 43.82432) |
Retrieve Streamflow Data from NWIS
For the sites identified above, download all historical daily streamflow data (1900 through 2023 Water Years).
[7]:
# create a python dictionary of dataframes by site id number
flow_data = {}
for StaID in tqdm(sites['site_no'], disable=disable_tqdm, desc="Downloading NWIS Flow Data for Sites"):
flow_data[StaID] = nwis.get_record(sites=StaID, parameterCd='00060', start="1900-01-01", end="2023-10-01", service='dv')
Calculate Variable Streamflow Percentile Thresholds
For the sites identified above, calculate streamflow percentile thresholds at 0, 1, 5, 10, … , 90, 95, 99, 100 percentiles.
Note that when using the default settings of calculate_fixed_percentile_threshold() it is common for NA values to be returned for the highest/lowest percentile thresholds such as 1 and 99. This is because a very long streamflow record (100+ years) is required to have sufficient observations to calculate the 99th or 1st percentile of streamflow for a given day when using
the default settings of method=weibull
with mask_out_of_range=True
.
[8]:
# Define what percentile levels (thresholds) that we want to calculate.
# Intervals of 5 or less are recommended to have sufficient levels to interpolate between in later calculations.
# Note that 0 and 100 percentile levels are ignored, refer to min and max values returned instead.
percentile_levels = np.concatenate((np.array([1]), np.arange(5,96,5), np.array([99])), axis=0)
print(percentile_levels)
[ 1 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 99]
[9]:
percentile_values = {}
for StaID in tqdm(sites['site_no'], disable=disable_tqdm, desc="Processing Sites"):
if '00060_Mean' in flow_data[StaID].columns:
# Filter data as only approved data in NWIS should be used to calculate statistics
df = hyswap.utils.filter_approved_data(flow_data[StaID], '00060_Mean_cd')
percentile_values[StaID] = hyswap.percentiles.calculate_variable_percentile_thresholds_by_day(
df, '00060_Mean', percentiles=percentile_levels)
else:
print('No standard discharge data column found for site ' + StaID + ', skipping')
No standard discharge data column found for site 01155910, skipping
No standard discharge data column found for site 434928072192701, skipping
[10]:
#| tbl-cap: Sample of calcualted variable streamflow percentile thresholds for first site in list
display(percentile_values[list(percentile_values.keys())[0]].head())
min | p01 | p05 | p10 | p15 | p20 | p25 | p30 | p35 | p40 | ... | p80 | p85 | p90 | p95 | p99 | max | mean | count | start_yr | end_yr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
month_day | |||||||||||||||||||||
01-01 | 29.0 | NaN | 31.14 | 33.2 | 41.2 | 44.6 | 46.0 | 50.4 | 57.6 | 63.2 | ... | 110.0 | 130.8 | 145.0 | 169.0 | NaN | 378.0 | 85.3 | 63 | 1940 | 2023 |
01-02 | 28.0 | NaN | 31.0 | 34.2 | 40.2 | 44.0 | 46.0 | 49.6 | 57.2 | 60.6 | ... | 120.2 | 127.0 | 144.2 | 178.4 | NaN | 350.0 | 84.32 | 63 | 1940 | 2023 |
01-03 | 27.0 | NaN | 30.2 | 35.4 | 40.0 | 43.6 | 45.0 | 48.4 | 54.2 | 61.6 | ... | 108.4 | 118.2 | 133.0 | 205.0 | NaN | 300.0 | 81.88 | 63 | 1940 | 2023 |
01-04 | 28.0 | NaN | 30.2 | 35.8 | 40.64 | 43.8 | 45.0 | 51.6 | 60.32 | 66.0 | ... | 104.6 | 114.0 | 130.4 | 184.4 | NaN | 220.0 | 78.39 | 63 | 1940 | 2023 |
01-05 | 26.0 | NaN | 29.2 | 35.8 | 40.34 | 42.8 | 45.0 | 52.4 | 59.2 | 66.76 | ... | 94.2 | 103.8 | 118.0 | 160.0 | NaN | 425.0 | 79.83 | 63 | 1940 | 2023 |
5 rows × 27 columns
Create a Current Flow Conditions Map for Daily Mean Streamflow
Retrieve most recent (yesterday) daily mean streamflow
Download data from NWIS and calculate corresponding streamflow percentile for the most recent daily mean discharge
[11]:
yesterday = datetime.strftime(datetime.now(tz=ZoneInfo("US/Eastern")) - timedelta(1), '%Y-%m-%d')
recent_dvs = nwis.get_record(sites=sites['site_no'].tolist(), parameterCd='00060', start=yesterday, end=yesterday, service='dv')
recent_dvs = qaqc_nwis_data(recent_dvs, '00060_Mean')
Categorize streamflow based on calculated percentile values
Calculate estimated streamflow percentile for the new data by interpolating against the previously calculated percentile threshold levels.
[12]:
# estimate percentiles
df = pd.DataFrame()
for StaID, site_df in recent_dvs.groupby(level="site_no", group_keys=False):
if StaID in list(percentile_values.keys()):
if not percentile_values[StaID].isnull().all().all():
percentiles = hyswap.percentiles.calculate_multiple_variable_percentiles_from_values(
site_df,'00060_Mean', percentile_values[StaID])
df = pd.concat([df, percentiles])
# categorize streamflow by the estimated streamflow percentiles
df = hyswap.utils.categorize_flows(df, 'est_pct', schema_name="NWD")
df = df.reset_index(level='datetime')
# Prep Data for mapping by joining site information and flow data
gage_df = pd.merge(sites, df, how="right", on="site_no")
Create Map of Streamflow Conditions
[13]:
#| fig-cap: Map showing most recent daily mean streamflow and corresponding flow conditions
map = create_gage_condition_map(gage_df, '00060_Mean', 'NWD', 'Current Daily Mean')
display(map)
Create Map of Streamflow Conditions using Alternative Categorization Schema
[14]:
#| fig-cap: Map showing most recent daily mean streamflow and corresponding flow conditions using a brown-blue schema
# Prep Data for mapping by joining site information and flow data
map = create_gage_condition_map(gage_df, '00060_Mean', 'WaterWatch_BrownBlue', 'Current Daily Mean')
display(map)
Create a “Real-Time” Flow Conditions Map for Instantaneous Streamflow
Retrieve most recent instantaneous streamflow records
Download data from NWIS and calculate corresponding streamflow percentile for the most recent instantaneous discharge measurement
[15]:
recent_ivs = nwis.get_record(sites=sites['site_no'].tolist(), parameterCd='00060', service='iv')
recent_ivs = qaqc_nwis_data(recent_ivs, '00060')
Categorize streamflow based on calculated percentile values
Calculate estimated streamflow percentile for the new instantaneous data by interpolating against the previously calculated percentile threshold levels from daily streamflow records.
[16]:
# estimate percentiles
df = pd.DataFrame()
for StaID, site_df in recent_ivs.groupby(level="site_no", group_keys=False):
if StaID in list(percentile_values.keys()):
if not percentile_values[StaID].isnull().all().all():
percentiles = hyswap.percentiles.calculate_multiple_variable_percentiles_from_values(
site_df,'00060', percentile_values[StaID])
df = pd.concat([df, percentiles])
# categorize streamflow by the estimated streamflow percentiles
df = hyswap.utils.categorize_flows(df, 'est_pct', schema_name="NWD")
df = df.tz_convert(tz='US/Eastern')
df = df.reset_index(level='datetime')
# Prep Data for mapping by joining site information and flow data
gage_df = pd.merge(sites, df, how="right", on="site_no")
Create Map of Real-Time Streamflow Conditions
[17]:
#| fig-cap: Map showing most real-time streamflow conditions
map = create_gage_condition_map(gage_df, '00060', 'NWD', 'Real-Time Instantaneous')
display(map)
Create a Current Flow Conditions Map for n-Day Daily Streamflow
Retrieve daily streamflow records for past 7 days
Download data from NWIS and calculate corresponding streamflow percentiles for each day
[18]:
past_dvs = nwis.get_record(
sites=sites['site_no'].tolist(),
parameterCd='00060',
start=datetime.strftime(datetime.now(tz=ZoneInfo("US/Eastern")) - timedelta(7), '%Y-%m-%d'),
end=yesterday,
service='dv',
multi_index=False
)
past_dvs = qaqc_nwis_data(past_dvs, '00060_Mean')
past_dvs_7d = pd.DataFrame()
for StaID, new_df in past_dvs.groupby('site_no'):
df = hyswap.utils.rolling_average(new_df, '00060_Mean', '7D').round(2)
past_dvs_7d=pd.concat([past_dvs_7d, df], axis=0)
past_dvs_7d = past_dvs_7d.dropna()
Calculate 7-day average streamflow and corresponding variable percentile thresholds
[19]:
flow_data_7d = {}
for StaID in tqdm(sites['site_no'], disable=disable_tqdm):
if '00060_Mean' in flow_data[StaID].columns:
flow_data_7d[StaID] = hyswap.utils.rolling_average(flow_data[StaID], '00060_Mean', '7D').round(2)
else:
print('No standard discharge data column found for site ' + StaID + ', skipping')
No standard discharge data column found for site 01155910, skipping
No standard discharge data column found for site 434928072192701, skipping
[20]:
percentile_values_7d = {}
for StaID in tqdm(sites['site_no'], disable=disable_tqdm, desc="Processing"):
if '00060_Mean' in flow_data[StaID].columns:
# Filter data as only approved data in NWIS should be used to calculate statistics
df = hyswap.utils.filter_approved_data(flow_data_7d[StaID], '00060_Mean_cd')
percentile_values_7d[StaID] = hyswap.percentiles.calculate_variable_percentile_thresholds_by_day(
df, '00060_Mean', percentiles=percentile_levels)
else:
print('No standard discharge data column found for site ' + StaID + ', skipping')
No standard discharge data column found for site 01155910, skipping
No standard discharge data column found for site 434928072192701, skipping
Categorize streamflow based on calculated percentile values
Calculate estimated streamflow percentile for the new data by interpolating against the previously calculated percentile threshold levels.
[21]:
# estimate percentiles
df = pd.DataFrame()
for StaID, site_df in past_dvs_7d.groupby("site_no"):
if StaID in list(percentile_values_7d.keys()):
if not percentile_values[StaID].isnull().all().all():
month_day = site_df.index.strftime('%m-%d')[0]
site_df['est_pct'] = hyswap.percentiles.calculate_variable_percentile_from_value(
site_df['00060_Mean'][0], percentile_values[StaID], month_day)
df = pd.concat([df, site_df])
# categorize streamflow by the estimated streamflow percentiles
df = hyswap.utils.categorize_flows(df, 'est_pct', schema_name="NWD")
# keep only most recent 7-day average flow for plotting
df = df[df.index.get_level_values('datetime') == yesterday]
df = df.reset_index(level='datetime')
# Prep Data for mapping by joining site information and flow data
gage_df = pd.merge(sites, df, how="right", on="site_no")
Create Map of 7-Day Average Streamflow Conditions
[22]:
#| fig-cap: Map showing most recent 7-day average streamflow and corresponding flow conditions
map = create_gage_condition_map(gage_df, '00060_Mean', 'NWD', 'Current 7-Day Average')
display(map)
Create a Drought Conditions Map for a Previous Day’s Streamflow
Retrieve daily streamflow records from a past day
Download data from NWIS and calculate corresponding streamflow percentiles for the given day’s streamflow
[23]:
past_day = "2023-05-30"
past_dvs = nwis.get_record(sites=sites['site_no'].tolist(),
parameterCd='00060',
start=past_day,
end=past_day,
service='dv')
past_dvs = qaqc_nwis_data(past_dvs, '00060_Mean')
Categorize streamflow based on calculated percentile values
[24]:
# Calculate estimated streamflow percentile for the new data by interpolating against
# the previously calculated percentile threshold levels
df = pd.DataFrame()
for StaID, site_df in past_dvs.groupby(level="site_no", group_keys=False):
if StaID in list(percentile_values.keys()):
if not percentile_values[StaID].isnull().all().all():
percentiles = hyswap.percentiles.calculate_multiple_variable_percentiles_from_values(
site_df,'00060_Mean', percentile_values[StaID])
df = pd.concat([df, percentiles])
# categorize streamflow by the estimated streamflow percentiles
df = hyswap.utils.categorize_flows(df, 'est_pct', schema_name="WaterWatch_Drought")
df = df.reset_index(level='datetime')
# Prep Data for mapping by joining site information and flow data
gage_df = pd.merge(sites, df, how="right", on="site_no")
Create Map of Streamflow Drought Conditions
[25]:
#| fig-cap: Map showing historical daily mean streamflow and corresponding flow conditions using a drought categorization schema
map = create_gage_condition_map(gage_df, '00060_Mean', 'WaterWatch_Drought', 'Daily Mean')
display(map)
Create a Flood Conditions Map for a past Day’s Streamflow
This example uses fixed percentiles that are not calculated by day of year, but instead across all days of the year together. Flow categories are therefore relative to absolute streamflow levels rather than what is normal for that day of the year.
Retrieve daily streamflow records from a past day
Download data from NWIS and calculate corresponding fixed streamflow percentiles for the given day’s streamflow
[26]:
past_day = "2023-07-10"
past_dvs = nwis.get_record(sites=sites['site_no'].tolist(),
parameterCd='00060',
start=past_day,
end=past_day,
service='dv')
past_dvs = qaqc_nwis_data(past_dvs, '00060_Mean')
[27]:
fixed_percentile_values = {}
for StaID in tqdm(sites['site_no'], disable=disable_tqdm):
if '00060_Mean' in flow_data[StaID].columns:
# Filter data as only approved data in NWIS should be used to calculate statistics
df = hyswap.utils.filter_approved_data(flow_data[StaID], '00060_Mean_cd')
if not df.empty:
fixed_percentile_values[StaID] = hyswap.percentiles.calculate_fixed_percentile_thresholds(
df['00060_Mean'], percentiles=percentile_levels)
else:
print(StaID + ' has no approved data, skipping')
else:
print(StaID + ' does not have standard discharge data column, skipping')
01155910 does not have standard discharge data column, skipping
434928072192701 does not have standard discharge data column, skipping
Categorize streamflow based on calculated percentile values
[28]:
# estimate percentiles
for StaID in past_dvs.index.get_level_values(0):
if StaID in list(fixed_percentile_values.keys()):
past_dvs.at[(StaID, past_day), 'est_pct'] = hyswap.percentiles.calculate_fixed_percentile_from_value(
past_dvs.at[(StaID, past_day), '00060_Mean'], fixed_percentile_values[StaID])
# categorize streamflow by the estimated streamflow percentiles
df = hyswap.utils.categorize_flows(past_dvs, 'est_pct', schema_name="WaterWatch_Flood")
df = df.reset_index(level='datetime')
# Prep Data for mapping by joining site information and flow data
gage_df = pd.merge(sites, df, how="right", on="site_no")
Create Map of Streamflow High-Flow Conditions
[29]:
#| fig-cap: Map showing historical daily mean streamflow and corresponding flow conditions using a high-flow categorization schema
map = create_gage_condition_map(gage_df, '00060_Mean', 'WaterWatch_Flood', 'Daily Mean')
display(map)