Source code for esmtools.temporal

# Most of this module comes from the following example:
# http://xarray.pydata.org/en/stable/examples/monthly-means.html
import numpy as np
import xarray as xr

from .constants import CALENDARS
from .timeutils import get_calendar, get_days_per_month

GROUPBY_TIMES = {"annual": "time.year"}
TIME_RESOLUTIONS = [k for k in GROUPBY_TIMES]


def _weighted_resample(ds, calendar=None, dim="time", resample_resolution=None):
    """Generalized function for time-weighted resampling.

    Args:
        ds (xarray object): Dataset to resample.
        calendar (str): Calendar type (see wrapper functions).
        dim (str): Name of time dimension.
        resample_resolution (str): Temporal resolution to resample to
            * 'annual'

    Returns:
        ds_weighted (xarray object): Variable(s) resampled to the desired temporal
                                     resolution with weighting.
    """
    if (calendar is None) or (calendar not in CALENDARS):
        calendar = get_calendar(ds[dim])

    if resample_resolution not in TIME_RESOLUTIONS:
        raise ValueError(f"Please submit a temporal resolution from {TIME_RESOLUTIONS}")

    time_length = xr.DataArray(
        get_days_per_month(ds.time.to_index(), calendar=calendar),
        coords=[ds.time],
        name="time_length",
    )

    time_res = GROUPBY_TIMES[resample_resolution]
    # Get weights of each time unit (e.g., daily, monthly)
    weights = time_length.groupby(time_res) / time_length.groupby(time_res).sum()

    # Assert that the sum of the weights for each year is 1.0.
    weights_sum = weights.groupby(time_res).sum().values
    np.testing.assert_allclose(weights_sum, np.ones(len(weights_sum)))

    # Calculate the weighted average
    ds_weighted = (ds * weights).groupby(time_res).sum(dim=dim)
    return ds_weighted


[docs]def to_annual(ds, calendar=None, how="mean", dim="time"): """Resample sub-annual temporal resolution to annual resolution with weighting. .. note:: Using ``pandas.groupby()`` still performs an arithmetic mean. This function properly weights, e.g., February is weighted at 28/365 if going from monthly to annual. Args: ds (xarray object): Dataset or DataArray with data to be temporally averaged. calendar (str): Calendar type for data. If None and `ds` is in `cftime`, infer calendar type. * 'noleap'/'365_day': Gregorian calendar without leap years (all are 365 days long). * 'gregorian'/'standard': Mixed Gregorian/Julian calendar. 1582-10-05 to 1582-10-14 don't exist, because people are crazy. Nor does year 0. * 'proleptic_gregorian': A Gregorian calendar extended to dates before 1582-10-15. * 'all_leap'/'366_day': Gregorian calendar with every year being a leap year (all years are 366 days long). * '360_day': All years are 360 days divided into 30 day months. * 'julian': Standard Julian calendar. how (optional str): How to convert to annual. Currently only `mean` is supported, but we plan to add `sum` as well. dim (optional str): Dimension to apply resampling over (default 'time'). Returns: ds_weighted (xarray object): Dataset or DataArray resampled to annual resolution """ if how != "mean": raise NotImplementedError( "Only annual-weighted averaging is currently" + "supported. Please change `how` keyword to 'mean'" ) return _weighted_resample( ds, calendar=calendar, dim=dim, resample_resolution="annual" )