Exemple #1
0
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)
Exemple #3
0
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")
Exemple #4
0
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]))
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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)
Exemple #9
0
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))
Exemple #10
0
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"'
    )
Exemple #11
0
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
Exemple #12
0
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
Exemple #13
0
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