From 68212db032f6bab2dc04738e114fa0257566f8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 13 Nov 2024 13:37:33 -0500 Subject: [PATCH 1/5] streamflow uses `strf` instead of `q` --- CHANGELOG.rst | 3 +- tests/conftest.py | 8 ++-- tests/test_generic.py | 28 +++++++------- tests/test_generic_indicators.py | 35 +++++++++-------- tests/test_hydrology.py | 20 +++++----- tests/test_land.py | 26 ++++++------- tests/test_stats.py | 8 ++-- xclim/core/indicator.py | 2 +- xclim/data/fr.json | 4 +- xclim/data/variables.yml | 2 +- xclim/indicators/land/_streamflow.py | 24 ++++++------ xclim/indices/_hydrology.py | 56 ++++++++++++++-------------- 12 files changed, 111 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f85d72b51..d0f183b60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,7 +4,7 @@ Changelog v0.54.0 (unreleased) -------------------- -Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bourgault (:user:`aulemahal`). +Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bourgault (:user:`aulemahal`), Éric Dupuis (:user:`coxipi`). Breaking changes ---------------- @@ -17,6 +17,7 @@ Bug fixes Internal changes ^^^^^^^^^^^^^^^^ * Changed french translations with word "pluvieux" to "avec précipitations". (:issue:`1960`, :pull:`1994`). +* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml`` (:issue:`1912`, :pull:``) v0.53.2 (2024-10-31) -------------------- diff --git a/tests/conftest.py b/tests/conftest.py index ba3597b17..ca245e0b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -140,8 +140,8 @@ def _pr_series(values, start="1/1/2000", units="kg m-2 s-1"): @pytest.fixture -def q_series(): - def _q_series(values, start="1/1/2000", units="m3 s-1"): +def strf_series(): + def _strf_series(values, start="1/1/2000", units="m3 s-1"): coords = pd.date_range(start, periods=len(values), freq="D") return xr.DataArray( values, @@ -154,11 +154,11 @@ def _q_series(values, start="1/1/2000", units="m3 s-1"): }, ) - return _q_series + return _strf_series @pytest.fixture -def ndq_series(random): +def ndstrf_series(random): nx, ny, nt = 2, 3, 5000 x = np.arange(0, nx) y = np.arange(0, ny) diff --git a/tests/test_generic.py b/tests/test_generic.py index 8191624a9..d95972bcc 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -14,27 +14,27 @@ class TestSelectResampleOp: - def test_month(self, q_series): - q = q_series(np.arange(1000)) + def test_month(self, strf_series): + q = strf_series(np.arange(1000)) o = generic.select_resample_op(q, "count", freq="YS", month=3) np.testing.assert_array_equal(o, 31) - def test_season_default(self, q_series): + def test_season_default(self, strf_series): # Will use freq='YS', so count J, F and D of each year. - q = q_series(np.arange(1000)) + q = strf_series(np.arange(1000)) o = generic.select_resample_op(q, "min", season="DJF") assert o[0] == 0 assert o[1] == 366 - def test_season(self, q_series): - q = q_series(np.arange(1000)) + def test_season(self, strf_series): + q = strf_series(np.arange(1000)) o = generic.select_resample_op(q, "count", freq="YS-DEC", season="DJF") assert o[0] == 31 + 29 class TestSelectRollingResampleOp: - def test_rollingmax(self, q_series): - q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_rollingmax(self, strf_series): + q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "max", window=14, window_center=False, window_op="mean" ) @@ -48,8 +48,8 @@ def test_rollingmax(self, q_series): ) assert o.attrs["units"] == "m3 s-1" - def test_rollingmaxindexer(self, q_series): - q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_rollingmaxindexer(self, strf_series): + q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "min", window=14, window_center=False, window_op="max", season="DJF" ) @@ -58,8 +58,8 @@ def test_rollingmaxindexer(self, q_series): ) # 14th day for 1st year, then Jan 1st for the next two assert o.attrs["units"] == "m3 s-1" - def test_freq(self, q_series): - q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_freq(self, strf_series): + q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "max", window=3, window_center=True, window_op="integral", freq="MS" ) @@ -88,13 +88,13 @@ def test_simple(self, tas_series): class TestFlowGeneric: - def test_doyminmax(self, q_series): + def test_doyminmax(self, strf_series): a = np.ones(365) a[9] = 2 a[19] = -2 a[39] = 4 a[49] = -4 - q = q_series(a) + q = strf_series(a) dmx = generic.doymax(q) dmn = generic.doymin(q) assert dmx.values == [40] diff --git a/tests/test_generic_indicators.py b/tests/test_generic_indicators.py index 6411e1479..427e2326c 100644 --- a/tests/test_generic_indicators.py +++ b/tests/test_generic_indicators.py @@ -31,8 +31,8 @@ def test_ndim(self, pr_ndseries, random): assert out.shape == (2, 1, 2) np.testing.assert_array_equal(out.isnull(), False) - def test_options(self, q_series, random): - q = q_series(random.random(19)) + def test_options(self, strf_series, random): + q = strf_series(random.random(19)) out = generic.fit(q, dist="norm") np.testing.assert_array_equal(out.isnull(), False) @@ -42,9 +42,9 @@ def test_options(self, q_series, random): class TestReturnLevel: - def test_seasonal(self, ndq_series): + def test_seasonal(self, ndstrf_series): out = generic.return_level( - ndq_series, mode="max", t=[2, 5], dist="gamma", season="DJF" + ndstrf_series, mode="max", t=[2, 5], dist="gamma", season="DJF" ) assert out.description == ( @@ -60,8 +60,8 @@ def test_any_variable(self, pr_series, random): out = generic.return_level(pr, mode="max", t=2, dist="gamma") assert out.units == pr.units - def test_no_indexer(self, ndq_series): - out = generic.return_level(ndq_series, mode="max", t=[2, 5], dist="gamma") + def test_no_indexer(self, ndstrf_series): + out = generic.return_level(ndstrf_series, mode="max", t=[2, 5], dist="gamma") assert out.description in [ "Frequency analysis for the maximal annual 1-day value estimated using the gamma distribution." ] @@ -69,12 +69,14 @@ def test_no_indexer(self, ndq_series): assert out.shape == (2, 2, 3) # nrt, nx, ny np.testing.assert_array_equal(out.isnull(), False) - def test_q27(self, ndq_series): - out = generic.return_level(ndq_series, mode="max", t=2, dist="gamma", window=7) + def test_q27(self, ndstrf_series): + out = generic.return_level( + ndstrf_series, mode="max", t=2, dist="gamma", window=7 + ) assert out.shape == (1, 2, 3) - def test_empty(self, ndq_series): - q = ndq_series.copy() + def test_empty(self, ndstrf_series): + q = ndstrf_series.copy() q[:, 0, 0] = np.nan out = generic.return_level( q, mode="max", t=2, dist="genextreme", window=6, freq="YS" @@ -94,13 +96,16 @@ def test_simple(self, pr_series, random, op, word): out = generic.stats(pr, freq="YS", op=op) assert out.long_name == f"{word} of variable" - def test_ndq(self, ndq_series): - out = generic.stats(ndq_series, freq="YS", op="min", season="MAM") + def test_ndq(self, ndstrf_series): + out = generic.stats(ndstrf_series, freq="YS", op="min", season="MAM") assert out.attrs["units"] == "m3 s-1" - def test_missing(self, ndq_series): - a = ndq_series.where( - ~((ndq_series.time.dt.dayofyear == 5) & (ndq_series.time.dt.year == 1902)) + def test_missing(self, ndstrf_series): + a = ndstrf_series.where( + ~( + (ndstrf_series.time.dt.dayofyear == 5) + & (ndstrf_series.time.dt.year == 1902) + ) ) assert a.shape == (5000, 2, 3) out = generic.stats(a, op="max", month=1) diff --git a/tests/test_hydrology.py b/tests/test_hydrology.py index 0572d12e1..a7ae5fe4d 100644 --- a/tests/test_hydrology.py +++ b/tests/test_hydrology.py @@ -6,19 +6,19 @@ class TestBaseFlowIndex: - def test_simple(self, q_series): + def test_simple(self, strf_series): a = np.zeros(365) + 10 a[10:17] = 1 - q = q_series(a) + q = strf_series(a) out = xci.base_flow_index(q) np.testing.assert_array_equal(out, 1.0 / a.mean()) class TestRBIndex: - def test_simple(self, q_series): + def test_simple(self, strf_series): a = np.zeros(365) a[10] = 10 - q = q_series(a) + q = strf_series(a) out = xci.rb_flashiness_index(q) np.testing.assert_array_equal(out, 2) @@ -73,30 +73,30 @@ def test_simple(self, snw_series, pr_series): class TestFlowindex: - def test_simple(self, q_series): + def test_simple(self, strf_series): a = np.ones(365 * 2) * 10 a[10:50] = 50 - q = q_series(a) + q = strf_series(a) out = xci.flow_index(q, 0.95) np.testing.assert_array_equal(out, 5) class TestHighflowfrequency: - def test_simple(self, q_series): + def test_simple(self, strf_series): a = np.zeros(365 * 2) a[50:60] = 10 a[200:210] = 20 - q = q_series(a) + q = strf_series(a) out = xci.high_flow_frequency(q, 9, freq="YS") np.testing.assert_array_equal(out, [20, 0]) class TestLowflowfrequency: - def test_simple(self, q_series): + def test_simple(self, strf_series): a = np.ones(365 * 2) * 10 a[50:60] = 1 a[200:210] = 1 - q = q_series(a) + q = strf_series(a) out = xci.low_flow_frequency(q, 0.2, freq="YS") np.testing.assert_array_equal(out, [20, 0]) diff --git a/tests/test_land.py b/tests/test_land.py index aedc1563b..4af5c86ca 100644 --- a/tests/test_land.py +++ b/tests/test_land.py @@ -8,27 +8,27 @@ from xclim import land -def test_base_flow_index(ndq_series): - out = land.base_flow_index(ndq_series, freq="YS") +def test_base_flow_index(ndstrf_series): + out = land.base_flow_index(ndstrf_series, freq="YS") assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) -def test_rb_flashiness_index(ndq_series): - out = land.base_flow_index(ndq_series, freq="YS") +def test_rb_flashiness_index(ndstrf_series): + out = land.base_flow_index(ndstrf_series, freq="YS") assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) -def test_qdoy_max(ndq_series, q_series): - out = land.doy_qmax(ndq_series, freq="YS", season="JJA") +def test_qdoy_max(ndstrf_series, strf_series): + out = land.doy_strfmax(ndstrf_series, freq="YS", season="JJA") assert out.attrs["units"] == "1" a = np.ones(450) a[100] = 2 - out = land.doy_qmax(q_series(a), freq="YS") + out = land.doy_strfmax(strf_series(a), freq="YS") assert out[0] == 101 @@ -68,20 +68,20 @@ def test_snw_storm_days(snw_series): np.testing.assert_array_equal(out, [9, np.nan]) -def test_flow_index(q_series): +def test_flow_index(strf_series): a = np.ones(365 * 2) * 10 a[10:50] = 50 - q = q_series(a) + q = strf_series(a) out = land.flow_index(q, p=0.95) np.testing.assert_array_equal(out, 5) -def test_high_flow_frequency(q_series): +def test_high_flow_frequency(strf_series): a = np.zeros(366 * 2) * 10 a[50:60] = 10 a[200:210] = 20 - q = q_series(a) + q = strf_series(a) out = land.high_flow_frequency( q, threshold_factor=9, @@ -90,10 +90,10 @@ def test_high_flow_frequency(q_series): np.testing.assert_array_equal(out, [20, 0, np.nan]) -def test_low_flow_frequency(q_series): +def test_low_flow_frequency(strf_series): a = np.ones(366 * 2) * 10 a[50:60] = 1 a[200:210] = 1 - q = q_series(a) + q = strf_series(a) out = land.low_flow_frequency(q, threshold_factor=0.2, freq="YS") np.testing.assert_array_equal(out, [20, 0, np.nan]) diff --git a/tests/test_stats.py b/tests/test_stats.py index 29a40b3f0..ef41b5955 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -266,8 +266,8 @@ def test_pwm_fit(self, dist, use_dask, random): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") -def test_frequency_analysis(ndq_series, use_dask): - q = ndq_series.copy() +def test_frequency_analysis(ndstrf_series, use_dask): + q = ndstrf_series.copy() q[:, 0, 0] = np.nan if use_dask: q = q.chunk() @@ -291,9 +291,9 @@ def test_frequency_analysis(ndq_series, use_dask): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") -def test_frequency_analysis_lmoments(ndq_series, use_dask): +def test_frequency_analysis_lmoments(ndstrf_series, use_dask): lmom = pytest.importorskip("lmoments3.distr") - q = ndq_series.copy() + q = ndstrf_series.copy() q[:, 0, 0] = np.nan if use_dask: q = q.chunk() diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 1822d04c4..02938058d 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1205,7 +1205,7 @@ def json(cls, args=None): param["choices"] = list(param["choices"]) if param["default"] is _empty_default: del param["default"] - elif callable(param): # Rare special case (doy_qmax and doy_qmin). + elif callable(param): # Rare special case (doy_strfmax and doy_strfmin). out["parameters"][name] = f"{param.__module__}.{param.__name__}" return out diff --git a/xclim/data/fr.json b/xclim/data/fr.json index d42c6b437..84a81e531 100644 --- a/xclim/data/fr.json +++ b/xclim/data/fr.json @@ -716,13 +716,13 @@ "title": "Calcul les paramètres d'une distribution univariée pour un ensemble de données", "abstract": "" }, - "DOY_QMAX": { + "DOY_STRFMAX": { "long_name": "Jour de l'année du maximum du débit en {indexer:nom}", "description": "Jour de l'année du maximum du débit en {indexer:nom}.", "title": "Jour de l'année du maximum du débit", "abstract": "" }, - "DOY_QMIN": { + "DOY_STRFMIN": { "long_name": "Jour de l'année du minimum du débit en {indexer:nom}", "description": "Jour de l'année du minimum du débit en {indexer:nom}.", "title": "Jour de l'année du minimum du débit", diff --git a/xclim/data/variables.yml b/xclim/data/variables.yml index a02194514..29362380e 100644 --- a/xclim/data/variables.yml +++ b/xclim/data/variables.yml @@ -237,7 +237,7 @@ variables: standard_name: lwe_thickness_of_snow_amount data_flags: - negative_accumulation_values: - streamflow: + strf: canonical_units: m3 s-1 cell_methods: "time: mean" description: The amount of water, in all phases, flowing in the river channel and flood plain. diff --git a/xclim/indicators/land/_streamflow.py b/xclim/indicators/land/_streamflow.py index fc73699af..0e94b414b 100644 --- a/xclim/indicators/land/_streamflow.py +++ b/xclim/indicators/land/_streamflow.py @@ -19,8 +19,8 @@ __all__ = [ "base_flow_index", - "doy_qmax", - "doy_qmin", + "doy_strfmax", + "doy_strfmin", "flow_index", "high_flow_frequency", "low_flow_frequency", @@ -37,8 +37,8 @@ class Streamflow(ResamplingIndicator): # TODO: TJS: The signature of this method seems wrong. Should it be `def cfcheck(cls, q):` or something else? Is it a static method? @staticmethod - def cfcheck(q): - check_valid(q, "standard_name", "water_volume_transport_in_river_channel") + def cfcheck(strf): + check_valid(strf, "standard_name", "water_volume_transport_in_river_channel") base_flow_index = Streamflow( @@ -65,10 +65,10 @@ def cfcheck(q): ) -doy_qmax = Streamflow( +doy_strfmax = Streamflow( title="Day of year of the maximum streamflow", identifier="doy_qmax", - var_name="q{indexer}_doy_qmax", + var_name="strf{indexer}_doy_strfmax", long_name="Day of the year of the maximum streamflow over {indexer}", description="Day of the year of the maximum streamflow over {indexer}.", units="", @@ -77,10 +77,10 @@ def cfcheck(q): ) -doy_qmin = Streamflow( +doy_strfmin = Streamflow( title="Day of year of the minimum streamflow", - identifier="doy_qmin", - var_name="q{indexer}_doy_qmin", + identifier="doy_strfmin", + var_name="strf{indexer}_doy_strfmin", long_name="Day of the year of the minimum streamflow over {indexer}", description="Day of the year of the minimum streamflow over {indexer}.", units="", @@ -93,7 +93,7 @@ def cfcheck(q): context="hydro", title="Flow index", identifier="flow_index", - var_name="q_flow_index", + var_name="strf_flow_index", long_name="Flow index", description="{p}th percentile normalized by the median flow.", units="1", @@ -104,7 +104,7 @@ def cfcheck(q): high_flow_frequency = Streamflow( title="High flow frequency", identifier="high_flow_frequency", - var_name="q_high_flow_frequency", + var_name="strf_high_flow_frequency", long_name="High flow frequency", description="{freq} frequency of flows greater than {threshold_factor} times the median flow.", units="days", @@ -115,7 +115,7 @@ def cfcheck(q): low_flow_frequency = Streamflow( title="Low flow frequency", identifier="low_flow_frequency", - var_name="q_low_flow_frequency", + var_name="strf_low_flow_frequency", long_name="Low flow frequency", description="{freq} frequency of flows smaller than a fraction ({threshold_factor}) of the mean flow.", units="days", diff --git a/xclim/indices/_hydrology.py b/xclim/indices/_hydrology.py index 23d320cbc..0781b524e 100644 --- a/xclim/indices/_hydrology.py +++ b/xclim/indices/_hydrology.py @@ -26,15 +26,15 @@ ] -@declare_units(q="[discharge]") -def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: +@declare_units(strf="[discharge]") +def base_flow_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: r"""Base flow index. Return the base flow index, defined as the minimum 7-day average flow divided by the mean flow. Parameters ---------- - q : xarray.DataArray + strf : xarray.DataArray Rate of river discharge. freq : str Resampling frequency. @@ -61,8 +61,8 @@ def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: \mathrm{CMA}_7(q_i) = \frac{\sum_{j=i-3}^{i+3} q_j}{7} """ - m7 = q.rolling(time=7, center=True).mean(skipna=False).resample(time=freq) - mq = q.resample(time=freq) + m7 = strf.rolling(time=7, center=True).mean(skipna=False).resample(time=freq) + mq = strf.resample(time=freq) m7m = m7.min(dim="time") out = m7m / mq.mean(dim="time") @@ -70,8 +70,8 @@ def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: return out -@declare_units(q="[discharge]") -def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: +@declare_units(strf="[discharge]") +def rb_flashiness_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: r"""Richards-Baker flashiness index. Measures oscillations in flow relative to total flow, quantifying the frequency and rapidity of short term changes @@ -79,7 +79,7 @@ def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: Parameters ---------- - q : xarray.DataArray + strf : xarray.DataArray Rate of river discharge. freq : str Resampling frequency. @@ -101,8 +101,8 @@ def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: ---------- :cite:cts:`baker_new_2004` """ - d = np.abs(q.diff(dim="time")).resample(time=freq) - mq = q.resample(time=freq) + d = np.abs(strf.diff(dim="time")).resample(time=freq) + mq = strf.resample(time=freq) out = d.sum(dim="time") / mq.sum(dim="time") out.attrs["units"] = "" return out @@ -285,8 +285,8 @@ def melt_and_precip_max( return out -@declare_units(q="[discharge]") -def flow_index(q: xr.DataArray, p: float = 0.95) -> xr.DataArray: +@declare_units(strf="[discharge]") +def flow_index(strf: xr.DataArray, p: float = 0.95) -> xr.DataArray: """ Flow index @@ -294,7 +294,7 @@ def flow_index(q: xr.DataArray, p: float = 0.95) -> xr.DataArray: Parameters ---------- - q : xr.DataArray + strf : xr.DataArray Daily streamflow data. p : float Percentile for calculating the flow index, between 0 and 1. Default of 0.95 is for high flows. @@ -308,16 +308,16 @@ def flow_index(q: xr.DataArray, p: float = 0.95) -> xr.DataArray: ---------- :cite:cts:`Clausen2000` """ - qp = q.quantile(p, dim="time") - q_median = q.median(dim="time") - out = qp / q_median + strfp = strf.quantile(p, dim="time") + strf_median = strf.median(dim="time") + out = strfp / strf_median out.attrs["units"] = "1" return out -@declare_units(q="[discharge]") +@declare_units(strf="[discharge]") def high_flow_frequency( - q: xr.DataArray, threshold_factor: int = 9, freq: str = "YS-OCT" + strf: xr.DataArray, threshold_factor: int = 9, freq: str = "YS-OCT" ) -> xr.DataArray: """ High flow frequency. @@ -328,7 +328,7 @@ def high_flow_frequency( Parameters ---------- - q : xr.DataArray + strf : xr.DataArray Daily streamflow data. threshold_factor : int Factor by which the median flow is multiplied to set the high flow threshold, default is 9. @@ -344,15 +344,15 @@ def high_flow_frequency( ---------- :cite:cts:`addor2018,Clausen2000` """ - median_flow = q.median(dim="time") + median_flow = strf.median(dim="time") threshold = threshold_factor * median_flow - out = threshold_count(q, ">", threshold, freq=freq) - return to_agg_units(out, q, "count") + out = threshold_count(strf, ">", threshold, freq=freq) + return to_agg_units(out, strf, "count") -@declare_units(q="[discharge]") +@declare_units(strf="[discharge]") def low_flow_frequency( - q: xr.DataArray, threshold_factor: float = 0.2, freq: str = "YS-OCT" + strf: xr.DataArray, threshold_factor: float = 0.2, freq: str = "YS-OCT" ) -> xr.DataArray: """ Low flow frequency. @@ -363,7 +363,7 @@ def low_flow_frequency( Parameters ---------- - q : xr.DataArray + strf : xr.DataArray Daily streamflow data. threshold_factor : float Factor by which the mean flow is multiplied to set the low flow threshold, default is 0.2. @@ -379,7 +379,7 @@ def low_flow_frequency( ---------- :cite:cts:`Olden2003` """ - mean_flow = q.mean(dim="time") + mean_flow = strf.mean(dim="time") threshold = threshold_factor * mean_flow - out = threshold_count(q, "<", threshold, freq=freq) - return to_agg_units(out, q, "count") + out = threshold_count(strf, "<", threshold, freq=freq) + return to_agg_units(out, strf, "count") From fed82fe8f72830bebc13ea532188f76dc1d2c43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 13 Nov 2024 13:46:27 -0500 Subject: [PATCH 2/5] add pull number --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d0f183b60..1eca75004 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bo Breaking changes ---------------- * The minimum required version of `dask` has been increased to `2024.8.1`. (:issue:`1992`, :pull:`1991`). +* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml`` (:issue:`1912`, :pull:`1996`) Bug fixes ^^^^^^^^^ @@ -17,7 +18,6 @@ Bug fixes Internal changes ^^^^^^^^^^^^^^^^ * Changed french translations with word "pluvieux" to "avec précipitations". (:issue:`1960`, :pull:`1994`). -* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml`` (:issue:`1912`, :pull:``) v0.53.2 (2024-10-31) -------------------- From cd45420b2df262f5d667b3b7cffe089df2ff88b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 13 Nov 2024 13:47:41 -0500 Subject: [PATCH 3/5] more specific description change of breaking change --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1eca75004..511709983 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bo Breaking changes ---------------- * The minimum required version of `dask` has been increased to `2024.8.1`. (:issue:`1992`, :pull:`1991`). -* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml`` (:issue:`1912`, :pull:`1996`) +* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml``. Many hydroclimatic indicators (e.g. ``flow_index``) signature are changed as a result. (:issue:`1912`, :pull:`1996`) Bug fixes ^^^^^^^^^ From aa0d3a22e0fa7df8cfb541e8c21b18c41c655f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 14 Nov 2024 09:06:40 -0500 Subject: [PATCH 4/5] just q in variables.yml, nothing more --- CHANGELOG.rst | 2 +- tests/conftest.py | 8 ++-- tests/test_generic.py | 28 +++++++------- tests/test_generic_indicators.py | 35 ++++++++--------- tests/test_hydrology.py | 20 +++++----- tests/test_land.py | 26 ++++++------- tests/test_stats.py | 8 ++-- xclim/core/indicator.py | 2 +- xclim/data/fr.json | 4 +- xclim/data/variables.yml | 2 +- xclim/indicators/land/_streamflow.py | 24 ++++++------ xclim/indices/_hydrology.py | 56 ++++++++++++++-------------- 12 files changed, 105 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 511709983..26ac7f02f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,6 @@ Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bo Breaking changes ---------------- * The minimum required version of `dask` has been increased to `2024.8.1`. (:issue:`1992`, :pull:`1991`). -* Streamflow variables now expressed as `strf` instead of `q`, with a proper entry in ``variables.yml``. Many hydroclimatic indicators (e.g. ``flow_index``) signature are changed as a result. (:issue:`1912`, :pull:`1996`) Bug fixes ^^^^^^^^^ @@ -18,6 +17,7 @@ Bug fixes Internal changes ^^^^^^^^^^^^^^^^ * Changed french translations with word "pluvieux" to "avec précipitations". (:issue:`1960`, :pull:`1994`). +* `streamflow` entry replaced with `q` in ``variables.yml``. (:issue:`1912`, :pull:`1996`) v0.53.2 (2024-10-31) -------------------- diff --git a/tests/conftest.py b/tests/conftest.py index ca245e0b1..ba3597b17 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -140,8 +140,8 @@ def _pr_series(values, start="1/1/2000", units="kg m-2 s-1"): @pytest.fixture -def strf_series(): - def _strf_series(values, start="1/1/2000", units="m3 s-1"): +def q_series(): + def _q_series(values, start="1/1/2000", units="m3 s-1"): coords = pd.date_range(start, periods=len(values), freq="D") return xr.DataArray( values, @@ -154,11 +154,11 @@ def _strf_series(values, start="1/1/2000", units="m3 s-1"): }, ) - return _strf_series + return _q_series @pytest.fixture -def ndstrf_series(random): +def ndq_series(random): nx, ny, nt = 2, 3, 5000 x = np.arange(0, nx) y = np.arange(0, ny) diff --git a/tests/test_generic.py b/tests/test_generic.py index d95972bcc..8191624a9 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -14,27 +14,27 @@ class TestSelectResampleOp: - def test_month(self, strf_series): - q = strf_series(np.arange(1000)) + def test_month(self, q_series): + q = q_series(np.arange(1000)) o = generic.select_resample_op(q, "count", freq="YS", month=3) np.testing.assert_array_equal(o, 31) - def test_season_default(self, strf_series): + def test_season_default(self, q_series): # Will use freq='YS', so count J, F and D of each year. - q = strf_series(np.arange(1000)) + q = q_series(np.arange(1000)) o = generic.select_resample_op(q, "min", season="DJF") assert o[0] == 0 assert o[1] == 366 - def test_season(self, strf_series): - q = strf_series(np.arange(1000)) + def test_season(self, q_series): + q = q_series(np.arange(1000)) o = generic.select_resample_op(q, "count", freq="YS-DEC", season="DJF") assert o[0] == 31 + 29 class TestSelectRollingResampleOp: - def test_rollingmax(self, strf_series): - q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_rollingmax(self, q_series): + q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "max", window=14, window_center=False, window_op="mean" ) @@ -48,8 +48,8 @@ def test_rollingmax(self, strf_series): ) assert o.attrs["units"] == "m3 s-1" - def test_rollingmaxindexer(self, strf_series): - q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_rollingmaxindexer(self, q_series): + q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "min", window=14, window_center=False, window_op="max", season="DJF" ) @@ -58,8 +58,8 @@ def test_rollingmaxindexer(self, strf_series): ) # 14th day for 1st year, then Jan 1st for the next two assert o.attrs["units"] == "m3 s-1" - def test_freq(self, strf_series): - q = strf_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap + def test_freq(self, q_series): + q = q_series(np.arange(1, 366 + 365 + 365 + 1)) # 1st year is leap o = generic.select_rolling_resample_op( q, "max", window=3, window_center=True, window_op="integral", freq="MS" ) @@ -88,13 +88,13 @@ def test_simple(self, tas_series): class TestFlowGeneric: - def test_doyminmax(self, strf_series): + def test_doyminmax(self, q_series): a = np.ones(365) a[9] = 2 a[19] = -2 a[39] = 4 a[49] = -4 - q = strf_series(a) + q = q_series(a) dmx = generic.doymax(q) dmn = generic.doymin(q) assert dmx.values == [40] diff --git a/tests/test_generic_indicators.py b/tests/test_generic_indicators.py index 427e2326c..6411e1479 100644 --- a/tests/test_generic_indicators.py +++ b/tests/test_generic_indicators.py @@ -31,8 +31,8 @@ def test_ndim(self, pr_ndseries, random): assert out.shape == (2, 1, 2) np.testing.assert_array_equal(out.isnull(), False) - def test_options(self, strf_series, random): - q = strf_series(random.random(19)) + def test_options(self, q_series, random): + q = q_series(random.random(19)) out = generic.fit(q, dist="norm") np.testing.assert_array_equal(out.isnull(), False) @@ -42,9 +42,9 @@ def test_options(self, strf_series, random): class TestReturnLevel: - def test_seasonal(self, ndstrf_series): + def test_seasonal(self, ndq_series): out = generic.return_level( - ndstrf_series, mode="max", t=[2, 5], dist="gamma", season="DJF" + ndq_series, mode="max", t=[2, 5], dist="gamma", season="DJF" ) assert out.description == ( @@ -60,8 +60,8 @@ def test_any_variable(self, pr_series, random): out = generic.return_level(pr, mode="max", t=2, dist="gamma") assert out.units == pr.units - def test_no_indexer(self, ndstrf_series): - out = generic.return_level(ndstrf_series, mode="max", t=[2, 5], dist="gamma") + def test_no_indexer(self, ndq_series): + out = generic.return_level(ndq_series, mode="max", t=[2, 5], dist="gamma") assert out.description in [ "Frequency analysis for the maximal annual 1-day value estimated using the gamma distribution." ] @@ -69,14 +69,12 @@ def test_no_indexer(self, ndstrf_series): assert out.shape == (2, 2, 3) # nrt, nx, ny np.testing.assert_array_equal(out.isnull(), False) - def test_q27(self, ndstrf_series): - out = generic.return_level( - ndstrf_series, mode="max", t=2, dist="gamma", window=7 - ) + def test_q27(self, ndq_series): + out = generic.return_level(ndq_series, mode="max", t=2, dist="gamma", window=7) assert out.shape == (1, 2, 3) - def test_empty(self, ndstrf_series): - q = ndstrf_series.copy() + def test_empty(self, ndq_series): + q = ndq_series.copy() q[:, 0, 0] = np.nan out = generic.return_level( q, mode="max", t=2, dist="genextreme", window=6, freq="YS" @@ -96,16 +94,13 @@ def test_simple(self, pr_series, random, op, word): out = generic.stats(pr, freq="YS", op=op) assert out.long_name == f"{word} of variable" - def test_ndq(self, ndstrf_series): - out = generic.stats(ndstrf_series, freq="YS", op="min", season="MAM") + def test_ndq(self, ndq_series): + out = generic.stats(ndq_series, freq="YS", op="min", season="MAM") assert out.attrs["units"] == "m3 s-1" - def test_missing(self, ndstrf_series): - a = ndstrf_series.where( - ~( - (ndstrf_series.time.dt.dayofyear == 5) - & (ndstrf_series.time.dt.year == 1902) - ) + def test_missing(self, ndq_series): + a = ndq_series.where( + ~((ndq_series.time.dt.dayofyear == 5) & (ndq_series.time.dt.year == 1902)) ) assert a.shape == (5000, 2, 3) out = generic.stats(a, op="max", month=1) diff --git a/tests/test_hydrology.py b/tests/test_hydrology.py index a7ae5fe4d..0572d12e1 100644 --- a/tests/test_hydrology.py +++ b/tests/test_hydrology.py @@ -6,19 +6,19 @@ class TestBaseFlowIndex: - def test_simple(self, strf_series): + def test_simple(self, q_series): a = np.zeros(365) + 10 a[10:17] = 1 - q = strf_series(a) + q = q_series(a) out = xci.base_flow_index(q) np.testing.assert_array_equal(out, 1.0 / a.mean()) class TestRBIndex: - def test_simple(self, strf_series): + def test_simple(self, q_series): a = np.zeros(365) a[10] = 10 - q = strf_series(a) + q = q_series(a) out = xci.rb_flashiness_index(q) np.testing.assert_array_equal(out, 2) @@ -73,30 +73,30 @@ def test_simple(self, snw_series, pr_series): class TestFlowindex: - def test_simple(self, strf_series): + def test_simple(self, q_series): a = np.ones(365 * 2) * 10 a[10:50] = 50 - q = strf_series(a) + q = q_series(a) out = xci.flow_index(q, 0.95) np.testing.assert_array_equal(out, 5) class TestHighflowfrequency: - def test_simple(self, strf_series): + def test_simple(self, q_series): a = np.zeros(365 * 2) a[50:60] = 10 a[200:210] = 20 - q = strf_series(a) + q = q_series(a) out = xci.high_flow_frequency(q, 9, freq="YS") np.testing.assert_array_equal(out, [20, 0]) class TestLowflowfrequency: - def test_simple(self, strf_series): + def test_simple(self, q_series): a = np.ones(365 * 2) * 10 a[50:60] = 1 a[200:210] = 1 - q = strf_series(a) + q = q_series(a) out = xci.low_flow_frequency(q, 0.2, freq="YS") np.testing.assert_array_equal(out, [20, 0]) diff --git a/tests/test_land.py b/tests/test_land.py index 4af5c86ca..aedc1563b 100644 --- a/tests/test_land.py +++ b/tests/test_land.py @@ -8,27 +8,27 @@ from xclim import land -def test_base_flow_index(ndstrf_series): - out = land.base_flow_index(ndstrf_series, freq="YS") +def test_base_flow_index(ndq_series): + out = land.base_flow_index(ndq_series, freq="YS") assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) -def test_rb_flashiness_index(ndstrf_series): - out = land.base_flow_index(ndstrf_series, freq="YS") +def test_rb_flashiness_index(ndq_series): + out = land.base_flow_index(ndq_series, freq="YS") assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) -def test_qdoy_max(ndstrf_series, strf_series): - out = land.doy_strfmax(ndstrf_series, freq="YS", season="JJA") +def test_qdoy_max(ndq_series, q_series): + out = land.doy_qmax(ndq_series, freq="YS", season="JJA") assert out.attrs["units"] == "1" a = np.ones(450) a[100] = 2 - out = land.doy_strfmax(strf_series(a), freq="YS") + out = land.doy_qmax(q_series(a), freq="YS") assert out[0] == 101 @@ -68,20 +68,20 @@ def test_snw_storm_days(snw_series): np.testing.assert_array_equal(out, [9, np.nan]) -def test_flow_index(strf_series): +def test_flow_index(q_series): a = np.ones(365 * 2) * 10 a[10:50] = 50 - q = strf_series(a) + q = q_series(a) out = land.flow_index(q, p=0.95) np.testing.assert_array_equal(out, 5) -def test_high_flow_frequency(strf_series): +def test_high_flow_frequency(q_series): a = np.zeros(366 * 2) * 10 a[50:60] = 10 a[200:210] = 20 - q = strf_series(a) + q = q_series(a) out = land.high_flow_frequency( q, threshold_factor=9, @@ -90,10 +90,10 @@ def test_high_flow_frequency(strf_series): np.testing.assert_array_equal(out, [20, 0, np.nan]) -def test_low_flow_frequency(strf_series): +def test_low_flow_frequency(q_series): a = np.ones(366 * 2) * 10 a[50:60] = 1 a[200:210] = 1 - q = strf_series(a) + q = q_series(a) out = land.low_flow_frequency(q, threshold_factor=0.2, freq="YS") np.testing.assert_array_equal(out, [20, 0, np.nan]) diff --git a/tests/test_stats.py b/tests/test_stats.py index ef41b5955..29a40b3f0 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -266,8 +266,8 @@ def test_pwm_fit(self, dist, use_dask, random): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") -def test_frequency_analysis(ndstrf_series, use_dask): - q = ndstrf_series.copy() +def test_frequency_analysis(ndq_series, use_dask): + q = ndq_series.copy() q[:, 0, 0] = np.nan if use_dask: q = q.chunk() @@ -291,9 +291,9 @@ def test_frequency_analysis(ndstrf_series, use_dask): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") -def test_frequency_analysis_lmoments(ndstrf_series, use_dask): +def test_frequency_analysis_lmoments(ndq_series, use_dask): lmom = pytest.importorskip("lmoments3.distr") - q = ndstrf_series.copy() + q = ndq_series.copy() q[:, 0, 0] = np.nan if use_dask: q = q.chunk() diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 02938058d..1822d04c4 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1205,7 +1205,7 @@ def json(cls, args=None): param["choices"] = list(param["choices"]) if param["default"] is _empty_default: del param["default"] - elif callable(param): # Rare special case (doy_strfmax and doy_strfmin). + elif callable(param): # Rare special case (doy_qmax and doy_qmin). out["parameters"][name] = f"{param.__module__}.{param.__name__}" return out diff --git a/xclim/data/fr.json b/xclim/data/fr.json index 84a81e531..d42c6b437 100644 --- a/xclim/data/fr.json +++ b/xclim/data/fr.json @@ -716,13 +716,13 @@ "title": "Calcul les paramètres d'une distribution univariée pour un ensemble de données", "abstract": "" }, - "DOY_STRFMAX": { + "DOY_QMAX": { "long_name": "Jour de l'année du maximum du débit en {indexer:nom}", "description": "Jour de l'année du maximum du débit en {indexer:nom}.", "title": "Jour de l'année du maximum du débit", "abstract": "" }, - "DOY_STRFMIN": { + "DOY_QMIN": { "long_name": "Jour de l'année du minimum du débit en {indexer:nom}", "description": "Jour de l'année du minimum du débit en {indexer:nom}.", "title": "Jour de l'année du minimum du débit", diff --git a/xclim/data/variables.yml b/xclim/data/variables.yml index 29362380e..a02194514 100644 --- a/xclim/data/variables.yml +++ b/xclim/data/variables.yml @@ -237,7 +237,7 @@ variables: standard_name: lwe_thickness_of_snow_amount data_flags: - negative_accumulation_values: - strf: + streamflow: canonical_units: m3 s-1 cell_methods: "time: mean" description: The amount of water, in all phases, flowing in the river channel and flood plain. diff --git a/xclim/indicators/land/_streamflow.py b/xclim/indicators/land/_streamflow.py index 0e94b414b..fc73699af 100644 --- a/xclim/indicators/land/_streamflow.py +++ b/xclim/indicators/land/_streamflow.py @@ -19,8 +19,8 @@ __all__ = [ "base_flow_index", - "doy_strfmax", - "doy_strfmin", + "doy_qmax", + "doy_qmin", "flow_index", "high_flow_frequency", "low_flow_frequency", @@ -37,8 +37,8 @@ class Streamflow(ResamplingIndicator): # TODO: TJS: The signature of this method seems wrong. Should it be `def cfcheck(cls, q):` or something else? Is it a static method? @staticmethod - def cfcheck(strf): - check_valid(strf, "standard_name", "water_volume_transport_in_river_channel") + def cfcheck(q): + check_valid(q, "standard_name", "water_volume_transport_in_river_channel") base_flow_index = Streamflow( @@ -65,10 +65,10 @@ def cfcheck(strf): ) -doy_strfmax = Streamflow( +doy_qmax = Streamflow( title="Day of year of the maximum streamflow", identifier="doy_qmax", - var_name="strf{indexer}_doy_strfmax", + var_name="q{indexer}_doy_qmax", long_name="Day of the year of the maximum streamflow over {indexer}", description="Day of the year of the maximum streamflow over {indexer}.", units="", @@ -77,10 +77,10 @@ def cfcheck(strf): ) -doy_strfmin = Streamflow( +doy_qmin = Streamflow( title="Day of year of the minimum streamflow", - identifier="doy_strfmin", - var_name="strf{indexer}_doy_strfmin", + identifier="doy_qmin", + var_name="q{indexer}_doy_qmin", long_name="Day of the year of the minimum streamflow over {indexer}", description="Day of the year of the minimum streamflow over {indexer}.", units="", @@ -93,7 +93,7 @@ def cfcheck(strf): context="hydro", title="Flow index", identifier="flow_index", - var_name="strf_flow_index", + var_name="q_flow_index", long_name="Flow index", description="{p}th percentile normalized by the median flow.", units="1", @@ -104,7 +104,7 @@ def cfcheck(strf): high_flow_frequency = Streamflow( title="High flow frequency", identifier="high_flow_frequency", - var_name="strf_high_flow_frequency", + var_name="q_high_flow_frequency", long_name="High flow frequency", description="{freq} frequency of flows greater than {threshold_factor} times the median flow.", units="days", @@ -115,7 +115,7 @@ def cfcheck(strf): low_flow_frequency = Streamflow( title="Low flow frequency", identifier="low_flow_frequency", - var_name="strf_low_flow_frequency", + var_name="q_low_flow_frequency", long_name="Low flow frequency", description="{freq} frequency of flows smaller than a fraction ({threshold_factor}) of the mean flow.", units="days", diff --git a/xclim/indices/_hydrology.py b/xclim/indices/_hydrology.py index 0781b524e..23d320cbc 100644 --- a/xclim/indices/_hydrology.py +++ b/xclim/indices/_hydrology.py @@ -26,15 +26,15 @@ ] -@declare_units(strf="[discharge]") -def base_flow_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: +@declare_units(q="[discharge]") +def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: r"""Base flow index. Return the base flow index, defined as the minimum 7-day average flow divided by the mean flow. Parameters ---------- - strf : xarray.DataArray + q : xarray.DataArray Rate of river discharge. freq : str Resampling frequency. @@ -61,8 +61,8 @@ def base_flow_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: \mathrm{CMA}_7(q_i) = \frac{\sum_{j=i-3}^{i+3} q_j}{7} """ - m7 = strf.rolling(time=7, center=True).mean(skipna=False).resample(time=freq) - mq = strf.resample(time=freq) + m7 = q.rolling(time=7, center=True).mean(skipna=False).resample(time=freq) + mq = q.resample(time=freq) m7m = m7.min(dim="time") out = m7m / mq.mean(dim="time") @@ -70,8 +70,8 @@ def base_flow_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: return out -@declare_units(strf="[discharge]") -def rb_flashiness_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: +@declare_units(q="[discharge]") +def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray: r"""Richards-Baker flashiness index. Measures oscillations in flow relative to total flow, quantifying the frequency and rapidity of short term changes @@ -79,7 +79,7 @@ def rb_flashiness_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: Parameters ---------- - strf : xarray.DataArray + q : xarray.DataArray Rate of river discharge. freq : str Resampling frequency. @@ -101,8 +101,8 @@ def rb_flashiness_index(strf: xr.DataArray, freq: str = "YS") -> xr.DataArray: ---------- :cite:cts:`baker_new_2004` """ - d = np.abs(strf.diff(dim="time")).resample(time=freq) - mq = strf.resample(time=freq) + d = np.abs(q.diff(dim="time")).resample(time=freq) + mq = q.resample(time=freq) out = d.sum(dim="time") / mq.sum(dim="time") out.attrs["units"] = "" return out @@ -285,8 +285,8 @@ def melt_and_precip_max( return out -@declare_units(strf="[discharge]") -def flow_index(strf: xr.DataArray, p: float = 0.95) -> xr.DataArray: +@declare_units(q="[discharge]") +def flow_index(q: xr.DataArray, p: float = 0.95) -> xr.DataArray: """ Flow index @@ -294,7 +294,7 @@ def flow_index(strf: xr.DataArray, p: float = 0.95) -> xr.DataArray: Parameters ---------- - strf : xr.DataArray + q : xr.DataArray Daily streamflow data. p : float Percentile for calculating the flow index, between 0 and 1. Default of 0.95 is for high flows. @@ -308,16 +308,16 @@ def flow_index(strf: xr.DataArray, p: float = 0.95) -> xr.DataArray: ---------- :cite:cts:`Clausen2000` """ - strfp = strf.quantile(p, dim="time") - strf_median = strf.median(dim="time") - out = strfp / strf_median + qp = q.quantile(p, dim="time") + q_median = q.median(dim="time") + out = qp / q_median out.attrs["units"] = "1" return out -@declare_units(strf="[discharge]") +@declare_units(q="[discharge]") def high_flow_frequency( - strf: xr.DataArray, threshold_factor: int = 9, freq: str = "YS-OCT" + q: xr.DataArray, threshold_factor: int = 9, freq: str = "YS-OCT" ) -> xr.DataArray: """ High flow frequency. @@ -328,7 +328,7 @@ def high_flow_frequency( Parameters ---------- - strf : xr.DataArray + q : xr.DataArray Daily streamflow data. threshold_factor : int Factor by which the median flow is multiplied to set the high flow threshold, default is 9. @@ -344,15 +344,15 @@ def high_flow_frequency( ---------- :cite:cts:`addor2018,Clausen2000` """ - median_flow = strf.median(dim="time") + median_flow = q.median(dim="time") threshold = threshold_factor * median_flow - out = threshold_count(strf, ">", threshold, freq=freq) - return to_agg_units(out, strf, "count") + out = threshold_count(q, ">", threshold, freq=freq) + return to_agg_units(out, q, "count") -@declare_units(strf="[discharge]") +@declare_units(q="[discharge]") def low_flow_frequency( - strf: xr.DataArray, threshold_factor: float = 0.2, freq: str = "YS-OCT" + q: xr.DataArray, threshold_factor: float = 0.2, freq: str = "YS-OCT" ) -> xr.DataArray: """ Low flow frequency. @@ -363,7 +363,7 @@ def low_flow_frequency( Parameters ---------- - strf : xr.DataArray + q : xr.DataArray Daily streamflow data. threshold_factor : float Factor by which the mean flow is multiplied to set the low flow threshold, default is 0.2. @@ -379,7 +379,7 @@ def low_flow_frequency( ---------- :cite:cts:`Olden2003` """ - mean_flow = strf.mean(dim="time") + mean_flow = q.mean(dim="time") threshold = threshold_factor * mean_flow - out = threshold_count(strf, "<", threshold, freq=freq) - return to_agg_units(out, strf, "count") + out = threshold_count(q, "<", threshold, freq=freq) + return to_agg_units(out, q, "count") From 6acb4d2c8ce58151bc4d949b8b26c4f0f04baf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 14 Nov 2024 09:07:43 -0500 Subject: [PATCH 5/5] add the change --- xclim/data/variables.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/data/variables.yml b/xclim/data/variables.yml index a02194514..71a7b7dd0 100644 --- a/xclim/data/variables.yml +++ b/xclim/data/variables.yml @@ -237,7 +237,7 @@ variables: standard_name: lwe_thickness_of_snow_amount data_flags: - negative_accumulation_values: - streamflow: + q: canonical_units: m3 s-1 cell_methods: "time: mean" description: The amount of water, in all phases, flowing in the river channel and flood plain.