diff --git a/pepsico/app_calc.py b/pepsico/app_calc.py index a1685db6..a8926cdd 100644 --- a/pepsico/app_calc.py +++ b/pepsico/app_calc.py @@ -174,6 +174,22 @@ def seasonal_wwc( ) ) wwc_units = "days" + if variable == "longest_dry_spell": + data_ds = ( + (labelled_season_data <= wet_threshold) + .where(~np.isnan(labelled_season_data)) + .groupby(labelled_season_data["seasons_starts"]) + .map(length_of_longest_spell, "T") + ) + wwc_units = "days" + if variable == "longest_wet_spell": + data_ds = ( + (labelled_season_data > wet_threshold) + .where(~np.isnan(labelled_season_data)) + .groupby(labelled_season_data["seasons_starts"]) + .map(length_of_longest_spell, "T") + ) + wwc_units = "days" # This is all a bit tedious but I didn't figure out another way to keep # seasons_ends and renaming time dim T # Can revisit later if this code has a future @@ -443,14 +459,17 @@ def _cumsum_flagged_diff(flagged_data, dim): # Special case coord.size = 1 cfd = flagged_data if cfd[dim].size != 1: + # it's ok to treat individual NaNs as 0s, + # but if all NaN then must return NaN + cfd_mask = ~(np.isnan(cfd).all(dim=dim)) # Points to apply diff to unflagged_and_ends = (flagged_data == 0) * 1 unflagged_and_ends[{dim: [0, -1]}] = 1 - cfd = cfd.cumsum(dim=dim).where(unflagged_and_ends, other = np.nan).where( + cfd = cfd.cumsum(dim=dim).where(unflagged_and_ends, other=np.nan).where( # first cumul point must be set to 0 lambda x: x[dim] != cfd[dim][0], other=0 - ).bfill(dim).diff(dim) + ).bfill(dim).diff(dim).where(cfd_mask) return cfd def count_days_in_spells( @@ -515,6 +534,7 @@ def length_of_longest_spell(flagged_data, dim): return _cumsum_flagged_diff(flagged_data, dim).max(dim=dim) + def mean_length_of_spells(flagged_data, dim, min_spell_length=1): """ Mean length of spells. diff --git a/pepsico/proj_wwc/layout.py b/pepsico/proj_wwc/layout.py index b3a4d437..6f1cb77c 100644 --- a/pepsico/proj_wwc/layout.py +++ b/pepsico/proj_wwc/layout.py @@ -48,8 +48,8 @@ def app_layout(): "frost_days", "Tmax_90", "Tmin_10", - # "longest_dry_spell", - # "longest_wet_spell", + "longest_dry_spell", + "longest_wet_spell", # "wet_day_persistence", # "dry_day_persistence", # "dry_spells_mean_length", @@ -67,14 +67,14 @@ def app_layout(): "Count of Frost Days", "Max Temperature 90th %-ile", "Min Temperature 10th %-ile", - # "Longest Dry Spell", - # "Longest Wet Spell", + "Longest Dry Spell", + "Longest Wet Spell", # "Mean Wet Day Persistence", # "Mean Dry Day Persistence", # "Mean Dry Spells Length", # "Median Dry Spells Length", ], - init=0, + init=1, )), Block("Definitions", "Frost <=", @@ -234,6 +234,13 @@ def app_layout(): temperature is greater than a user-defined thredhold. """ ]), + html.P([ + html.B("Longest Dry/Wet Spell (longest_dry/wet_spell):"),""" + Length of longest dry/wet spell in the season. A dry/wet spell + is defined as consecutive dry/wet days. A dry/wet day is defined + as lesser or equal / greather than a user-defined threshold. + """ + ]), ), lou.map(GLOBAL_CONFIG["zoom"]), diff --git a/pepsico/proj_wwc/maproom.py b/pepsico/proj_wwc/maproom.py index 4fea4702..c12ec4f2 100644 --- a/pepsico/proj_wwc/maproom.py +++ b/pepsico/proj_wwc/maproom.py @@ -100,8 +100,8 @@ def select_var(variable): # "rain_events", "dry_days", "wet_days", - # "longest_dry_spell", - # "longest_wet_spell", + "longest_dry_spell", + "longest_wet_spell", "wet_day_persistence", "dry_day_persistence", "dry_spells_mean_length", @@ -515,7 +515,7 @@ def map_attributes(data, variable): colorscale = CMAPS["prcp_anomaly"] if data.name in ["tasmin", "tasmax"]: colorscale = CMAPS["temp_anomaly"] - if variable in ["frost_days", "dry_days"]: + if variable in ["frost_days", "dry_days", "longest_dry_spell"]: colorscale = colorscale.reversed() map_amp = np.max(np.abs(data)).values colorscale = colorscale.rescaled(-1*map_amp, map_amp) diff --git a/pepsico/test/test_app_calc.py b/pepsico/test/test_app_calc.py index d3100d3a..d8169698 100644 --- a/pepsico/test/test_app_calc.py +++ b/pepsico/test/test_app_calc.py @@ -58,6 +58,19 @@ def test__cumsum_flagged_diff_coord_size_1(): assert spells0 == 0 +def test__cumsum_flagged_diff_allnan(): + + t = pd.date_range(start="2000-05-01", end="2000-05-10", freq="1D") + values = np.array([ + np.nan, np.nan, np.nan, np.nan, np.nan, + np.nan, np.nan, np.nan, np.nan, np.nan, + ]) + precip = xr.DataArray(values, coords={"T": t}) + spells = app_calc._cumsum_flagged_diff(precip, "T") + + assert np.isnan(spells).all() + + def test_spells_length(): t = pd.date_range(start="2000-05-01", end="2000-05-10", freq="1D") @@ -91,6 +104,18 @@ def test_length_of_longest_spell(): assert longest_spell == 3 +def test_length_of_longest_spell_allnan(): + + t = pd.date_range(start="2000-05-01", end="2000-05-10", freq="1D") + values = np.array([ + np.nan, np.nan, np.nan, np.nan, np.nan, + np.nan, np.nan, np.nan, np.nan, np.nan, + ]) + precip = xr.DataArray(values, coords={"T": t}) + longest_spell = app_calc.length_of_longest_spell(precip, "T") + + assert np.isnan(longest_spell) + def test_mean_length_of_spells(): t = pd.date_range(start="2000-05-01", end="2000-05-10", freq="1D")