Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardized Streamflow Index and Standardized Groundwater level Index #1877

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
28 changes: 27 additions & 1 deletion docs/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -2153,7 +2153,7 @@ @article{droogers2002
type = {Article}
}

@Article{robin_2019,
@article{robin_2019,
author = {Robin, Y. and Vrac, M. and Naveau, P. and Yiou, P.},
title = {Multivariate stochastic bias corrections with optimal transport},
journal = {Hydrology and Earth System Sciences},
Expand Down Expand Up @@ -2203,3 +2203,29 @@ @article{knol_1989
publisher = "Springer",
number = "1",
}

@article{vicente-serrano_2012,
author = {Sergio M. Vicente-Serrano and Juan I. López-Moreno and Santiago Beguería and Jorge Lorenzo-Lacruz and Cesar Azorin-Molina and Enrique Morán-Tejeda},
title = {Accurate Computation of a Streamflow Drought Index},
journal = {Journal of Hydrologic Engineering},
volume = {17},
number = {2},
pages = {318-332},
year = {2012},
doi = {10.1061/(ASCE)HE.1943-5584.0000433},
URL = {https://ascelibrary.org/doi/abs/10.1061/%28ASCE%29HE.1943-5584.0000433},
eprint = {https://ascelibrary.org/doi/pdf/10.1061/%28ASCE%29HE.1943-5584.0000433},
abstract = "In this study, the authors investigated an approach to calculate the standardized streamflow index (SSI), which allows accurate spatial and temporal comparison of the hydrological conditions of a stream or set of streams. For this purpose, the capability of six three-parameter distributions (lognormal, Pearson Type III, log-logistic, general extreme value, generalized Pareto, and Weibull) and two different approaches to select the most suitable distribution the best monthly fit (BMF) and the minimum orthogonal distance (MD), were tested by using a monthly streamflow data set for the Ebro Basin (Spain). This large Mediterranean basin is characterized by high variability in the magnitude of streamflows and in seasonal regimes. The results show that the most commonly used probability distributions for flow frequency analysis provided good fits to the streamflow series. Thus, the visual inspection of the L-moment diagrams and the results of the Kolmogorov-Smirnov test did not enable the selection of a single optimum probability distribution. However, no single probability distribution for all the series was suitable for obtaining a robust standardized streamflow series because each of the distributions had one or more limitations. The BMF and MD approaches improved the results in the expected average, standard deviation, and the frequencies of extreme events of the SSI series in relation to the selection of a unique distribution for each station. The BMF and MD approaches involved using different probability distributions for each gauging station and month of the year to calculate the SSI. Both approaches are easy to apply and they provide very similar results in the quality of the obtained hydrological drought indexes. The proposed procedures are very flexible for analyses involving contrasting hydrological regimes and flow characteristics."
}

@article{bloomfield_2013,
AUTHOR = {Bloomfield, J. P. and Marchant, B. P.},
TITLE = {Analysis of groundwater drought building on the standardised precipitation index approach},
JOURNAL = {Hydrology and Earth System Sciences},
VOLUME = {17},
YEAR = {2013},
NUMBER = {12},
PAGES = {4769--4787},
URL = {https://hess.copernicus.org/articles/17/4769/2013/},
DOI = {10.5194/hess-17-4769-2013}
}
36 changes: 36 additions & 0 deletions tests/test_hydrology.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from __future__ import annotations

from pathlib import Path

import numpy as np
import pytest

from xclim import indices as xci
from xclim import land
from xclim.core.units import convert_units_to


class TestBaseFlowIndex:
Expand All @@ -23,6 +28,37 @@ def test_simple(self, q_series):
np.testing.assert_array_equal(out, 2)


class TestStandardizedStreamflow:
@pytest.mark.slow
def test_3d_data_with_nans(self, open_dataset):
nc_ds = Path("Raven", "q_sim.nc")
# test with data
ds = open_dataset(nc_ds)
q = ds.q_obs.sel(time=slice("2008")).rename("q")
qMM = convert_units_to(q, "mm**3/s", context="hydro") # noqa
# put a nan somewhere
qMM.values[10] = np.nan
q.values[10] = np.nan

out1 = land.standardized_streamflow_index(
q,
freq="MS",
window=1,
dist="genextreme",
method="APP",
fitkwargs={"floc": 0},
)
out2 = land.standardized_streamflow_index(
qMM,
freq="MS",
window=1,
dist="genextreme",
method="APP",
fitkwargs={"floc": 0},
)
np.testing.assert_array_almost_equal(out1, out2, 3)


class TestSnwMax:
def test_simple(self, snw_series):
a = np.zeros(366)
Expand Down
135 changes: 135 additions & 0 deletions tests/test_indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,141 @@ def test_standardized_precipitation_evapotranspiration_index(

np.testing.assert_allclose(spei.values, values, rtol=0, atol=diff_tol)

@pytest.mark.slow
@pytest.mark.parametrize(
"freq, window, dist, method, values, diff_tol",
[
# reference results: Obtained with R package `standaRdized`
(
"D",
1,
"genextreme",
"ML",
[0.5331, 0.5338, 0.5098, 0.4656, 0.4937],
9e-2,
),
(
"D",
12,
"genextreme",
"ML",
[0.4414, 0.4695, 0.4861, 0.4838, 0.4877],
9e-2,
),
# reference results : xclim version where the test was implemented
(
"D",
1,
"genextreme",
"ML",
[0.6105, 0.6167, 0.5957, 0.5520, 0.5794],
2e-2,
),
(
"D",
1,
"genextreme",
"APP",
[-0.0259, -0.0141, -0.0080, -0.0098, 0.0089],
2e-2,
),
("D", 1, "fisk", "ML", [0.3514, 0.3741, 0.1349, 0.4332, 0.1724], 2e-2),
("D", 1, "fisk", "APP", [0.3321, 0.3477, 0.3536, 0.3468, 0.3723], 2e-2),
(
"D",
12,
"genextreme",
"ML",
[0.5131, 0.5442, 0.5645, 0.5660, 0.5720],
2e-2,
),
(
"D",
12,
"genextreme",
"APP",
[-0.0697, -0.0550, -0.0416, -0.0308, -0.0194],
2e-2,
),
("D", 12, "fisk", "ML", [0.2096, 0.2728, 0.3259, 0.3466, 0.2836], 2e-2),
("D", 12, "fisk", "APP", [0.2667, 0.2893, 0.3088, 0.3233, 0.3385], 2e-2),
(
"MS",
1,
"genextreme",
"ML",
[0.7315, -1.4919, -0.5405, 0.9965, -0.7449],
2e-2,
),
(
"MS",
1,
"genextreme",
"APP",
[0.0979, -1.6806, -0.5345, 0.7355, -0.7583],
2e-2,
),
("MS", 1, "fisk", "ML", [0.5331, -1.5777, -0.4363, 0.2525, -0.8149], 2e-2),
("MS", 1, "fisk", "APP", [0.4663, -1.9076, -0.5362, 0.8070, -0.8035], 2e-2),
(
"MS",
12,
"genextreme",
"ML",
[-0.9795, -1.0398, -1.9019, -1.6970, -1.4761],
2e-2,
),
(
"MS",
12,
"genextreme",
"APP",
[-0.9095, -1.0996, -1.9207, -2.2665, -2.1746],
2e-2,
),
(
"MS",
12,
"fisk",
"ML",
[-1.0776, -1.0827, -1.9333, -1.7764, -1.8391],
2e-2,
),
(
"MS",
12,
"fisk",
"APP",
[-0.9607, -1.1265, -1.7004, -1.8747, -1.8132],
2e-2,
),
],
)
def test_standardized_streamflow_index(
self, open_dataset, freq, window, dist, method, values, diff_tol
):
ds = open_dataset("Raven/q_sim.nc")
q = ds.q_obs.rename("q")
q_cal = ds.q_sim.rename("q").fillna(ds.q_sim.mean())
if freq == "D":
q = q.sel(time=slice("2008-01-01", "2008-01-30")).fillna(ds.q_obs.mean())
else:
q = q.sel(time=slice("2008-01-01", "2009-12-31")).fillna(ds.q_obs.mean())
fitkwargs = {"floc": 0} if method == "APP" else {}
params = xci.stats.standardized_index_fit_params(
q_cal,
freq=freq,
window=window,
dist=dist,
method=method,
fitkwargs=fitkwargs,
zero_inflated=True,
)
ssi = xci.standardized_streamflow_index(q, params=params)
ssi = ssi.isel(time=slice(-11, -1, 2)).values.flatten()

np.testing.assert_allclose(ssi, values, rtol=0, atol=diff_tol)

@pytest.mark.parametrize(
"indexer",
[
Expand Down
12 changes: 12 additions & 0 deletions xclim/data/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@
"title": "Précipitation liquide accumulée totale",
"abstract": "Précipitation liquide accumulée totale. Les précipitations sont considérées liquides lorsque la température quotidienne moyenne est au-dessus de 0°C."
},
"SGI": {
"long_name": "Indice d'eau souterraine standardisé (SGI)",
"description": "Niveau de l'eau souterraine sur une fenêtre mobile de {window} {freq:nom}, normalisé telle que la moyenne du SGI est 0 pour les données de calibration. L'unité de la fenêtre 'X' est l'unité de temps déterminée par la fréquence de rééchantillonage,",
"title": "Indice d'eau souterraine standardisé (SGI)",
"abstract": "Niveau de l'eau souterraine sur une fenêtre mobile, normalisée telle que la moyenne du SGI est 0 pour les données de calibration. L'unité de la fenêtre est l'unité de temps déterminée par la fréquence de rééchantillonnage."
},
"SOLIDPRCPTOT": {
"long_name": "Précipitation totale lorsque la température moyenne est en dessous ou égale à {thresh}",
"description": "Précipitation solide totale {freq:f}, estimée comme la précipitation lorsque la température moyenne est en dessous ou égale à {thresh}.",
Expand All @@ -287,6 +293,12 @@
"title": "Indice de précipitation évapotranspiration standardisé (SPEI)",
"abstract": "Budget d'eau (précipitation - évapotranspiration) sur une fenêtre mobile, normalisé tel que la moyenne du SPEI est 0 pour les données de calibration. L'unité de la fenêtre est l'unité de temps déterminée par la fréquence de rééchantillonnage."
},
"SSI": {
"long_name": "Indice d'écoulement standardisé (SSI)",
"description": "Écoulement sur une fenêtre mobile de {window} {freq:nom}, normalisée telle que la moyenne du SSI est 0 pour les données de calibration. L'unité de la fenêtre 'X' est l'unité de temps déterminée par la fréquence de rééchantillonage,",
"title": "Indice d'écoulement standardisé (SSI)",
"abstract": "Écoulement sur une fenêtre mobile, normalisée telle que la moyenne du SSI est 0 pour les données de calibration. L'unité de la fenêtre est l'unité de temps déterminée par la fréquence de rééchantillonnage."
},
"DC": {
"long_name": "Indice de sécheresse",
"description": "Code numérique estimant la teneur en humidité moyenne des couches organiques.",
Expand Down
49 changes: 48 additions & 1 deletion xclim/indicators/land/_streamflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
from xclim.core.cfchecks import check_valid
from xclim.core.indicator import ResamplingIndicator
from xclim.core.units import declare_units
from xclim.indices import base_flow_index, generic, rb_flashiness_index
from xclim.indices import (
base_flow_index,
generic,
rb_flashiness_index,
standardized_groundwater_index,
standardized_streamflow_index,
)

__all__ = [
"base_flow_index",
"doy_qmax",
"doy_qmin",
"rb_flashiness_index",
"standardized_groundwater_index",
"standardized_streamflow_index",
]


Expand All @@ -28,6 +36,13 @@ def cfcheck(q):
check_valid(q, "standard_name", "water_volume_transport_in_river_channel")


class StandardizedStreamflowIndexes(ResamplingIndicator):
LamAdr marked this conversation as resolved.
Show resolved Hide resolved
"""Resampling but flexible inputs indicators."""

src_freq = ["D", "MS"]
context = "hydro"


base_flow_index = Streamflow(
title="Base flow index",
identifier="base_flow_index",
Expand Down Expand Up @@ -74,3 +89,35 @@ def cfcheck(q):
compute=declare_units(da="[discharge]")(generic.select_resample_op),
parameters={"op": generic.doymin, "out_units": None},
)


standardized_streamflow_index = StandardizedStreamflowIndexes(
title="Standardized Streamflow Index (SSI)",
identifier="ssi",
units="",
standard_name="ssi",
long_name="Standardized Streamflow Index (SSI)",
description="Streamflow over a moving {window}-X window, normalized such that SSI averages to 0 for "
"calibration data. The window unit `X` is the minimal time period defined by resampling frequency {freq}.",
abstract="Streamflow over a moving window, normalized such that SSI averages to 0 for the calibration data. "
"The window unit `X` is the minimal time period defined by the resampling frequency.",
cell_methods="",
keywords="streamflow",
compute=standardized_streamflow_index,
)


standardized_groundwater_index = StandardizedStreamflowIndexes(
title="Standardized Groundwater Index (SGI)",
identifier="sgi",
units="",
standard_name="sgi",
long_name="Standardized Groundwater Index (SGI)",
description="Groundwater over a moving {window}-X window, normalized such that SGI averages to 0 for "
"calibration data. The window unit `X` is the minimal time period defined by resampling frequency {freq}.",
abstract="Groundwater over a moving window, normalized such that SGI averages to 0 for the calibration data. "
"The window unit `X` is the minimal time period defined by the resampling frequency.",
cell_methods="",
keywords="groundwater",
compute=standardized_groundwater_index,
)
2 changes: 1 addition & 1 deletion xclim/indices/_agro.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ def standardized_precipitation_index(
>>> from xclim.indices import standardized_precipitation_index
>>> ds = xr.open_dataset(path_to_pr_file)
>>> pr = ds.pr
>>> cal_start, cal_end = "1990-05-01", "1990-08-31"
>>> cal_start, cal_end = "1990-05-01", "1992-06-01"
>>> spi_3 = standardized_precipitation_index(
... pr,
... freq="MS",
Expand Down
Loading
Loading