def daily_temperature_range(tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: r"""Mean of daily temperature range. The mean difference between the daily maximum temperature and the daily minimum temperature. Parameters ---------- tasmin : xarray.DataArray Minimum daily temperature values [℃] or [K] tasmax : xarray.DataArray Maximum daily temperature values [℃] or [K] freq : str Resampling frequency; Defaults to "YS". Returns ------- xarray.DataArray The average variation in daily temperature range for the given time period. Notes ----- Let :math:`TX_{ij}` and :math:`TN_{ij}` be the daily maximum and minimum temperature at day :math:`i` of period :math:`j`. Then the mean diurnal temperature range in period :math:`j` is: .. math:: DTR_j = \frac{ \sum_{i=1}^I (TX_{ij} - TN_{ij}) }{I} """ q = 1 * units2pint(tasmax) - 0 * units2pint(tasmin) dtr = tasmax - tasmin out = dtr.resample(time=freq).mean(dim="time", keep_attrs=True) out.attrs["units"] = f"{q.units}" return out
def test_units2pint(self, pr_series): u = units2pint(pr_series([1, 2])) assert (str(u)) == "kilogram / meter ** 2 / second" assert pint2cfunits(u) == "kg m-2 s-1" u = units2pint("m^3 s-1") assert str(u) == "meter ** 3 / second" assert pint2cfunits(u) == "m^3 s-1" u = units2pint("2 kg m-2 s-1") assert (str(u)) == "kilogram / meter ** 2 / second" u = units2pint("%") assert str(u) == "percent"
def _check_same_units(*args): sim = args[0] ref = args[1] units_sim = units2pint(sim.units) units_ref = units2pint(ref.units) if units_sim != units_ref: warn( f" sim({units_sim}) and ref({units_ref}) don't have the same units." f" sim will be converted to {units_ref}." ) sim = convert_units_to(sim, ref) out = func(sim, ref, *args[2:]) return out
def ts_fit_graph(ts, params): """Create graphic showing an histogram of the data and the distribution fitted to it. Parameters ---------- ts : str Path to netCDF file storing the time series. params : str Path to netCDF file storing the distribution parameters. Returns ------- fig """ from xclim.indices.stats import get_dist n = ts.nbasins.size dist = params.attrs["scipy_dist"] fig, axes = plt.subplots(n, figsize=(10, 6), squeeze=False) for i in range(n): ax = axes.flat[i] ax2 = plt.twinx(ax) p = params.isel(nbasins=i) # Plot histogram of time series as density then as a normal count. density, bins, patches = ax.hist( ts.isel(nbasins=i).dropna(dim="time"), alpha=0.5, density=True, bins="auto", label="__nolabel__", ) ax2.hist( ts.isel(nbasins=i).dropna(dim="time"), bins=bins, facecolor=(1, 1, 1, 0.01), edgecolor="gray", linewidth=1, ) # Plot pdf of distribution dc = get_dist(dist)(*params.isel(nbasins=i)) mn = dc.ppf(0.01) mx = dc.ppf(0.99) q = np.linspace(mn, mx, 200) pdf = dc.pdf(q) ps = ", ".join(["{:.1f}".format(x) for x in p.values]) ax.plot(q, pdf, "-", label="{}({})".format(params.attrs["scipy_dist"], ps)) # Labels ax.set_xlabel("{} (${:~P}$)".format(ts.long_name, units2pint(ts.units))) ax.set_ylabel("Probability density") ax2.set_ylabel("Histogram count") ax.legend(frameon=False) plt.tight_layout() return fig
def daily_temperature_range( tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS", op: str = "mean", ) -> xarray.DataArray: r"""Statistics of daily temperature range. The mean difference between the daily maximum temperature and the daily minimum temperature. Parameters ---------- tasmin : xarray.DataArray Minimum daily temperature values [℃] or [K] tasmax : xarray.DataArray Maximum daily temperature values [℃] or [K] freq : str Resampling frequency; Defaults to "YS". op : str {'min', 'max', 'mean', 'std'} or func Reduce operation. Can either be a DataArray method or a function that can be applied to a DataArray. Returns ------- xarray.DataArray The average variation in daily temperature range for the given time period. Notes ----- For a default calculation using `op='mean'` : Let :math:`TX_{ij}` and :math:`TN_{ij}` be the daily maximum and minimum temperature at day :math:`i` of period :math:`j`. Then the mean diurnal temperature range in period :math:`j` is: .. math:: DTR_j = \frac{ \sum_{i=1}^I (TX_{ij} - TN_{ij}) }{I} """ q = 1 * units2pint(tasmax) - 0 * units2pint(tasmin) dtr = tasmax - tasmin out = select_resample_op(dtr, op=op, freq=freq) out.attrs["units"] = f"{q.units}" return out
def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: r"""ANUCLIM Precipitation Seasonality (C of V). The annual precipitation Coefficient of Variation (C of V) expressed in percent. Calculated as the standard deviation of precipitation values for a given year expressed as a percentage of the mean of those values. Parameters ---------- pr : xarray.DataArray Total precipitation rate at daily, weekly, or monthly frequency. Units need to be defined as a rate (e.g. mm d-1, mm week-1). freq : str Resampling frequency. Returns ------- xarray.DataArray, [%] Precipitation coefficient of variation Examples -------- The following would compute for each grid cell of file `pr.day.nc` the annual precipitation seasonality: >>> import xclim.indices as xci >>> p = xr.open_dataset(path_to_pr_file).pr >>> pday_seasonality = xci.precip_seasonality(p) >>> p_weekly = xci.precip_accumulation(p, freq='7D') # Input units need to be a rate >>> p_weekly.attrs['units'] = "mm/week" >>> pweek_seasonality = xci.precip_seasonality(p_weekly) 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. If input units are in mm s-1 (or equivalent) values are converted to mm/day to avoid potentially small denominator values. """ # If units in mm/sec convert to mm/days to avoid potentially small denominator if units2pint(pr) == units("mm / s"): pr = convert_units_to(pr, "mm d-1") with xarray.set_options(keep_attrs=True): seas = 100 * _anuclim_coeff_var(pr, freq=freq) seas.attrs["units"] = "%" return seas
def extreme_temperature_range(tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: r"""Extreme intra-period temperature range. The maximum of max temperature (TXx) minus the minimum of min temperature (TNn) for the given time period. Parameters ---------- tasmin : xarray.DataArray Minimum daily temperature values [℃] or [K] tasmax : xarray.DataArray Maximum daily temperature values [℃] or [K] freq : Optional[str[ Resampling frequency; Defaults to "YS". Returns ------- xarray.DataArray Extreme intra-period temperature range for the given time period. Notes ----- Let :math:`TX_{ij}` and :math:`TN_{ij}` be the daily maximum and minimum temperature at day :math:`i` of period :math:`j`. Then the extreme temperature range in period :math:`j` is: .. math:: ETR_j = max(TX_{ij}) - min(TN_{ij}) """ q = 1 * units2pint(tasmax) - 0 * units2pint(tasmin) tx_max = tasmax.resample(time=freq).max(dim="time") tn_min = tasmin.resample(time=freq).min(dim="time") out = tx_max - tn_min out.attrs["units"] = f"{q.units}" return out
def xclim_units_any2pint(ds, var): """ Parameters ---------- ds : xr.Dataset var : str Returns ------- xr.Dataset with `var` units str attribute converted to xclim's pint registry format """ logger.info(f"Reformatting {var} unit string representation") ds[var].attrs["units"] = str(xclim_units.units2pint(ds[var].attrs["units"])) return ds
def daily_temperature_range_variability(tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: r"""Mean absolute day-to-day variation in daily temperature range. Mean absolute day-to-day variation in daily temperature range. Parameters ---------- tasmin : xarray.DataArray Minimum daily temperature values [℃] or [K] tasmax : xarray.DataArray Maximum daily temperature values [℃] or [K] freq : str Resampling frequency; Defaults to "YS". Returns ------- xarray.DataArray The average day-to-day variation in daily temperature range for the given time period. Notes ----- Let :math:`TX_{ij}` and :math:`TN_{ij}` be the daily maximum and minimum temperature at day :math:`i` of period :math:`j`. Then calculated is the absolute day-to-day differences in period :math:`j` is: .. math:: vDTR_j = \frac{ \sum_{i=2}^{I} |(TX_{ij}-TN_{ij})-(TX_{i-1,j}-TN_{i-1,j})| }{I} """ q = 1 * units2pint(tasmax) - 0 * units2pint(tasmin) vdtr = abs((tasmax - tasmin).diff(dim="time")) out = vdtr.resample(time=freq).mean(dim="time") out.attrs["units"] = f"{q.units}" return out
def test_units2pint(self, pr_series): u = units2pint(pr_series([1, 2])) assert (str(u)) == "kilogram / meter ** 2 / second" assert pint2cfunits(u) == "kg m-2 s-1" u = units2pint("m^3 s-1") assert str(u) == "meter ** 3 / second" assert pint2cfunits(u) == "m^3 s-1" u = units2pint("kg m-2 s-1") assert (str(u)) == "kilogram / meter ** 2 / second" u = units2pint("%") assert str(u) == "percent" u = units2pint("1") assert str(u) == "dimensionless" u = units2pint("mm s-1") assert str(u) == "millimeter / second" u = units2pint("degrees_north") assert str(u) == "degrees_north"
def humidex( tas: xr.DataArray, tdps: Optional[xr.DataArray] = None, hurs: Optional[xr.DataArray] = None, ) -> xr.DataArray: r"""Humidex index. The humidex indicates how hot the air feels to an average person, accounting for the effect of humidity. It can be loosely interpreted as the equivalent perceived temperature when the air is dry. Parameters ---------- tas : xarray.DataArray Air temperature. tdps : xarray.DataArray, Dewpoint temperature. hurs : xarray.DataArray Relative humidity. Returns ------- xarray.DataArray, [temperature] The humidex index. Notes ----- The humidex is usually computed using hourly observations of dry bulb and dewpoint temperatures. It is computed using the formula based on [masterton79]_: .. math:: T + {\frac {5}{9}}\left[e - 10\right] where :math:`T` is the dry bulb air temperature (°C). The term :math:`e` can be computed from the dewpoint temperature :math:`T_{dewpoint}` in °K: .. math:: e = 6.112 \times \exp(5417.7530\left({\frac {1}{273.16}}-{\frac {1}{T_{\text{dewpoint}}}}\right) where the constant 5417.753 reflects the molecular weight of water, latent heat of vaporization, and the universal gas constant ([mekis15]_). Alternatively, the term :math:`e` can also be computed from the relative humidity `h` expressed in percent using [sirangelo20]_: .. math:: e = \frac{h}{100} \times 6.112 * 10^{7.5 T/(T + 237.7)}. The humidex *comfort scale* ([eccc]_) can be interpreted as follows: - 20 to 29 : no discomfort; - 30 to 39 : some discomfort; - 40 to 45 : great discomfort, avoid exertion; - 46 and over : dangerous, possible heat stroke; Please note that while both the humidex and the heat index are calculated using dew point, the humidex uses a dew point of 7 °C (45 °F) as a base, whereas the heat index uses a dew point base of 14 °C (57 °F). Further, the heat index uses heat balance equations which account for many variables other than vapor pressure, which is used exclusively in the humidex calculation. References ---------- .. [masterton79] Masterton, J. M., & Richardson, F. A. (1979). HUMIDEX, A method of quantifying human discomfort due to excessive heat and humidity, CLI 1-79. Downsview, Ontario: Environment Canada, Atmospheric Environment Service. .. [mekis15] Éva Mekis, Lucie A. Vincent, Mark W. Shephard & Xuebin Zhang (2015) Observed Trends in Severe Weather Conditions Based on Humidex, Wind Chill, and Heavy Rainfall Events in Canada for 1953–2012, Atmosphere-Ocean, 53:4, 383-397, DOI: 10.1080/07055900.2015.1086970 .. [sirangelo20] Sirangelo, B., Caloiero, T., Coscarelli, R. et al. Combining stochastic models of air temperature and vapour pressure for the analysis of the bioclimatic comfort through the Humidex. Sci Rep 10, 11395 (2020). https://doi.org/10.1038/s41598-020-68297-4 .. [eccc] https://climate.weather.gc.ca/glossary_e.html """ if (tdps is None) == (hurs is None): raise ValueError( "At least one of `tdps` or `hurs` must be given, and not both.") # Vapour pressure in hPa if tdps is not None: # Convert dewpoint temperature to Kelvins tdps = convert_units_to(tdps, "kelvin") e = 6.112 * np.exp(5417.7530 * (1 / 273.16 - 1.0 / tdps)) elif hurs is not None: # Convert dry bulb temperature to Celsius tasC = convert_units_to(tas, "celsius") e = hurs / 100 * 6.112 * 10**(7.5 * tasC / (tasC + 237.7)) # Temperature delta due to humidity in delta_degC h = 5 / 9 * (e - 10) h.attrs["units"] = "delta_degree_Celsius" # Get delta_units for output du = (1 * units2pint(tas) - 0 * units2pint(tas)).units h = convert_units_to(h, du) # Add the delta to the input temperature out = h + tas out.attrs["units"] = tas.units return out