Skip to content

Commit

Permalink
Snow season and days above (#1708)
Browse files Browse the repository at this point in the history
<!--Please ensure the PR fulfills the following requirements! -->
<!-- If this is your first PR, make sure to add your details to the
AUTHORS.rst! -->
### Pull Request Checklist:
- [x] This PR addresses an already opened issue (for bug fixes /
features)
    - This PR fixes #1703
- [x] Tests for the changes have been added (for bug fixes / features)
- [x] (If applicable) Documentation has been added / updated (for bug
fixes / features)
- [x] CHANGES.rst has been updated (with summary of main changes)
- [x] Link to issue (:issue:`number`) and pull request (:pull:`number`)
has been added

### What kind of change does this PR introduce?

* Renames old `sn[dw]_season_length` to `sn[dw]_days_above`, which
better reflects what they actually do.
* Implement `sn[dw]_season_length` as an actual season length :
duraction between a start and an end, both defined as the first day of a
period of minimum length continuously above/under a given threshold.
* Rephrase the documentation of these indicators and of the start/end
variants.
* Update the threshold of `snw`, like we said we'd do in 0.47 (youpsi).

### Does this PR introduce a breaking change?
Yes. An indicator name now points to another computation.

### Other information:
  • Loading branch information
aulemahal authored Apr 18, 2024
2 parents 207bd67 + 218dae9 commit a40069b
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 99 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ Announcements
^^^^^^^^^^^^^
* `xclim` has migrated its development branch name from `master` to `main`. (:issue:`1667`, :pull:`1669`).

New indicators
^^^^^^^^^^^^^^
* New ``snw_season_length`` and ``snd_season_length`` computing the duration between the start and the end of the snow season, both defined as the first day of a continuous period with snow above/under a threshold. Previous versions of these indicators were renamed ``snw_days_above`` and ``snd_days_above`` to better reflect what they computed : the number of days with snow above a given threshold (with no notion of continuity). (:issue:`1703`, :pull:`1708`).

Breaking changes
^^^^^^^^^^^^^^^^
* The previously deprecated functions ``xclim.sdba.processing.construct_moving_yearly_window`` and ``xclim.sdba.processing.unpack_moving_yearly_window`` have been removed. These functions have been replaced by ``xclim.core.calendar.stack_periods`` and ``xclim.core.calendar.unstack_periods``. (:pull:`1717`).
* Indicators ``snw_season_length`` and ``snd_season_length`` have been modified, see above.

Bug fixes
^^^^^^^^^
Expand Down
16 changes: 11 additions & 5 deletions tests/test_indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -2820,21 +2820,27 @@ def test_nan_slices(self, snd_series, snw_series):


class TestSnowCover:
@pytest.mark.parametrize("length", [0, 10])
@pytest.mark.parametrize("length", [0, 15])
def test_snow_season_length(self, snd_series, snw_series, length):
a = np.zeros(366)
a[10 : 10 + length] = 0.3
a[20 : 20 + length] = 0.3
snd = snd_series(a)
# kg m-2 = 1000 kg m-3 * 1 m
snw = snw_series(1000 * a)

out = xci.snd_season_length(snd)
assert len(out) == 2
assert out[0] == length
if length == 0:
assert out.isnull().all()
else:
assert out[0] == length

out = xci.snw_season_length(snw)
assert len(out) == 2
assert out[0] == length
if length == 0:
assert out.isnull().all()
else:
assert out[0] == length

def test_continous_snow_season_start(self, snd_series, snw_series):
a = np.arange(366) / 100.0
Expand All @@ -2851,7 +2857,7 @@ def test_continous_snow_season_start(self, snd_series, snw_series):

out = xci.snw_season_start(snw)
assert len(out) == 2
np.testing.assert_array_equal(out, [snw.time.dt.dayofyear[0].data + 2, np.nan])
np.testing.assert_array_equal(out, [snw.time.dt.dayofyear[0].data + 1, np.nan])
for attr in ["units", "is_dayofyear", "calendar"]:
assert attr in out.attrs.keys()
assert out.attrs["units"] == ""
Expand Down
18 changes: 14 additions & 4 deletions tests/test_snow.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TestSnowDepthCoverDuration:
def test_simple(self, snd_series):
snd = snd_series(np.ones(110), start="2001-01-01")

out = land.snd_season_length(snd, freq="ME")
out = land.snd_days_above(snd, freq="ME")
assert out.units == "days"
np.testing.assert_array_equal(out, [31, 28, 31, np.nan])

Expand All @@ -30,16 +30,17 @@ class TestSnowWaterCoverDuration:
)
def test_simple(self, snw_series, factor, exp):
snw = snw_series(np.ones(110) * factor, start="2001-01-01")
out = land.snw_season_length(snw, freq="ME")
out = land.snw_days_above(snw, freq="ME")
assert out.units == "days"
np.testing.assert_array_equal(out, exp)


class TestContinuousSnowDepthCoverStartEnd:
class TestContinuousSnowDepthSeason:
def test_simple(self, snd_series):
a = np.zeros(365)
# snow depth
a[100:200] = 0.03
a[150:160] = 0
snd = snd_series(a, start="2001-07-01")
snd = snd.expand_dims(lat=[0, 1, 2])

Expand All @@ -51,12 +52,17 @@ def test_simple(self, snd_series):
assert out.units == ""
np.testing.assert_array_equal(out.isel(lat=0), snd.time.dt.dayofyear[200])

out = land.snd_season_length(snd)
assert out.units == "days"
np.testing.assert_array_equal(out.isel(lat=0), 100)


class TestContinuousSnowWaterCoverStartEnd:
class TestContinuousSnowWaterSeason:
def test_simple(self, snw_series):
a = np.zeros(365)
# snow amount
a[100:200] = 0.03 * 1000
a[150:160] = 0
snw = snw_series(a, start="2001-07-01")
snw = snw.expand_dims(lat=[0, 1, 2])

Expand All @@ -68,6 +74,10 @@ def test_simple(self, snw_series):
assert out.units == ""
np.testing.assert_array_equal(out.isel(lat=0), snw.time.dt.dayofyear[200])

out = land.snw_season_length(snw)
assert out.units == "days"
np.testing.assert_array_equal(out.isel(lat=0), 100)


class TestSndMaxDoy:
def test_simple(self, snd_series):
Expand Down
28 changes: 20 additions & 8 deletions xclim/data/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1005,40 +1005,52 @@
},
"SND_SEASON_LENGTH": {
"long_name": "Durée de couvert de neige",
"description": "Nombre {freq:m} de jours où l'épaisseur de neige est au-dessus ou égale à {thresh}.",
"description": "La saison débute lorsque l'épaisseur de neige est au-dessus de {thresh} durant {window} jours et se termine lorsqu'elle redescend sous {thresh} durant {window} jours.",
"title": "Durée du couvert de neige (épaisseur)",
"abstract": "Nombre de jours pendant lesquels l'épaisseur de neige est au-dessus ou égale à un seuil donné."
"abstract": "Durée de la saison de neige, qui débute lorsque la quantité de neige est au-dessus d'un seuil donné durant un nombre de jours donné et qui se termine lorsqu'elle redescend sous le seuil pour la même durée."
},
"SND_SEASON_START": {
"long_name": "Date du début du couvert de neige continu",
"description": "Première date à laquelle l'épaisseur de neige est au-dessus ou égale à {thresh} pendant au moins {window} jours consécutifs.",
"title": "Date du début du couvert de neige (épaisseur)",
"title": "Date de début du couvert de neige (épaisseur)",
"abstract": "Première date à partir de laquelle l'épaisseur de neige est au-dessus ou égale à un seuil donné pendant un nombre de jours consécutifs."
},
"SND_SEASON_END": {
"long_name": "Date de fin du couvert de neige continu",
"description": "Première date à laquelle l'épaisseur de neige passe sous {thresh} pendant au moins {window} jours consécutifs suite à l'établissement du couvert de neige.",
"title": "Date du fin du couvert de neige (épaisseur)",
"title": "Date de fin du couvert de neige (épaisseur)",
"abstract": "Première date à partir de laquelle l'épaisseur de neige est sous à un seuil donné pendant un nombre de jours consécutifs suite au début du couvert de neige."
},
"SND_DAYS_ABOVE": {
"long_name": "Nombre de jours avec de la neige au sol.",
"description": "Nombre {freq:m} de jours avec au moins {thresh} de neige au sol.",
"title": "Nombre de jours avec de la neige au sol (épaisseur)",
"abstract": "Nombre de jours avec une épaisseur de neige au sol au-dessus d'un seuil donné."
},
"SNW_SEASON_LENGTH": {
"long_name": "Durée de couvert de neige",
"description": "Nombre {freq:m} de jours où la quantité de neige est au-dessus ou égale à {thresh}.",
"description": "La saison débute lorsque la quantité de neige est au-dessus de {thresh} durant {window} jours et se termine lorsqu'elle redescend sous {thresh} durant {window} jours.",
"title": "Durée du couvert de neige (quantité)",
"abstract": "Nombre de jours pendant lesquels la quantité de neige est au-dessus ou égale à un seuil donné."
"abstract": "Durée de la saison de neige, qui débute lorsque l'épaisseur de neige est au-dessus d'un seuil donné durant un nombre de jours donnés et qui se termine lorsqu'elle redescend sous le seuil pour la même durée."
},
"SNW_SEASON_START": {
"long_name": "Date du début du couvert de neige continu",
"description": "Première date à laquelle la quantité de neige est au-dessus ou égale à {thresh} pendant au moins {window} jours consécutifs.",
"title": "Date du début du couvert de neige (quantité)",
"title": "Date de début du couvert de neige (quantité)",
"abstract": "Première date à partir de laquelle la quantité de neige est au-dessus ou égale à un seuil donné pendant un nombre de jours consécutifs."
},
"SNW_SEASON_END": {
"long_name": "Date de fin du couvert de neige continu",
"description": "Première date à laquelle la quantité de neige passe sous {thresh} pendant au moins {window} jours consécutifs suite à l'établissement du couvert de neige.",
"title": "Date du fin du couvert de neige (quantité)",
"title": "Date de fin du couvert de neige (quantité)",
"abstract": "Première date à partir de laquelle la quantité de neige est sous à un seuil donné pendant un nombre de jours consécutifs suite au début du couvert de neige."
},
"SNW_DAYS_ABOVE": {
"long_name": "Nombre de jours avec de la neige au sol.",
"description": "Nombre {freq:m} de jours avec au moins {thresh} de neige au sol.",
"title": "Nombre de jours avec de la neige au sol (quantité)",
"abstract": "Nombre de jours avec une quantité de neige au sol au-dessus d'un seuil donné."
},
"SND_MAX": {
"long_name": "Épaisseur de neige maximale",
"description": "Épaisseur maximale {freq:f} de la neige.",
Expand Down
41 changes: 31 additions & 10 deletions xclim/indicators/land/_snow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

__all__ = [
"blowing_snow",
"snd_days_above",
"snd_max_doy",
"snd_season_end",
"snd_season_length",
Expand All @@ -14,6 +15,7 @@
"snd_to_snw",
"snow_depth",
"snow_melt_we_max",
"snw_days_above",
"snw_max",
"snw_max_doy",
"snw_season_end",
Expand All @@ -39,27 +41,28 @@ class SnowWithIndexing(ResamplingIndicatorWithIndexing):


snd_season_length = SnowWithIndexing(
title="Snow cover duration (depth)",
identifier="snd_season_length",
units="days",
long_name="Snow cover duration",
description="The {freq} number of days with snow depth greater than or equal to {thresh}.",
abstract="Number of days when the snow depth is greater than or equal to a given threshold.",
description=(
"The duration of the snow season, starting with at least {window} days with snow depth above {thresh} "
"and ending with at least {window} days with snow depth under {thresh}."
),
compute=xci.snd_season_length,
)

snw_season_length = SnowWithIndexing(
title="Snow cover duration (amount)",
identifier="snw_season_length",
units="days",
long_name="Snow cover duration",
description="The {freq} number of days with snow amount greater than or equal to {thresh}.",
abstract="Number of days when the snow amount is greater than or equal to a given threshold.",
description=(
"The duration of the snow season, starting with at least {window} days with snow amount above {thresh} "
"and ending with at least {window} days with snow amount under {thresh}."
),
compute=xci.snw_season_length,
)

snd_season_start = Snow(
title="Start date of continuous snow depth cover",
identifier="snd_season_start",
standard_name="day_of_year",
long_name="Start date of continuous snow depth cover",
Expand All @@ -71,7 +74,6 @@ class SnowWithIndexing(ResamplingIndicatorWithIndexing):
)

snw_season_start = Snow(
title="Start date of continuous snow amount cover",
identifier="snw_season_start",
standard_name="day_of_year",
long_name="Start date of continuous snow amount cover",
Expand All @@ -83,7 +85,6 @@ class SnowWithIndexing(ResamplingIndicatorWithIndexing):
)

snd_season_end = Snow(
title="End date of continuous snow depth cover",
identifier="snd_season_end",
standard_name="day_of_year",
long_name="End date of continuous snow depth cover",
Expand All @@ -94,7 +95,6 @@ class SnowWithIndexing(ResamplingIndicatorWithIndexing):
)

snw_season_end = Snow(
title="End date of continuous snow amount cover",
identifier="snw_season_end",
standard_name="day_of_year",
long_name="End date of continuous snow amount cover",
Expand Down Expand Up @@ -231,3 +231,24 @@ class SnowWithIndexing(ResamplingIndicatorWithIndexing):
var_name="snd",
compute=xci.snw_to_snd,
)


snd_days_above = SnowWithIndexing(
title="Days with snow (depth)",
identifier="snd_days_above",
units="days",
long_name="Number of days with snow",
description="The {freq} number of days with snow depth greater than or equal to {thresh}.",
abstract="Number of days when the snow depth is greater than or equal to a given threshold.",
compute=xci.snd_days_above,
)

snw_days_above = SnowWithIndexing(
title="Days with snow (amount)",
identifier="snw_days_above",
units="days",
long_name="Number of days with snow",
description="The {freq} number of days with snow amount greater than or equal to {thresh}.",
abstract="Number of days when the snow amount is greater than or equal to a given threshold.",
compute=xci.snw_days_above,
)
Loading

0 comments on commit a40069b

Please sign in to comment.