def prcptot( pr: xarray.DataArray, src_timestep: str = None, freq: str = "YS" ) -> xarray.DataArray: r"""ANUCLIM Accumulated total precipitation. Parameters ---------- pr : xarray.DataArray Total precipitation flux [mm d-1], [mm week-1], [mm month-1] or similar. src_timestep : {'D', 'W', 'M'} Input data time frequency - One of daily, weekly or monthly. freq : str Resampling frequency. Returns ------- xarray.DataArray, [length] Total precipitation. Notes ----- According to the ANUCLIM user-guide https://fennerschool.anu.edu.au/files/anuclim61.pdf (ch. 6), input values should be at a weekly (or monthly) frequency. However, the xclim.indices implementation here will calculate the result with input data with daily frequency as well. """ pram = rate2amount(pr) return pram.resample(time=freq).sum(dim="time", keep_attrs=True)
def precip_accumulation( pr: xarray.DataArray, tas: xarray.DataArray = None, phase: Optional[str] = None, thresh: str = "0 degC", freq: str = "YS", ) -> xarray.DataArray: r"""Accumulated total (liquid and/or solid) precipitation. Resample the original daily mean precipitation flux and accumulate over each period. If a daily temperature is provided, the `phase` keyword can be used to sum precipitation of a given phase only. When the temperature is under the provided threshold, precipitation is assumed to be snow, and liquid rain otherwise. This indice is agnostic to the type of daily temperature (tas, tasmax or tasmin) given. Parameters ---------- pr : xarray.DataArray Mean daily precipitation flux. tas : xarray.DataArray, optional Mean, maximum or minimum daily temperature. phase : {None, 'liquid', 'solid'} Which phase to consider, "liquid" or "solid", if None (default), both are considered. thresh : str Threshold of `tas` over which the precipication is assumed to be liquid rain. freq : str Resampling frequency. Returns ------- xarray.DataArray, [length] The total daily precipitation at the given time frequency for the given phase. Notes ----- Let :math:`PR_i` be the mean daily precipitation of day :math:`i`, then for a period :math:`j` starting at day :math:`a` and finishing on day :math:`b`: .. math:: PR_{ij} = \sum_{i=a}^{b} PR_i If tas and phase are given, the corresponding phase precipitation is estimated before computing the accumulation, using one of `snowfall_approximation` or `rain_approximation` with the `binary` method. Examples -------- The following would compute, for each grid cell of a dataset, the total precipitation at the seasonal frequency, ie DJF, MAM, JJA, SON, DJF, etc.: >>> from xclim.indices import precip_accumulation >>> pr_day = xr.open_dataset(path_to_pr_file).pr >>> prcp_tot_seasonal = precip_accumulation(pr_day, freq="QS-DEC") """ if phase == "liquid": pr = rain_approximation(pr, tas=tas, thresh=thresh, method="binary") elif phase == "solid": pr = snowfall_approximation(pr, tas=tas, thresh=thresh, method="binary") return rate2amount(pr).resample(time=freq).sum(dim="time", keep_attrs=True)
def dry_spell_total_length( pr: xarray.DataArray, thresh: str = "1.0 mm", window: int = 3, op: str = "sum", freq: str = "YS", **indexer, ) -> xarray.DataArray: """ Total length of dry spells Total number of days in dry periods of a minimum length, during which the maximum or accumulated precipitation within a window of the same length is under a threshold. Parameters ---------- pr : xarray.DataArray Daily precipitation. thresh : str Accumulated precipitation value under which a period is considered dry. window : int Number of days where the maximum or accumulated precipitation is under threshold. op : {"max", "sum"} Reduce operation. freq : str Resampling frequency. indexer : Indexing parameters to compute the indicator on a temporal subset of the data. It accepts the same arguments as :py:func:`xclim.indices.generic.select_time`. Indexing is done after finding the dry days, but before finding the spells. Returns ------- xarray.DataArray The {freq} total number of days in dry periods of minimum {window} days. Notes ----- The algorithm assumes days before and after the timeseries are "wet", meaning that the condition for being considered part of a dry spell is stricter on the edges. For example, with `window=3` and `op='sum'`, the first day of the series is considered part of a dry spell only if the accumulated precipitation within the first 3 days is under the threshold. In comparison, a day in the middle of the series is considered part of a dry spell if any of the three 3-day periods of which it is part are considered dry (so a total of five days are included in the computation, compared to only 3.) """ pram = rate2amount(pr, out_units="mm") thresh = convert_units_to(thresh, pram) pram_pad = pram.pad(time=(0, window)) mask = getattr(pram_pad.rolling(time=window), op)() < thresh dry = (mask.rolling(time=window).sum() >= 1).shift(time=-(window - 1)) dry = dry.isel(time=slice(0, pram.time.size)).astype(float) out = select_time(dry, **indexer).resample(time=freq).sum("time") return to_agg_units(out, pram, "count")
def test_rate2amount(pr_series): pr = pr_series(np.ones(365 + 366 + 365), start="2019-01-01") am_d = rate2amount(pr) np.testing.assert_array_equal(am_d, 86400) with xr.set_options(keep_attrs=True): pr_ms = pr.resample(time="MS").mean() pr_m = pr.resample(time="M").mean() am_ms = rate2amount(pr_ms) np.testing.assert_array_equal(am_ms[:4], 86400 * np.array([31, 28, 31, 30])) am_m = rate2amount(pr_m) np.testing.assert_array_equal(am_m[:4], 86400 * np.array([31, 28, 31, 30])) np.testing.assert_array_equal(am_ms, am_m) pr_ys = pr.resample(time="YS").mean() am_ys = rate2amount(pr_ys) np.testing.assert_array_equal(am_ys, 86400 * np.array([365, 366, 365]))
def _to_quarter( pr: Optional[xarray.DataArray] = None, tas: Optional[xarray.DataArray] = None, ) -> xarray.DataArray: """Convert daily, weekly or monthly time series to quarterly time series according to ANUCLIM specifications.""" if tas is not None and pr is not None: raise ValueError( "Supply only one variable, 'tas' (exclusive) or 'pr'.") freq = xarray.infer_freq((tas if tas is not None else pr).time) if freq is None: raise ValueError("Can't infer sampling frequency of the input data.") if freq.upper().startswith("D"): if tas is not None: tas = tg_mean(tas, freq="7D") if pr is not None: # Accumulate on a week # Ensure units are back to a "rate" for rate2amount below pr = convert_units_to(precip_accumulation(pr, freq="7D"), "mm") pr.attrs["units"] = "mm/week" freq = "W" if freq.upper().startswith("W"): window = 13 elif freq.upper().startswith("M"): window = 3 else: raise NotImplementedError( f'Unknown input time frequency "{freq}": must be one of "D", "W" or "M".' ) if tas is not None: tas = ensure_chunk_size(tas, time=np.ceil(window / 2)) out = tas.rolling(time=window, center=False).mean(skipna=False) out.attrs = tas.attrs elif pr is not None: pr = ensure_chunk_size(pr, time=np.ceil(window / 2)) pram = rate2amount(pr) out = pram.rolling(time=window, center=False).sum() out.attrs = pr.attrs out.attrs["units"] = pram.units else: raise ValueError("No variables supplied.") out = ensure_chunk_size(out, time=-1) return out
def dry_spell_frequency( pr: xarray.DataArray, thresh: str = "1.0 mm", window: int = 3, freq: str = "YS", op: str = "sum", ) -> xarray.DataArray: """ Return the number of dry periods of n days and more, during which the accumulated or maximal daily precipitation amount on a window of n days is under the threshold. Parameters ---------- pr : xarray.DataArray Daily precipitation. thresh : str Precipitation amount under which a period is considered dry. The value against which the threshold is compared depends on `op` . window : int Minimum length of the spells. freq : str Resampling frequency. op: {"sum","max"} Operation to perform on the window. Default is "sum", which checks that the sum of accumulated precipitation over the whole window is less than the threshold. "max" checks that the maximal daily precipitation amount within the window is less than the threshold. This is the same as verifying that each individual day is below the threshold. Returns ------- xarray.DataArray The {freq} number of dry periods of minimum {window} days. Examples -------- >>> pr = xr.open_dataset(path_to_pr_file).pr >>> dry_spell_frequency(pr=pr, op="sum") >>> dry_spell_frequency(pr=pr, op="max") """ pram = rate2amount(pr, out_units="mm") thresh = convert_units_to(thresh, pram) agg_pr = getattr(pram.rolling(time=window, center=True), op)() out = ((agg_pr < thresh).resample(time=freq).map(rl.windowed_run_events, window=1, dim="time")) out.attrs["units"] = "" return out
def _to_quarter( freq: str, pr: Optional[xarray.DataArray] = None, tas: Optional[xarray.DataArray] = None, ) -> xarray.DataArray: """Convert daily, weekly or monthly time series to quarterly time series according to ANUCLIM specifications.""" if freq.upper().startswith("D"): if tas is not None: tas = tg_mean(tas, freq="7D") if pr is not None: # Accumulate on a week # Ensure units are back to a "rate" for rate2amount below pr = convert_units_to(precip_accumulation(pr, freq="7D"), "mm") pr.attrs["units"] = "mm/week" freq = "W" if freq.upper().startswith("W"): window = 13 elif freq.upper().startswith("M"): window = 3 else: raise NotImplementedError( f'Unknown input time frequency "{freq}": must be one of "D", "W" or "M".' ) if tas is not None: tas = ensure_chunk_size(tas, time=np.ceil(window / 2)) if pr is not None: pr = ensure_chunk_size(pr, time=np.ceil(window / 2)) if pr is not None: pram = rate2amount(pr) out = pram.rolling(time=window, center=False).sum() out.attrs = pr.attrs out.attrs["units"] = pram.units if tas is not None: out = tas.rolling(time=window, center=False).mean(skipna=False) out.attrs = tas.attrs out = ensure_chunk_size(out, time=-1) return out
def test_amount2rate(pr_series): pr = pr_series(np.ones(365 + 366 + 365), start="2019-01-01") am = rate2amount(pr) np.testing.assert_allclose(amount2rate(am), pr) with xr.set_options(keep_attrs=True): am_ms = am.resample(time="MS").sum() am_m = am.resample(time="M").sum() pr_ms = amount2rate(am_ms) np.testing.assert_allclose(pr_ms, 1) pr_m = amount2rate(am_m) np.testing.assert_allclose(pr_m, 1) am_ys = am.resample(time="YS").sum() pr_ys = amount2rate(am_ys) np.testing.assert_allclose(pr_ys, 1)
def prcptot(pr: xarray.DataArray, thresh: str = "0 mm/d", freq: str = "YS") -> xarray.DataArray: r"""Accumulated total precipitation. Parameters ---------- pr : xarray.DataArray Total precipitation flux [mm d-1], [mm week-1], [mm month-1] or similar. thresh : str Threshold over which precipitation starts being cumulated. freq : str Resampling frequency. Returns ------- xarray.DataArray, [length] Total {freq} precipitation. """ thresh = convert_units_to(thresh, pr) return (rate2amount(pr.where(pr >= thresh, 0)).resample(time=freq).sum(keep_attrs=True))
def prcptot_wetdry_period(pr: xarray.DataArray, *, op: str, src_timestep: str, freq: str = "YS") -> xarray.DataArray: r"""ANUCLIM precipitation of the wettest/driest day, week, or month, depending on the time step. Parameters ---------- pr : xarray.DataArray Total precipitation flux [mm d-1], [mm week-1], [mm month-1] or similar. op : {'wettest', 'driest'} Operation to perform : 'wettest' calculate wettest period ; 'driest' calculate driest period. src_timestep : {'D', 'W', 'M'} Input data time frequency - One of daily, weekly or monthly. freq : str Resampling frequency. Returns ------- xarray.DataArray, [length] Total precipitation of the {op} period. Notes ----- According to the ANUCLIM user-guide https://fennerschool.anu.edu.au/files/anuclim61.pdf (ch. 6), input values should be at a weekly (or monthly) frequency. However, the xclim.indices implementation here will calculate the result with input data with daily frequency as well. As such weekly or monthly input values, if desired, should be calculated prior to calling the function. """ pram = rate2amount(pr) if op == "wettest": return pram.resample(time=freq).max(dim="time", keep_attrs=True) if op == "driest": return pram.resample(time=freq).min(dim="time", keep_attrs=True) raise NotImplementedError( f'Unknown operation "{op}" ; op parameter but be one of "wettest" or "driest"' )
def max_n_day_precipitation_amount(pr: xarray.DataArray, window: int = 1, freq: str = "YS"): r"""Highest precipitation amount cumulated over a n-day moving window. Calculate the n-day rolling sum of the original daily total precipitation series and determine the maximum value over each period. Parameters ---------- pr : xarray.DataArray Daily precipitation values. window : int Window size in days. freq : str Resampling frequency. Returns ------- xarray.DataArray, [length] The highest cumulated n-period precipitation value at the given time frequency. Examples -------- >>> from xclim.indices import max_n_day_precipitation_amount # The following would compute for each grid cell the highest 5-day total precipitation #at an annual frequency: >>> pr = xr.open_dataset(path_to_pr_file).pr >>> out = max_n_day_precipitation_amount(pr, window=5, freq="YS") """ # Rolling sum of the values pram = rate2amount(pr) arr = pram.rolling(time=window).sum(skipna=False) out = arr.resample(time=freq).max(dim="time", keep_attrs=True) out.attrs["units"] = pram.units return out
def melt_and_precip_max( swe: xarray.DataArray, pr: xarray.DataArray, window: int = 3, freq: str = "AS-JUL" ) -> xarray.DataArray: """Maximum snow melt and precipitation The maximum snow melt plus precipitation over a given number of days expressed in snow water equivalent. Parameters ---------- swe : xarray.DataArray Snow water equivalent. pr : xarray.DataArray Daily precipitation flux. window : int Number of days during which the water input is accumulated. freq : str Resampling frequency. Returns ------- xarray.DataArray The maximum snow melt plus precipitation over a given number of days for each period. [mass/area]. """ # Compute change in SWE. Set melt as a positive change. dswe = swe.diff(dim="time") * -1 # Add precipitation total total = rate2amount(pr) + dswe # Sum over window agg = total.rolling(time=window).sum() # Max over period out = agg.resample(time=freq).max(dim="time") out.attrs["units"] = swe.units return out
def extreme_precip_accumulation_and_days(pr: xr.DataArray, perc: float = 95, freq: str = "YS"): """Total precipitation accumulation during extreme events and number of days of such precipitation. The `perc` percentile of the precipitation (including all values, not in a day-of-year manner) is computed. Then, for each period, the days where `pr` is above the threshold are accumulated, to get the total precip related to those extreme events. Parameters ---------- pr: xr.DataArray Precipitation flux (both phases). perc: float Percentile corresponding to "extreme" precipitation, [0-100]. freq: str Resampling frequency. Returns ------- xarray.DataArray Precipitation accumulated during events where pr was above the {perc}th percentile of the whole series. xarray.DataArray Number of days where pr was above the {perc}th percentile of the whole series. """ pr_thresh = pr.quantile(perc / 100, dim="time").drop_vars("quantile") extreme_days = pr >= pr_thresh pr_extreme = rate2amount(pr).where(extreme_days) out1 = pr_extreme.resample(time=freq).sum() out1.attrs["units"] = pr_extreme.units out2 = extreme_days.resample(time=freq).sum() out2.attrs["units"] = "days" return out1, out2