diff --git a/pepsico/app_calc.py b/pepsico/app_calc.py index 36393f10..a1685db6 100644 --- a/pepsico/app_calc.py +++ b/pepsico/app_calc.py @@ -101,7 +101,8 @@ def seasonal_data(monthly_data, start_month, end_month, start_year=None, end_yea def seasonal_wwc( - labelled_season_data, variable, frost_threshold, wet_threshold + labelled_season_data, variable, frost_threshold, wet_threshold, hot_threshold, + warm_nights_spell, ): # Boolean variables need the additional where to return NaNs from False/0 to Nans # and the sum parameters for entirely NaNs seasons to remain NaNs and not turn to @@ -128,7 +129,7 @@ def seasonal_wwc( data_ds = labelled_season_data.groupby( labelled_season_data["seasons_starts"] ).mean() - wwc_units = "˚C" + wwc_units = "˚C" # It takes several 10s of minutes to get empirical quantiles maps if variable in ["Tmax_90", "Tmin_10"]: quantile = 0.1 if variable == "Tmin_10" else 0.9 @@ -162,6 +163,17 @@ def seasonal_wwc( .sum(skipna=True, min_count=1) ) wwc_units = "days" + if variable == "warm_nights": + data_ds = ( + (labelled_season_data > hot_threshold) + .where(~np.isnan(labelled_season_data)) + .groupby(labelled_season_data["seasons_starts"]) + .map( + count_days_in_spells, "T", min_spell_length=warm_nights_spell, + skipna=True, min_count=1, + ) + ) + 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 @@ -441,7 +453,9 @@ def _cumsum_flagged_diff(flagged_data, dim): ).bfill(dim).diff(dim) return cfd -def count_days_in_spells(flagged_data, dim, min_spell_length=1): +def count_days_in_spells( + flagged_data, dim, min_spell_length=1, skipna=None, min_count=None, +): """ Counts number of days in spells. Parameters @@ -466,9 +480,15 @@ def count_days_in_spells(flagged_data, dim, min_spell_length=1): -------- """ - return _cumsum_flagged_diff(flagged_data, dim).where( - lambda x : x >= min_spell_length - ).sum(dim=dim) + return ( + _cumsum_flagged_diff(flagged_data, dim) + # Nullify the spells that are too short to be counted + .where(lambda x : x >= min_spell_length, 0) + # Return NaNs to NaNs + .where(~np.isnan(flagged_data)) + # Include sum's skipna and min_count to properly deal with 0s vs NaNs + .sum(dim=dim, skipna=skipna, min_count=min_count) + ) def length_of_longest_spell(flagged_data, dim): """ Length of longest spells. diff --git a/pepsico/proj_wwc/layout.py b/pepsico/proj_wwc/layout.py index c069bcaf..b3a4d437 100644 --- a/pepsico/proj_wwc/layout.py +++ b/pepsico/proj_wwc/layout.py @@ -37,7 +37,7 @@ def app_layout(): Block("WWC statistics", Select( id="variable", options=[ - # "warm_nights", + "warm_nights", # "rain_events", "mean_Tmax", "mean_Tmin", @@ -56,7 +56,7 @@ def app_layout(): # "dry_spells_median_length", ], labels=[ - # "Warm Nights", + "Warm Nights", # "Count of Rain Events", "Mean Max Temperature", "Mean Min Temperature", @@ -96,7 +96,27 @@ def app_layout(): width="5em", debounce=False, ), - "mm", + "mm; ", + "Hot/Cold day > / <=", + Number( + id="hot", + default=25, + min=-99, + max=999, + width="5em", + debounce=False, + ), + "˚C; ", + "Warm Nights Spell >=", + Number( + id="wms", + default=5, + min=0, + max=99, + width="5em", + debounce=False, + ), + "days", ), Block("Season", Number( @@ -206,6 +226,14 @@ def app_layout(): Obtained through parametric Normal distributions. """ ]), + html.P([ + html.B("Warm Nights (warm_nights):"),""" + Number of days in a season in a warm night spell. A warm night + spell is defined as a user-defined minimum consecutive number of + warm nights. A warm night is defined as days where minimum + temperature is greater than a user-defined thredhold. + """ + ]), ), lou.map(GLOBAL_CONFIG["zoom"]), diff --git a/pepsico/proj_wwc/maproom.py b/pepsico/proj_wwc/maproom.py index 2597851b..af8ce0fd 100644 --- a/pepsico/proj_wwc/maproom.py +++ b/pepsico/proj_wwc/maproom.py @@ -87,10 +87,10 @@ def select_var(variable): "Tmin_10", "frost_season_length", "frost_days", + "warm_nights", ]: data_var = "tasmin" if variable in [ - # "warm_nights", "mean_Tmax", "Tmax_90", # "heatwave_duration", @@ -115,7 +115,7 @@ def local_data( lat, lng, region, model, variable, start_day, start_month, end_day, end_month, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): model = [model] if model != "Multi-Model-Average" else [ "GFDL-ESM4", "IPSL-CM6A-LR", "MPI-ESM1-2-HR","MRI-ESM2-0", "UKESM1-0-LL" @@ -160,7 +160,8 @@ def local_data( ac.daily_tobegroupedby_season( data_ds, start_day, start_month, end_day, end_month, ), - variable, frost_threshold, wet_threshold, + variable, frost_threshold, wet_threshold, hot_threshold, + warm_nights_spell, ) return data_ds, error_msg @@ -226,12 +227,14 @@ def invalid_button(lat, lng, lat_min, lng_min, lat_max, lng_max): State("end_month", "value"), State("frost", "value"), State("wet", "value"), + State("hot", "value"), + State("wms", "value"), prevent_initial_call=True, ) def send_data_as_csv( n_clicks, marker_pos, region, variable, start_day, start_month, end_day, end_month, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): lat = marker_pos[0] lng = marker_pos[1] @@ -241,11 +244,13 @@ def send_data_as_csv( end_month = ac.strftimeb2int(end_month) frost_threshold = float(frost_threshold) wet_threshold = float(wet_threshold) + hot_threshold = float(hot_threshold) + warm_nights_spell = int(warm_nights_spell) model = "Multi-Model-Average" data_ds, error_msg = local_data( lat, lng, region, model, variable, start_day, start_month, end_day, end_month, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ) if error_msg == None : lng_units = "E" if (lng >= 0) else "W" @@ -279,12 +284,14 @@ def send_data_as_csv( State("end_year_ref", "value"), State("frost", "value"), State("wet", "value"), + State("hot", "value"), + State("wms", "value"), ) def local_plots( marker_pos, region, n_clicks, model, variable, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): lat = marker_pos[0] lng = marker_pos[1] @@ -294,10 +301,12 @@ def local_plots( end_month = ac.strftimeb2int(end_month) frost_threshold = float(frost_threshold) wet_threshold = float(wet_threshold) + hot_threshold = float(hot_threshold) + warm_nights_spell = int(warm_nights_spell) data_ds, error_msg = local_data( lat, lng, region, model, variable, start_day, start_month, end_day, end_month, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ) if error_msg != None : local_graph = pingrid.error_fig(error_msg) @@ -458,7 +467,7 @@ def seasonal_change( scenario, model, variable, region, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): model = [model] if model != "Multi-Model-Average" else [ "GFDL-ESM4", "IPSL-CM6A-LR", "MPI-ESM1-2-HR","MRI-ESM2-0", "UKESM1-0-LL" @@ -475,7 +484,8 @@ def seasonal_change( ).to_dataset(), start_day, start_month, end_day, end_month, ), - variable, frost_threshold, wet_threshold, + variable, frost_threshold, wet_threshold, hot_threshold, + warm_nights_spell, ).mean(dim="T", keep_attrs=True) for m in model ], "M").mean("M", keep_attrs=True) data = xr.concat([ @@ -489,7 +499,8 @@ def seasonal_change( ).to_dataset(), start_day, start_month, end_day, end_month, ), - variable, frost_threshold, wet_threshold, + variable, frost_threshold, wet_threshold, hot_threshold, + warm_nights_spell, ).mean(dim="T", keep_attrs=True) for m in model ], "M").mean("M", keep_attrs=True) #Tedious way to make a subtraction only to keep attributes (units) @@ -531,12 +542,14 @@ def map_attributes(data, variable): State("end_year_ref", "value"), State("frost", "value"), State("wet", "value"), + State("hot", "value"), + State("wms", "value"), ) def draw_colorbar( region, n_clicks, scenario, model, variable, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): start_day = int(start_day) end_day = int(end_day) @@ -544,11 +557,13 @@ def draw_colorbar( end_month = ac.strftimeb2int(end_month) frost_threshold = float(frost_threshold) wet_threshold = float(wet_threshold) + hot_threshold = float(hot_threshold) + warm_nights_spell = int(warm_nights_spell) data = seasonal_change( scenario, model, variable, region, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ) colorbar, min, max = map_attributes(data, variable) return ( @@ -575,12 +590,14 @@ def draw_colorbar( State("end_year_ref", "value"), State("frost", "value"), State("wet", "value"), + State("hot", "value"), + State("wms", "value"), ) def make_map( region, n_clicks, scenario, model, variable, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): try: send_alarm = False @@ -588,7 +605,8 @@ def make_map( f"{TILE_PFX}/{{z}}/{{x}}/{{y}}/{region}/{scenario}/{model}/" f"{variable}/{start_day}/{end_day}/{start_month}/{end_month}/" f"{start_year}/{end_year}/{start_year_ref}/{end_year_ref}/" - f"{frost_threshold}/{wet_threshold}" + f"{frost_threshold}/{wet_threshold}/{hot_threshold}/" + f"{warm_nights_spell}" ) except: url_str= "" @@ -605,7 +623,7 @@ def make_map( f"{TILE_PFX}///////" f"/////" f"////" - f"//" + f"///" ), endpoint=f"{config['core_path']}" ) @@ -613,7 +631,7 @@ def fcst_tiles(tz, tx, ty, region, scenario, model, variable, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ): start_day = int(start_day) end_day = int(end_day) @@ -621,11 +639,13 @@ def fcst_tiles(tz, tx, ty, end_month = ac.strftimeb2int(end_month) frost_threshold = float(frost_threshold) wet_threshold = float(wet_threshold) + hot_threshold = float(hot_threshold) + warm_nights_spell = int(warm_nights_spell) data = seasonal_change( scenario, model, variable, region, start_day, end_day, start_month, end_month, start_year, end_year, start_year_ref, end_year_ref, - frost_threshold, wet_threshold, + frost_threshold, wet_threshold, hot_threshold, warm_nights_spell, ) ( data[select_var(variable)].attrs["colormap"],