diff --git a/.flake8 b/.flake8
index 6aa94ce03..db12200fe 100644
--- a/.flake8
+++ b/.flake8
@@ -10,6 +10,8 @@ ignore =
D,
E,
F,
+ RST210,
+ RST213,
W503
per-file-ignores =
xclim/core/locales.py:RST399
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fb5b0ee01..685be8e85 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,7 @@ repos:
rev: v3.19.0
hooks:
- id: pyupgrade
- args: ['--py39-plus']
+ args: ['--py310-plus']
exclude: 'xclim/core/indicator.py'
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
@@ -60,8 +60,8 @@ repos:
rev: 1.9.1
hooks:
- id: nbqa-pyupgrade
- args: [ '--py39-plus' ]
- additional_dependencies: [ 'pyupgrade==3.18.0' ]
+ args: [ '--py310-plus' ]
+ additional_dependencies: [ 'pyupgrade==3.19.0' ]
- id: nbqa-black
additional_dependencies: [ 'black==24.10.0' ]
- repo: https://github.com/kynan/nbstripout
@@ -69,7 +69,7 @@ repos:
hooks:
- id: nbstripout
files: '.ipynb'
- args: [ '--extra-keys', 'metadata.kernelspec' ]
+ args: [ '--extra-keys=metadata.kernelspec' ]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
@@ -98,6 +98,13 @@ repos:
hooks:
- id: codespell
additional_dependencies: [ 'tomli' ]
+ args: [ '--toml=pyproject.toml' ]
+ - repo: https://github.com/numpy/numpydoc
+ rev: v1.8.0
+ hooks:
+ - id: numpydoc-validation
+ # Exclude the missing submodule from the xclim.core, see:
+ exclude: ^docs/|^tests/|^xclim/sdba|^xclim/core/missing.py
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2e752ba33..772ea6f81 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,9 +6,14 @@ v0.54.0 (unreleased)
--------------------
Contributors to this version: Trevor James Smith (:user:`Zeitsperre`), Pascal Bourgault (:user:`aulemahal`), Éric Dupuis (:user:`coxipi`), Sascha Hofmann (:user:`saschahofmann`).
+New features and enhancements
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+* Python 3.9 coding conventions have been dropped in favour of Python 3.10+ conventions. (:pull:`1988`).
+
Breaking changes
^^^^^^^^^^^^^^^^
* The minimum required version of `dask` has been increased to `2024.8.1`. (:issue:`1992`, :pull:`1991`).
+* The docstrings of many `xclim` modules, classes, methods, and functions have been slightly adjusted to ensure stricter compliance with established `numpy` docstring conventions. (:pull:`1988`).
Bug fixes
^^^^^^^^^
@@ -25,6 +30,7 @@ Internal changes
* `streamflow` entry replaced with `q` in ``variables.yml``. (:issue:`1912`, :pull:`1996`)
* In order to address 403 (forbidden) request errors when retrieving data from GitHub via ReadTheDocs, the ``nimbus`` class has been modified to use an overloaded `fetch` method that appends a User-Agent header to the request. (:pull:`2001`).
* Addressed a very rare race condition that can happen if `pytest` is tearing down the test environment when running across multiple workers. (:pull:`1863`).
+* The `numpydoc` linting tool has been added to the development dependencies, linting checks, and the `pre-commit` configuration. (:pull:`1988`).
CI changes
^^^^^^^^^^
diff --git a/Makefile b/Makefile
index 898f27d5a..a0d4aba12 100644
--- a/Makefile
+++ b/Makefile
@@ -53,16 +53,17 @@ clean-test: ## remove test and coverage artifacts
rm -fr .pytest_cache
lint: ## check style with flake8 and black
- black --check xclim tests
- ruff check xclim tests
- flake8 --config=.flake8 xclim tests
- vulture xclim tests
- nbqa black --check docs
- blackdoc --check --exclude=xclim/indices/__init__.py xclim
- blackdoc --check docs
- codespell xclim tests docs
- deptry .
- yamllint --config-file=.yamllint.yaml xclim
+ python -m black --check xclim tests
+ python -m ruff check xclim tests
+ python -m flake8 --config=.flake8 xclim tests
+ python -m vulture xclim tests
+ python -m nbqa black --check docs
+ python -m blackdoc --check --exclude=xclim/indices/__init__.py xclim
+ python -m blackdoc --check docs
+ codespell .
+ python -m numpydoc lint xclim/*.py xclim/ensembles/*.py xclim/indices/*.py xclim/indicators/*.py xclim/testing/*.py
+ python -m deptry .
+ python -m yamllint --config-file=.yamllint.yaml xclim
test: ## run tests quickly with the default Python
pytest
@@ -73,9 +74,9 @@ test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
- coverage run --source xclim -m pytest
- coverage report -m
- coverage html
+ python -m coverage run --source xclim -m pytest
+ python -m coverage report -m
+ python -m coverage html
$(BROWSER) htmlcov/index.html
autodoc-obsolete: clean-docs ## create sphinx-apidoc files (obsolete)
@@ -105,10 +106,10 @@ servedocs: autodoc-custom-index ## generate Sphinx HTML documentation, including
$(MAKE) -C docs livehtml
release: dist ## package and upload a release
- flit publish dist/*
+ python -m flit publish dist/*
dist: clean ## builds source and wheel package
- flit build
+ python -m flit build
ls -l dist
install: clean ## install the package to the active Python's site-packages
diff --git a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb
index 31fe82dde..268870d4d 100644
--- a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb
+++ b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb
@@ -8,7 +8,7 @@
"\n",
"This notebook will get you started on the use of `xclim` to subset netCDF arrays and compute climate indicators, taking advantage of parallel processing capabilities offered by `xarray` and `dask`. \n",
"\n",
- "`xarray` is a python package making it easy to work with n-dimensional arrays. It labels axes with their names (time, lat, lon, level) instead of indices (0,1,2,3), reducing the likelihood of bugs and making the code easier to understand. One of the key strengths of `xarray` is that it knows how to deal with non-standard calendars (I'm looking at you 360_days) and can easily resample daily time series to weekly, monthly, seasonal or annual periods. Finally, `xarray` is tightly inegrated with `dask`, a package that can automatically parallelize operations.\n"
+ "`xarray` is a python package making it easy to work with n-dimensional arrays. It labels axes with their names (time, lat, lon, level) instead of indices (0,1,2,3), reducing the likelihood of bugs and making the code easier to understand. One of the key strengths of `xarray` is that it knows how to deal with non-standard calendars (I'm looking at you 360_days) and can easily resample daily time series to weekly, monthly, seasonal or annual periods. Finally, `xarray` is tightly integrated with `dask`, a package that can automatically parallelize operations.\n"
]
},
{
@@ -317,7 +317,7 @@
"source": [
"## 7. xclim computations are *lazy*\n",
"\n",
- "Up until now we have ony created a schedule of tasks with a small preview, not done any actual computations. As mentionned above, writing the output to disk will trigger the cascade of computations on all the chunks. "
+ "Up until now we have only created a schedule of tasks with a small preview, not done any actual computations. As mentioned above, writing the output to disk will trigger the cascade of computations on all the chunks. "
]
},
{
@@ -443,7 +443,7 @@
"source": [
"#### Threshold indices\n",
"\n",
- "`xclim` unit handling also applies to threshold indicators. Users can provide threshold in units of choice and `xclim` will adjust automatically. For example determining the number of days with tasmax > 20°C users can define a threshold input of '20 C' or '20 degC' even if input data is in Kelvin. Alernatively users could send provide a threshold in Kelvin '293.15 K' (if they really wanted to)"
+ "`xclim` unit handling also applies to threshold indicators. Users can provide threshold in units of choice and `xclim` will adjust automatically. For example determining the number of days with tasmax > 20°C users can define a threshold input of '20 C' or '20 degC' even if input data is in Kelvin. Alternatively, users could send provide a threshold in Kelvin '293.15 K' (if they really wanted to)"
]
},
{
@@ -486,13 +486,6 @@
"out3 = atmos.tx_days_above(dsTasmax_C.tasmax, thresh=\"293.15 K\", freq=\"MS\")\n",
"print(\"\\n3. results using inputs in °C : threshold in K: \\n\\n\", out3.values)"
]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
}
],
"metadata": {
diff --git a/docs/references.bib b/docs/references.bib
index 6947caccd..ef78be3a7 100644
--- a/docs/references.bib
+++ b/docs/references.bib
@@ -442,7 +442,7 @@ @article{blazejczyk_introduction_2013
volume = {86},
url = {https://repository.lboro.ac.uk/articles/journal_contribution/An_introduction_to_the_Universal_Thermal_Climate_Index_UTCI_/9347024/1},
doi = {10.7163/GPol.2013.1},
- abstract = {The assessment of the thermal environment is one ofthe main issues in bioclimatic research, and more than 100 simple bioclimatic indices have thus far been developed to facilitate it. However, most of these indices have proved to be of limited applicability, and do not portroy the actual impacts of thermal conditions on human beings. Indices derived from human heatbalance models (one- or two-node) have been found to offer a better representation of the environmental impact in question than do simple ones. Indeed, the new generation of multi-node models for human heat balance do allow full account to be taken of heat transfer and exchange, both within the human body and between the body surface and the surrounding air layer. In this paper, it is essential background information regarding the newly-developed Universal Thermal Climate Index UTCI that is presented, this in fact deriving from the Fiala multi-node model of human heatbalance. The UTCI is defined as the air temperature (Ta) of the reference condition causing the same model response as actual conditions. UTCI was developed in 2009 by virtue of international co-operation between leading experts in the areas of human thermophysiology, physiological modelling, meteorology and climatology. The necessary research for this had been conducted within the framework of a special commission of the International Society of Biometeorology (ISB) and European COST Action 730.},
+ abstract = {The assessment of the thermal environment is one of the main issues in bioclimatic research, and more than 100 simple bioclimatic indices have thus far been developed to facilitate it. However, most of these indices have proved to be of limited applicability, and do not portroy the actual impacts of thermal conditions on human beings. Indices derived from human heatbalance models (one- or two-node) have been found to offer a better representation of the environmental impact in question than do simple ones. Indeed, the new generation of multi-node models for human heat balance do allow full account to be taken of heat transfer and exchange, both within the human body and between the body surface and the surrounding air layer. In this paper, it is essential background information regarding the newly-developed Universal Thermal Climate Index UTCI that is presented, this in fact deriving from the Fiala multi-node model of human heatbalance. The UTCI is defined as the air temperature (Ta) of the reference condition causing the same model response as actual conditions. UTCI was developed in 2009 by virtue of international co-operation between leading experts in the areas of human thermophysiology, physiological modelling, meteorology and climatology. The necessary research for this had been conducted within the framework of a special commission of the International Society of Biometeorology (ISB) and European COST Action 730.},
language = {en},
number = {1},
urldate = {2022-07-29},
diff --git a/environment.yml b/environment.yml
index dbe12b892..bfc0fcece 100644
--- a/environment.yml
+++ b/environment.yml
@@ -31,7 +31,7 @@ dependencies:
# Testing and development dependencies
- black ==24.10.0
- blackdoc ==0.3.9
- - bump-my-version >=0.27.0
+ - bump-my-version >=0.28.1
- cairosvg
- codespell ==2.3.0
- coverage >=7.5.0
@@ -55,6 +55,7 @@ dependencies:
- nc-time-axis >=1.4.1
- netcdf4 # Required for some Jupyter notebooks
- notebook
+ - numpydoc >=1.8.0
- pandas-stubs >=2.2
- pooch >=1.8.0
- pre-commit >=3.7
@@ -64,7 +65,7 @@ dependencies:
- pytest-cov >=5.0.0
- pytest-socket >=0.6.0
- pytest-xdist >=3.2
- - ruff >=0.5.6
+ - ruff >=0.7.0
- sphinx >=7.0.0
- sphinx-autobuild >=2024.4.16
- sphinx-autodoc-typehints
diff --git a/pyproject.toml b/pyproject.toml
index 17e3ec879..cef107e99 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,6 +75,7 @@ dev = [
"nbconvert <7.14", # Pinned due to directive errors in sphinx. See: https://github.com/jupyter/nbconvert/issues/2092
"nbqa >=1.8.2",
"nbval >=0.11.0",
+ "numpydoc >=1.8.0",
"pandas-stubs >=2.2",
"pip >=24.2.0",
"pooch >=1.8.0",
@@ -84,7 +85,7 @@ dev = [
"pytest-cov >=5.0.0",
"pytest-socket >=0.6.0",
"pytest-xdist[psutil] >=3.2",
- "ruff >=0.5.6",
+ "ruff >=0.7.0",
"tokenize-rt >=5.2.0",
"tox >=4.21.2",
"tox-gh >=1.4.4",
@@ -161,7 +162,7 @@ values = [
]
[tool.codespell]
-skip = 'xclim/data/*.json,docs/_build,docs/notebooks/xclim_training/*.ipynb,docs/references.bib,__pycache__,*.gz,*.nc,*.png,*.svg,*.whl'
+skip = '*xclim/data/*.json,*docs/_build,*docs/notebooks/xclim_training/*.ipynb,*docs/references.bib,*.gz,*.nc,*.png,*.svg,*.whl'
ignore-words-list = "absolue,astroid,bloc,bui,callendar,degreee,environnement,hanel,inferrable,lond,nam,nd,ot,ressources,socio-economic,sie,vas"
[tool.coverage.run]
@@ -247,6 +248,34 @@ module = [
]
ignore_missing_imports = true
+[tool.numpydoc_validation]
+checks = [
+ "all", # report on all checks, except the below
+ "ES01", # "No extended summary found"
+ "EX01", # "No examples section found"
+ "GL06", # "Found unknown section \"{section}\""
+ "SA01", # "See Also section not found",
+ "SS01" # "No summary found"
+]
+# remember to use single quotes for regex in TOML
+exclude = [
+ # don't report on objects that match any of these regex
+ '\.undocumented_method$',
+ '\.__repr__$',
+ # any object starting with an underscore is a private object
+ '\._\w+'
+]
+override_SS05 = [
+ # override SS05 to allow docstrings starting with these words
+ '^Access ',
+ '^Assess ',
+ '^Days ',
+ '^Degree-days ',
+ '^Griffiths ',
+ '^Process ',
+ '^Statistics '
+]
+
[tool.pytest.ini_options]
minversion = "7.0"
addopts = [
diff --git a/tests/test_indicators.py b/tests/test_indicators.py
index d61b77bd7..8df70f4dd 100644
--- a/tests/test_indicators.py
+++ b/tests/test_indicators.py
@@ -591,7 +591,7 @@ def test_parse_doc():
assert doc["notes"].startswith("Let")
assert "math::" in doc["notes"]
assert "references" not in doc
- assert doc["long_name"] == "The mean daily temperature at the given time frequency"
+ assert doc["long_name"] == "The mean daily temperature at the given time frequency."
doc = parse_doc(xclim.indices.saturation_vapor_pressure.__doc__)
assert (
diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py
index f1670e613..8466bd9ca 100644
--- a/tests/test_sdba/test_adjustment.py
+++ b/tests/test_sdba/test_adjustment.py
@@ -37,7 +37,12 @@
get_correction,
invert,
)
-from xclim.testing.sdba_utils import nancov # noqa
+
+
+def nancov(X):
+ """Numpy's cov but dropping observations with NaNs."""
+ X_na = np.isnan(X).any(axis=0)
+ return np.cov(X[:, ~X_na])
class TestBaseAdjustment:
diff --git a/tox.ini b/tox.ini
index 6a5320f62..043c98a16 100644
--- a/tox.ini
+++ b/tox.ini
@@ -33,27 +33,21 @@ extras =
deps =
codespell ==2.3.0
deptry==0.16.1
- flake8 >=7.0.0
- flake8-rst-docstrings
- black[jupyter]==24.4.2
+ flake8==7.1.1
+ flake8-rst-docstrings==0.3.0
+ black[jupyter]==24.10.0
blackdoc==0.3.9
- nbqa
- ruff==0.4.10
+ nbqa==1.8.2
+ numpydoc==1.8.0
+ ruff==0.7.0
vulture==2.11
yamllint==1.35.1
commands_pre =
commands =
- black --check xclim tests
- ruff check xclim tests
- flake8 --config=.flake8 xclim tests
- vulture xclim tests
- nbqa black --check docs
- blackdoc --check --exclude=xclim/indices/__init__.py xclim
- blackdoc --check docs
- codespell xclim tests docs
- deptry .
- yamllint --config-file=.yamllint.yaml xclim
+ make lint
commands_post =
+allowlist_externals =
+ make
[testenv:docs]
description = Build the documentation with makefile under {basepython}
diff --git a/xclim/analog.py b/xclim/analog.py
index 6e16dcb1d..239856463 100644
--- a/xclim/analog.py
+++ b/xclim/analog.py
@@ -6,7 +6,7 @@
# Code adapted from flyingpigeon.dissimilarity, Nov 2020.
from __future__ import annotations
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
from typing import Any
import numpy as np
@@ -26,7 +26,8 @@ def spatial_analogs(
method: str = "kldiv",
**kwargs,
):
- r"""Compute dissimilarity statistics between target points and candidate points.
+ r"""
+ Compute dissimilarity statistics between target points and candidate points.
Spatial analogues based on the comparison of climate indices. The algorithm compares
the distribution of the reference indices with the distribution of spatially
@@ -45,7 +46,7 @@ def spatial_analogs(
The dimension over which the *distributions* are constructed. This can be a multi-index dimension.
method : {'seuclidean', 'nearest_neighbor', 'zech_aslan', 'kolmogorov_smirnov', 'friedman_rafsky', 'kldiv'}
Which method to use when computing the dissimilarity statistic.
- \*\*kwargs
+ **kwargs : dict
Any other parameter passed directly to the dissimilarity method.
Returns
@@ -111,7 +112,8 @@ def spatial_analogs(
def standardize(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
- """Standardize x and y by the square root of the product of their standard deviation.
+ """
+ Standardize x and y by the square root of the product of their standard deviation.
Parameters
----------
@@ -129,9 +131,22 @@ def standardize(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
return x / s, y / s
-def metric(func):
- """Register a metric function in the `metrics` mapping and add some preparation/checking code.
+def metric(func: Callable):
+ """
+ Register a metric function in the `metrics` mapping and add some preparation/checking code.
+
+ Parameters
+ ----------
+ func : callable
+ The metric function to be registered.
+
+ Returns
+ -------
+ callable
+ The metric function with some overhead code.
+ Notes
+ -----
All metric functions accept 2D inputs. This reshapes 1D inputs to (n, 1) and (m, 1).
All metric functions are invalid when any non-finite values are present in the inputs.
"""
@@ -166,7 +181,8 @@ def _metric_overhead(x, y, **kwargs):
@metric
def seuclidean(x: np.ndarray, y: np.ndarray) -> float:
- """Compute the Euclidean distance between the mean of a multivariate candidate sample with respect to the mean of a reference sample.
+ """
+ Compute the Euclidean distance between the mean of a multivariate candidate sample with respect to the mean of a reference sample.
This method is scale-invariant.
@@ -200,7 +216,8 @@ def seuclidean(x: np.ndarray, y: np.ndarray) -> float:
@metric
def nearest_neighbor(x: np.ndarray, y: np.ndarray) -> np.ndarray:
- """Compute a dissimilarity metric based on the number of points in the pooled sample whose nearest neighbor belongs to the same distribution.
+ """
+ Compute a dissimilarity metric based on the number of points in the pooled sample whose nearest neighbor belongs to the same distribution.
This method is scale-invariant.
@@ -237,7 +254,8 @@ def nearest_neighbor(x: np.ndarray, y: np.ndarray) -> np.ndarray:
@metric
def zech_aslan(x: np.ndarray, y: np.ndarray, *, dmin: float = 1e-12) -> float:
- r"""Compute a modified Zech-Aslan energy distance dissimilarity metric based on an analogy with the energy of a cloud of electrical charges.
+ r"""
+ Compute a modified Zech-Aslan energy distance dissimilarity metric based on an analogy with the energy of a cloud of electrical charges.
This method is scale-invariant.
@@ -370,7 +388,8 @@ def szekely_rizzo(x: np.ndarray, y: np.ndarray, *, standardize: bool = True) ->
@metric
def friedman_rafsky(x: np.ndarray, y: np.ndarray) -> float:
- """Compute a dissimilarity metric based on the Friedman-Rafsky runs statistics.
+ """
+ Compute a dissimilarity metric based on the Friedman-Rafsky runs statistics.
The algorithm builds a minimal spanning tree (the subset of edges connecting all points that minimizes the total
edge length) then counts the edges linking points from the same distribution. This method is scale-dependent.
@@ -414,7 +433,8 @@ def friedman_rafsky(x: np.ndarray, y: np.ndarray) -> float:
@metric
def kolmogorov_smirnov(x: np.ndarray, y: np.ndarray) -> float:
- """Compute the Kolmogorov-Smirnov statistic applied to two multivariate samples as described by Fasano and Franceschini.
+ """
+ Compute the Kolmogorov-Smirnov statistic applied to two multivariate samples as described by Fasano and Franceschini.
This method is scale-dependent.
@@ -435,17 +455,32 @@ def kolmogorov_smirnov(x: np.ndarray, y: np.ndarray) -> float:
:cite:cts:`fasano_multidimensional_1987`
"""
- def pivot(x, y):
- nx, d = x.shape
- ny, d = y.shape
+ def pivot(_x: np.ndarray, _y: np.ndarray) -> float:
+ """
+ Pivot function to compute the KS statistic.
+
+ Parameters
+ ----------
+ _x : np.ndarray
+ Reference sample.
+ _y : np.ndarray
+ Candidate sample.
+
+ Returns
+ -------
+ float
+ Kolmogorov-Smirnov dissimilarity metric ranging from 0 to 1.
+ """
+ nx, d = _x.shape
+ ny, d = _y.shape
# Multiplicative factor converting d-dim booleans to a unique integer.
mf = (2 ** np.arange(d)).reshape(1, d, 1)
minlength = 2**d
- # Assign a unique integer according on whether or not x[i] <= sample
- ix = ((x.T <= np.atleast_3d(x)) * mf).sum(1)
- iy = ((x.T <= np.atleast_3d(y)) * mf).sum(1)
+ # Assign a unique integer according to whether or not x[i] <= sample
+ ix = ((_x.T <= np.atleast_3d(_x)) * mf).sum(1)
+ iy = ((_x.T <= np.atleast_3d(_y)) * mf).sum(1)
# Count the number of samples in each quadrant
cx = 1.0 * np.apply_along_axis(np.bincount, 0, ix, minlength=minlength) / nx
@@ -456,7 +491,7 @@ def pivot(x, y):
# D[0,:] -= 1. / nx # I don't understand this...
# dmin, dmax = -D.min(), D.max() + .1 / nx
- return np.max(np.abs(cx - cy))
+ return float(np.max(np.abs(cx - cy)))
return max(pivot(x, y), pivot(y, x)) # pylint: disable=arguments-out-of-order
@@ -465,7 +500,8 @@ def pivot(x, y):
def kldiv(
x: np.ndarray, y: np.ndarray, *, k: int | Sequence[int] = 1
) -> float | Sequence[float]:
- r"""Compute the Kullback-Leibler divergence between two multivariate samples.
+ r"""
+ Compute the Kullback-Leibler divergence between two multivariate samples.
The formula to compute the K-L divergence from samples is given by:
@@ -481,7 +517,7 @@ def kldiv(
x : np.ndarray (n,d)
Samples from distribution P, which typically represents the true distribution (reference).
y : np.ndarray (m,d)
- Samples from distribution Q, which typically represents the approximate distribution (candidate)
+ Samples from distribution Q, which typically represents the approximate distribution (candidate).
k : int or sequence
The kth neighbours to look for when estimating the density of the distributions.
Defaults to 1, which can be noisy.
diff --git a/xclim/cli.py b/xclim/cli.py
index f7544527c..a326fc686 100644
--- a/xclim/cli.py
+++ b/xclim/cli.py
@@ -2,7 +2,7 @@
=============================
Command Line Interface module
=============================
-"""
+""" # numpydoc ignore=SS03,SS06
from __future__ import annotations
@@ -35,6 +35,7 @@
distributed = True
except ImportError: # noqa: S110
+ Client, progress = None, None
# Distributed is not a dependency of xclim
pass
@@ -154,7 +155,7 @@ def _process(ctx, **kwargs):
@click.command(short_help="Print versions of dependencies for debugging purposes.")
@click.pass_context
-def show_version_info(ctx):
+def show_version_info(ctx): # numpydoc ignore=PR01
"""Print versions of dependencies for debugging purposes."""
click.echo(show_versions())
ctx.exit()
@@ -180,7 +181,7 @@ def show_version_info(ctx):
f"`XCLIM_TESTDATA_CACHE` (if set) or `{default_testdata_cache}`.",
)
@click.pass_context
-def prefetch_testing_data(ctx, repo, branch, cache_dir):
+def prefetch_testing_data(ctx, repo, branch, cache_dir): # numpydoc ignore=PR01
"""Prefetch xclim testing data for development purposes."""
if repo:
testdata_repo = repo
@@ -211,7 +212,7 @@ def prefetch_testing_data(ctx, repo, branch, cache_dir):
"-r", "--rst", is_flag=True, help="Prints the history in ReStructuredText format."
)
@click.pass_context
-def release_notes(ctx, md, rst):
+def release_notes(ctx, md, rst): # numpydoc ignore=PR01
"""Generate the release notes history for publishing purposes."""
if md and rst:
raise click.BadArgumentUsage(
@@ -257,7 +258,7 @@ def release_notes(ctx, md, rst):
help="Resampling periods frequency used for aggregation. Default: None. Ignored if no variable provided.",
)
@click.pass_context
-def dataflags(ctx, variables, raise_flags, append, dims, freq):
+def dataflags(ctx, variables, raise_flags, append, dims, freq): # numpydoc ignore=PR01
"""Run quality control checks on input data variables and flag for quality control issues or suspicious values."""
ds = _get_input(ctx)
flagged = xr.Dataset()
@@ -318,7 +319,7 @@ def dataflags(ctx, variables, raise_flags, append, dims, freq):
@click.option(
"-i", "--info", is_flag=True, help="Prints more details for each indicator."
)
-def indices(info): # noqa
+def indices(info): # numpydoc ignore=PR01
"""List all indicators."""
formatter = click.HelpFormatter()
formatter.write_heading("Listing all available indicators for computation.")
@@ -343,7 +344,7 @@ def indices(info): # noqa
@click.command()
@click.argument("indicator", nargs=-1)
@click.pass_context
-def info(ctx, indicator):
+def info(ctx, indicator): # numpydoc ignore=PR01
"""Give information about INDICATOR."""
for indname in indicator:
ind = _get_indicator(indname)
@@ -382,7 +383,9 @@ def _format_dict(data, formatter, key_fg="blue", spaces=2):
class XclimCli(click.MultiCommand):
"""Main cli class."""
- def list_commands(self, ctx):
+ def list_commands(
+ self, ctx
+ ) -> tuple[str, str, str, str, str, str]: # numpydoc ignore=PR01,RT01
"""Return the available commands (other than the indicators)."""
return (
"indices",
@@ -393,7 +396,7 @@ def list_commands(self, ctx):
"show_version_info",
)
- def get_command(self, ctx, cmd_name):
+ def get_command(self, ctx, cmd_name) -> click.Command: # numpydoc ignore=PR01,RT01
"""Return the requested command."""
command = {
"dataflags": dataflags,
@@ -451,8 +454,9 @@ def get_command(self, ctx, cmd_name):
"If not specified, xarray decides.",
)
@click.pass_context
-def cli(ctx, **kwargs):
- """Entry point for the command line interface.
+def cli(ctx, **kwargs): # numpydoc ignore=PR01
+ """
+ Entry point for the command line interface.
Manages the global options.
"""
@@ -508,7 +512,7 @@ def cli(ctx, **kwargs):
@cli.result_callback()
@click.pass_context
-def write_file(ctx, *args, **kwargs):
+def write_file(ctx, *args, **kwargs): # numpydoc ignore=PR01
"""Write the output dataset to file."""
if ctx.obj["output"] is not None:
if ctx.obj["verbose"]:
diff --git a/xclim/core/_exceptions.py b/xclim/core/_exceptions.py
index ce91cff69..87db9d706 100644
--- a/xclim/core/_exceptions.py
+++ b/xclim/core/_exceptions.py
@@ -1,3 +1,5 @@
+"""Exceptions and error handling utilities."""
+
from __future__ import annotations
import logging
@@ -12,7 +14,7 @@ class ValidationError(ValueError):
"""Error raised when input data to an indicator fails the validation tests."""
@property
- def msg(self): # noqa
+ def msg(self): # numpydoc ignore=GL08
return self.args[0]
@@ -27,7 +29,8 @@ def raise_warn_or_log(
err_type: type = ValueError,
stacklevel: int = 1,
):
- """Raise, warn or log an error according.
+ """
+ Raise, warn or log an error according.
Parameters
----------
diff --git a/xclim/core/_types.py b/xclim/core/_types.py
index f28a55c9f..ed9b8fd0b 100644
--- a/xclim/core/_types.py
+++ b/xclim/core/_types.py
@@ -1,3 +1,5 @@
+"""Type annotations and constants used throughout xclim."""
+
from __future__ import annotations
from importlib.resources import as_file, files
diff --git a/xclim/core/bootstrapping.py b/xclim/core/bootstrapping.py
index 6e4897d6d..5dbe7a449 100644
--- a/xclim/core/bootstrapping.py
+++ b/xclim/core/bootstrapping.py
@@ -19,11 +19,24 @@
BOOTSTRAP_DIM = "_bootstrap"
-def percentile_bootstrap(func):
- """Decorator applying a bootstrap step to the calculation of exceedance over a percentile threshold.
+def percentile_bootstrap(func: Callable) -> Callable:
+ """
+ Decorator applying a bootstrap step to the calculation of exceedance over a percentile threshold.
This feature is experimental.
+ Parameters
+ ----------
+ func : Callable
+ The function to decorate.
+
+ Returns
+ -------
+ Callable
+ The decorated function.
+
+ Notes
+ -----
Bootstrapping avoids discontinuities in the exceedance between the reference period over which percentiles are
computed, and "out of reference" periods. See `bootstrap_func` for details.
@@ -53,7 +66,7 @@ def tg90p(
"""
@wraps(func)
- def wrapper(*args, **kwargs):
+ def wrapper(*args, **kwargs): # numpydoc ignore=GL08
ba = signature(func).bind(*args, **kwargs)
ba.apply_defaults()
bootstrap = ba.arguments.get("bootstrap", False)
@@ -66,7 +79,8 @@ def wrapper(*args, **kwargs):
def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray:
- r"""Bootstrap the computation of percentile-based indices.
+ r"""
+ Bootstrap the computation of percentile-based indices.
Indices measuring exceedance over percentile-based thresholds (such as tx90p) may contain artificial discontinuities
at the beginning and end of the reference period used to calculate percentiles. The bootstrap procedure can reduce
@@ -83,7 +97,7 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray:
----------
compute_index_func : Callable
Index function.
- \*\*kwargs
+ **kwargs : dict
Arguments to `func`.
Returns
@@ -91,10 +105,6 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray:
xr.DataArray
The result of func with bootstrapping.
- References
- ----------
- :cite:cts:`zhang_avoiding_2005`
-
Notes
-----
This function is meant to be used by the `percentile_bootstrap` decorator.
@@ -111,6 +121,9 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray:
Average output from index function over all resampled time series
Else compute index function using original percentile
+ References
+ ----------
+ :cite:cts:`zhang_avoiding_2005`
"""
# Identify the input and the percentile arrays from the bound arguments
per_key = None
@@ -225,7 +238,8 @@ def _get_year_label(year_dt) -> str:
def build_bootstrap_year_da(
da: DataArray, groups: dict[Any, slice], label: Any, dim: str = "time"
) -> DataArray:
- """Return an array where a group in the original is replaced by every other groups along a new dimension.
+ """
+ Return an array where a group in the original is replaced by every other groups along a new dimension.
Parameters
----------
diff --git a/xclim/core/calendar.py b/xclim/core/calendar.py
index 14b47084f..f17c14d69 100644
--- a/xclim/core/calendar.py
+++ b/xclim/core/calendar.py
@@ -75,7 +75,8 @@
def doy_from_string(doy: DayOfYearStr, year: int, calendar: str) -> int:
- """Return the day-of-year corresponding to an "MM-DD" string for a given year and calendar.
+ """
+ Return the day-of-year corresponding to an "MM-DD" string for a given year and calendar.
Parameters
----------
@@ -98,7 +99,8 @@ def doy_from_string(doy: DayOfYearStr, year: int, calendar: str) -> int:
def get_calendar(obj: Any, dim: str = "time") -> str:
- """Return the calendar of an object.
+ """
+ Return the calendar of an object.
Parameters
----------
@@ -111,16 +113,16 @@ def get_calendar(obj: Any, dim: str = "time") -> str:
dim : str
Name of the coordinate to check (if `obj` is a DataArray or Dataset).
- Raises
- ------
- ValueError
- If no calendar could be inferred.
-
Returns
-------
str
The Climate and Forecasting (CF) calendar name.
- Will always return "standard" instead of "gregorian", following CF conventions 1.9.
+ Will always return "standard" instead of "gregorian", following CF-Conventions v1.9.
+
+ Raises
+ ------
+ ValueError
+ If no calendar could be inferred.
"""
if isinstance(obj, xr.DataArray | xr.Dataset):
return obj[dim].dt.calendar
@@ -140,25 +142,29 @@ def get_calendar(obj: Any, dim: str = "time") -> str:
def common_calendar(calendars: Sequence[str], join="outer") -> str:
- """Return a calendar common to all calendars from a list.
+ """
+ Return a calendar common to all calendars from a list.
Uses the hierarchy: 360_day < noleap < standard < all_leap.
- Returns "default" only if all calendars are "default."
Parameters
----------
- calendars: Sequence of string
- List of calendar names.
+ calendars : Sequence of str
+ List of calendar names.
join : {'inner', 'outer'}
- The criterion for the common calendar.
+ The criterion for the common calendar.
+ - 'outer': the common calendar is the biggest calendar (in number of days by year) that will include all the
+ dates of the other calendars.
+ When converting the data to this calendar, no timeseries will lose elements, but some
+ might be missing (gaps or NaNs in the series).
+ - 'inner': the common calendar is the smallest calendar of the list.
+ When converting the data to this calendar, no timeseries will have missing elements (no gaps or NaNs),
+ but some might be dropped.
- - 'outer': the common calendar is the biggest calendar (in number of days by year) that will include all the
- dates of the other calendars.
- When converting the data to this calendar, no timeseries will lose elements, but some
- might be missing (gaps or NaNs in the series).
- - 'inner': the common calendar is the smallest calendar of the list.
- When converting the data to this calendar, no timeseries will have missing elements (no gaps or NaNs),
- but some might be dropped.
+ Returns
+ -------
+ str
+ Returns "default" only if all calendars are "default".
Examples
--------
@@ -209,8 +215,9 @@ def convert_doy(
align_on: str = "year",
missing: Any = np.nan,
dim: str = "time",
-) -> xr.DataArray:
- """Convert the calendar of day of year (doy) data.
+) -> xr.DataArray | xr.Dataset:
+ """
+ Convert the calendar of day of year (doy) data.
Parameters
----------
@@ -230,6 +237,11 @@ def convert_doy(
If `align_on` is "date" and the new doy doesn't exist in the new calendar, this value is used.
dim : str
Name of the temporal dimension.
+
+ Returns
+ -------
+ xr.DataArray or xr.Dataset
+ The converted doy data.
"""
if isinstance(source, xr.Dataset):
return source.map(
@@ -295,7 +307,8 @@ def convert_doy(
def ensure_cftime_array(time: Sequence) -> np.ndarray | Sequence[cftime.datetime]:
- """Convert an input 1D array to a numpy array of cftime objects.
+ """
+ Convert an input 1D array to a numpy array of cftime objects.
Python's datetime are converted to cftime.DatetimeGregorian ("standard" calendar).
@@ -307,6 +320,7 @@ def ensure_cftime_array(time: Sequence) -> np.ndarray | Sequence[cftime.datetime
Returns
-------
np.ndarray
+ An array of cftime.datetime objects.
Raises
------
@@ -336,7 +350,8 @@ def percentile_doy(
beta: float = 1.0 / 3.0,
copy: bool = True,
) -> xr.DataArray:
- """Percentile value for each day of the year.
+ """
+ Percentile value for each day of the year.
Return the climatological percentile over a moving window around each day of the year. Different quantile estimators
can be used by specifying `alpha` and `beta` according to specifications given by :cite:t:`hyndman_sample_1996`.
@@ -350,7 +365,7 @@ def percentile_doy(
window : int
Number of time-steps around each day of the year to include in the calculation.
per : float or sequence of floats
- Percentile(s) between [0, 100]
+ Percentile(s) between [0, 100].
alpha : float
Plotting position parameter.
beta : float
@@ -429,20 +444,27 @@ def percentile_doy(
def build_climatology_bounds(da: xr.DataArray) -> list[str]:
- """Build the climatology_bounds property with the start and end dates of input data.
+ """
+ Build the climatology_bounds property with the start and end dates of input data.
Parameters
----------
da : xr.DataArray
The input data.
Must have a time dimension.
+
+ Returns
+ -------
+ list of str
+ The climatology bounds.
"""
n = len(da.time)
return da.time[0 :: n - 1].dt.strftime("%Y-%m-%d").values.tolist()
def compare_offsets(freqA: str, op: str, freqB: str) -> bool: # noqa
- """Compare offsets string based on their approximate length, according to a given operator.
+ """
+ Compare offsets string based on their approximate length, according to a given operator.
Offset are compared based on their length approximated for a period starting
after 1970-01-01 00:00:00. If the offsets are from the same category (same first letter),
@@ -452,16 +474,16 @@ def compare_offsets(freqA: str, op: str, freqB: str) -> bool: # noqa
Parameters
----------
freqA : str
- RHS Date offset string ('YS', '1D', 'QS-DEC', ...)
+ RHS Date offset string ('YS', '1D', 'QS-DEC', ...).
op : {'<', '<=', '==', '>', '>=', '!='}
Operator to use.
freqB : str
- LHS Date offset string ('YS', '1D', 'QS-DEC', ...)
+ LHS Date offset string ('YS', '1D', 'QS-DEC', ...).
Returns
-------
bool
- freqA op freqB
+ The result of `freqA` `op` `freqB`.
"""
from ..indices.generic import get_op # pylint: disable=import-outside-toplevel
@@ -481,14 +503,15 @@ def compare_offsets(freqA: str, op: str, freqB: str) -> bool: # noqa
def parse_offset(freq: str) -> tuple[int, str, bool, str | None]:
- """Parse an offset string.
+ """
+ Parse an offset string.
Parse a frequency offset and, if needed, convert to cftime-compatible components.
Parameters
----------
freq : str
- Frequency offset.
+ Frequency offset.
Returns
-------
@@ -504,7 +527,6 @@ def parse_offset(freq: str) -> tuple[int, str, bool, str | None]:
anchor : str, optional
Anchor date for bases Y or Q. As xarray doesn't support "W",
neither does xclim (anchor information is lost when given).
-
"""
# Useful to raise on invalid freqs, convert Y to A and get default anchor (A, Q)
offset = pd.tseries.frequencies.to_offset(freq)
@@ -522,7 +544,8 @@ def parse_offset(freq: str) -> tuple[int, str, bool, str | None]:
def construct_offset(mult: int, base: str, start_anchored: bool, anchor: str | None):
- """Reconstruct an offset string from its parts.
+ """
+ Reconstruct an offset string from its parts.
Parameters
----------
@@ -553,7 +576,8 @@ def construct_offset(mult: int, base: str, start_anchored: bool, anchor: str | N
def is_offset_divisor(divisor: str, offset: str):
- """Check that divisor is a divisor of offset.
+ """
+ Check that divisor is a divisor of offset.
A frequency is a "divisor" of another if a whole number of periods of the
former fit within a single period of the latter.
@@ -562,12 +586,13 @@ def is_offset_divisor(divisor: str, offset: str):
----------
divisor : str
The divisor frequency.
- offset: str
+ offset : str
The large frequency.
Returns
-------
bool
+ Whether divisor is a divisor of offset.
Examples
--------
@@ -616,7 +641,8 @@ def is_offset_divisor(divisor: str, offset: str):
def _interpolate_doy_calendar(
source: xr.DataArray, doy_max: int, doy_min: int = 1
) -> xr.DataArray:
- """Interpolate from one set of dayofyear range to another.
+ """
+ Interpolate from one set of dayofyear range to another.
Interpolate an array defined over a `dayofyear` range (say 1 to 360) to another `dayofyear` range (say 1
to 365).
@@ -658,7 +684,8 @@ def _interpolate_doy_calendar(
def adjust_doy_calendar(
source: xr.DataArray, target: xr.DataArray | xr.Dataset
) -> xr.DataArray:
- """Interpolate from one set of dayofyear range to another calendar.
+ """
+ Interpolate from one set of dayofyear range to another calendar.
Interpolate an array defined over a `dayofyear` range (say 1 to 360) to another `dayofyear` range (say 1 to 365).
@@ -677,24 +704,29 @@ def adjust_doy_calendar(
max_target_doy = int(target.time.dt.dayofyear.max())
min_target_doy = int(target.time.dt.dayofyear.min())
- def has_same_calendar():
+ def has_same_calendar(_source, _target): # numpydoc ignore=GL08
# case of full year (doys between 1 and 360|365|366)
- return source.dayofyear.max() == max_doy[get_calendar(target)]
+ return _source.dayofyear.max() == max_doy[get_calendar(_target)]
- def has_similar_doys():
+ def has_similar_doys(
+ _source, _min_target_doy, _max_target_doy
+ ): # numpydoc ignore=GL08
# case of partial year (e.g. JJA, doys between 152|153 and 243|244)
return (
- source.dayofyear.min == min_target_doy
- and source.dayofyear.max == max_target_doy
+ _source.dayofyear.min == _min_target_doy
+ and _source.dayofyear.max == _max_target_doy
)
- if has_same_calendar() or has_similar_doys():
+ if has_same_calendar(source, target) or has_similar_doys(
+ source, min_target_doy, max_target_doy
+ ):
return source
return _interpolate_doy_calendar(source, max_target_doy, min_target_doy)
def resample_doy(doy: xr.DataArray, arr: xr.DataArray | xr.Dataset) -> xr.DataArray:
- """Create a temporal DataArray where each day takes the value defined by the day-of-year.
+ """
+ Create a temporal DataArray where each day takes the value defined by the day-of-year.
Parameters
----------
@@ -734,7 +766,8 @@ def time_bnds( # noqa: C901
freq: str | None = None,
precision: str | None = None,
):
- """Find the time bounds for a datetime index.
+ """
+ Find the time bounds for a datetime index.
As we are using datetime indices to stand in for period indices, assumptions regarding the period
are made based on the given freq.
@@ -792,7 +825,7 @@ def time_bnds( # noqa: C901
if freq is None:
freq = xr.infer_freq(time)
elif hasattr(freq, "freqstr"):
- # When freq is a Offset
+ # When freq is an Offset
freq = freq.freqstr
freq_base, freq_is_start = parse_offset(freq)[1:3]
@@ -822,7 +855,7 @@ def time_bnds( # noqa: C901
eps = pd.Timedelta(precision or "1ns")
day = pd.Timedelta("1D")
- def shift_time(t):
+ def shift_time(t): # numpydoc ignore=GL08
if not is_on_offset(t):
if freq_is_start:
t = period.rollback(t)
@@ -848,7 +881,8 @@ def shift_time(t):
def climatological_mean_doy(
arr: xr.DataArray, window: int = 5
) -> tuple[xr.DataArray, xr.DataArray]:
- """Calculate the climatological mean and standard deviation for each day of the year.
+ """
+ Calculate the climatological mean and standard deviation for each day of the year.
Parameters
----------
@@ -876,7 +910,8 @@ def climatological_mean_doy(
def within_bnds_doy(
arr: xr.DataArray, *, low: xr.DataArray, high: xr.DataArray
) -> xr.DataArray:
- """Return whether array values are within bounds for each day of the year.
+ """
+ Return whether array values are within bounds for each day of the year.
Parameters
----------
@@ -890,6 +925,7 @@ def within_bnds_doy(
Returns
-------
xarray.DataArray
+ Boolean array of values within doy.
"""
low = resample_doy(low, arr)
high = resample_doy(high, arr)
@@ -899,7 +935,8 @@ def within_bnds_doy(
def _doy_days_since_doys(
base: xr.DataArray, start: DayOfYearStr | None = None
) -> tuple[xr.DataArray, xr.DataArray, xr.DataArray]:
- """Calculate dayofyear to days since, or the inverse.
+ """
+ Calculate dayofyear to days since, or the inverse.
Parameters
----------
@@ -949,7 +986,8 @@ def doy_to_days_since(
start: DayOfYearStr | None = None,
calendar: str | None = None,
) -> xr.DataArray:
- """Convert day-of-year data to days since a given date.
+ """
+ Convert day-of-year data to days since a given date.
This is useful for computing meaningful statistics on doy data.
@@ -1020,7 +1058,8 @@ def days_since_to_doy(
start: DayOfYearStr | None = None,
calendar: str | None = None,
) -> xr.DataArray:
- """Reverse the conversion made by :py:func:`doy_to_days_since`.
+ """
+ Reverse the conversion made by :py:func:`doy_to_days_since`.
Converts data given in days since a specific date to day-of-year.
@@ -1087,12 +1126,12 @@ def select_time(
date_bounds: tuple[str, str] | None = None,
include_bounds: bool | tuple[bool, bool] = True,
) -> DataType:
- """Select entries according to a time period.
+ """
+ Select entries according to a time period.
- This conveniently improves xarray's :py:meth:`xarray.DataArray.where` and
- :py:meth:`xarray.DataArray.sel` with fancier ways of indexing over time elements.
- In addition to the data `da` and argument `drop`, only one of `season`, `month`,
- `doy_bounds` or `date_bounds` may be passed.
+ This conveniently improves xarray's :py:meth:`xarray.DataArray.where` and :py:meth:`xarray.DataArray.sel`
+ with fancier ways of indexing over time elements. In addition to the data `da` and argument `drop`,
+ only one of `season`, `month`, `doy_bounds` or `date_bounds` may be passed.
Parameters
----------
@@ -1100,17 +1139,17 @@ def select_time(
Input data.
drop : bool
Whether to drop elements outside the period of interest or to simply mask them (default).
- season : string or sequence of strings, optional
+ season : str or sequence of str, optional
One or more of 'DJF', 'MAM', 'JJA' and 'SON'.
- month : integer or sequence of integers, optional
- Sequence of month numbers (January = 1 ... December = 12)
- doy_bounds : 2-tuple of integers, optional
+ month : int or sequence of int, optional
+ Sequence of month numbers (January = 1 ... December = 12).
+ doy_bounds : 2-tuple of int, optional
The bounds as (start, end) of the period of interest expressed in day-of-year, integers going from
1 (January 1st) to 365 or 366 (December 31st).
If calendar awareness is needed, consider using ``date_bounds`` instead.
- date_bounds : 2-tuple of strings, optional
+ date_bounds : 2-tuple of str, optional
The bounds as (start, end) of the period of interest expressed as dates in the month-day (%m-%d) format.
- include_bounds : bool or 2-tuple of booleans
+ include_bounds : bool or 2-tuple of bool
Whether the bounds of `doy_bounds` or `date_bounds` should be inclusive or not.
Either one value for both or a tuple. Default is True, meaning bounds are inclusive.
@@ -1234,7 +1273,8 @@ def stack_periods(
align_days: bool = True,
pad_value=dtypes.NA,
):
- """Construct a multi-period array.
+ """
+ Construct a multi-period array.
Stack different equal-length periods of `da` into a new 'period' dimension.
@@ -1282,8 +1322,8 @@ def stack_periods(
Passed directly as argument ``fill_value`` to :py:func:`xarray.concat`,
the default is the same as on that function.
- Return
- ------
+ Returns
+ -------
xr.DataArray
A DataArray with a new `period` dimension and a `time` dimension with the length of the longest window.
The new time coordinate has the same frequency as the input data but is generated using
@@ -1438,8 +1478,11 @@ def stack_periods(
return out
-def unstack_periods(da: xr.DataArray | xr.Dataset, dim: str = "period"):
- """Unstack an array constructed with :py:func:`stack_periods`.
+def unstack_periods(
+ da: xr.DataArray | xr.Dataset, dim: str = "period"
+) -> xr.DataArray | xr.Dataset:
+ """
+ Unstack an array constructed with :py:func:`stack_periods`.
Can only work with periods stacked with a ``stride`` that divides ``window`` in an odd number of sections.
When ``stride`` is smaller than ``window``, only the center-most stride of each window is kept,
@@ -1447,11 +1490,16 @@ def unstack_periods(da: xr.DataArray | xr.Dataset, dim: str = "period"):
Parameters
----------
- da : xr.DataArray
+ da : xr.DataArray or xr.Dataset
As constructed by :py:func:`stack_periods`, attributes of the period coordinates must have been preserved.
dim : str
The period dimension name.
+ Returns
+ -------
+ xr.DataArray or xr.Dataset
+ The unstacked data.
+
Notes
-----
The following table shows which strides are included (``o``) in the unstacked output.
diff --git a/xclim/core/cfchecks.py b/xclim/core/cfchecks.py
index a84169d39..8e648fe98 100644
--- a/xclim/core/cfchecks.py
+++ b/xclim/core/cfchecks.py
@@ -11,14 +11,32 @@
import re
from collections.abc import Sequence
+import xarray as xr
+
from xclim.core._exceptions import ValidationError
from xclim.core._types import VARIABLES
from xclim.core.options import cfcheck
@cfcheck
-def check_valid(var, key: str, expected: str | Sequence[str]):
- r"""Check that a variable's attribute has one of the expected values. Raise a ValidationError otherwise."""
+def check_valid(var: xr.DataArray, key: str, expected: str | Sequence[str]):
+ r"""
+ Check that a variable's attribute has one of the expected values and raise a ValidationError if otherwise.
+
+ Parameters
+ ----------
+ var : xr.DataArray
+ The variable to check.
+ key : str
+ The attribute to check.
+ expected : str or sequence of str
+ The expected value(s).
+
+ Raises
+ ------
+ ValidationError
+ If the attribute is not present or does not match the expected value(s).
+ """
att = getattr(var, key, None)
if att is None:
raise ValidationError(f"Variable does not have a `{key}` attribute.")
@@ -33,8 +51,26 @@ def check_valid(var, key: str, expected: str | Sequence[str]):
)
-def cfcheck_from_name(varname, vardata, attrs: list[str] | None = None):
- """Perform cfchecks on a DataArray using specifications from xclim's default variables."""
+def cfcheck_from_name(
+ varname: str, vardata: xr.DataArray, attrs: list[str] | None = None
+):
+ """
+ Perform cfchecks on a DataArray using specifications from xclim's default variables.
+
+ Parameters
+ ----------
+ varname : str
+ The name of the variable to check.
+ vardata : xr.DataArray
+ The variable to check.
+ attrs : list of str, optional
+ The attributes to check. Default is ["cell_methods", "standard_name"].
+
+ Raises
+ ------
+ ValidationError
+ If the variable does not meet the expected CF-Convention.
+ """
if attrs is None:
attrs = ["cell_methods", "standard_name"]
diff --git a/xclim/core/datachecks.py b/xclim/core/datachecks.py
index b22a5f0af..dd9316a06 100644
--- a/xclim/core/datachecks.py
+++ b/xclim/core/datachecks.py
@@ -17,8 +17,11 @@
@datacheck
-def check_freq(var: xr.DataArray, freq: str | Sequence[str], strict: bool = True):
- """Raise an error if not series has not the expected temporal frequency or is not monotonically increasing.
+def check_freq(
+ var: xr.DataArray, freq: str | Sequence[str], strict: bool = True
+) -> None:
+ """
+ Raise an error if not series has not the expected temporal frequency or is not monotonically increasing.
Parameters
----------
@@ -57,19 +60,31 @@ def check_freq(var: xr.DataArray, freq: str | Sequence[str], strict: bool = True
)
-def check_daily(var: xr.DataArray):
- """Raise an error if not series has a frequency other that daily, or is not monotonically increasing.
+def check_daily(var: xr.DataArray) -> None:
+ """
+ Raise an error if series has a frequency other that daily, or is not monotonically increasing.
+
+ Parameters
+ ----------
+ var : xr.DataArray
+ Input array.
Notes
-----
This does not check for gaps in series.
"""
- return check_freq(var, "D")
+ check_freq(var, "D")
@datacheck
-def check_common_time(inputs: Sequence[xr.DataArray]):
- """Raise an error if the list of inputs doesn't have a single common frequency.
+def check_common_time(inputs: Sequence[xr.DataArray]) -> None:
+ """
+ Raise an error if the list of inputs doesn't have a single common frequency.
+
+ Parameters
+ ----------
+ inputs : Sequence of xr.DataArray
+ Input arrays.
Raises
------
@@ -77,11 +92,6 @@ def check_common_time(inputs: Sequence[xr.DataArray]):
- if the frequency of any input can't be inferred
- if inputs have different frequencies
- if inputs have a daily or hourly frequency, but they are not given at the same time of day.
-
- Parameters
- ----------
- inputs : Sequence of xr.DataArray
- Input arrays.
"""
# Check all have the same freq
freqs = [xr.infer_freq(da.time) for da in inputs]
diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py
index e965703a9..f521a8eb5 100644
--- a/xclim/core/dataflags.py
+++ b/xclim/core/dataflags.py
@@ -1,13 +1,13 @@
"""
Data Flags
-===========
+==========
Pseudo-indicators designed to analyse supplied variables for suspicious/erroneous indicator values.
"""
from __future__ import annotations
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
from decimal import Decimal
from functools import reduce
from inspect import signature
@@ -28,9 +28,10 @@
class DataQualityException(Exception):
- """Raised when any data evaluation checks are flagged as True.
+ """
+ Raised when any data evaluation checks are flagged as `True`.
- Attributes
+ Parameters
----------
flag_array : xarray.Dataset
Xarray.Dataset of Data Flags.
@@ -80,11 +81,22 @@ def __str__(self):
]
-def register_methods(variable_name=None):
- """Register a data flag functioné.
+def register_methods(variable_name: str = None) -> Callable:
+ """
+ Register a data flag as functional.
- Argument can be the output variable name template. The template may use any of the stringable input arguments.
+ Argument can be the output variable name template. The template may use any of the string-like input arguments.
If not given, the function name is used instead, which may create variable conflicts.
+
+ Parameters
+ ----------
+ variable_name : str, optional
+ The output variable name template. Default is `None`.
+
+ Returns
+ -------
+ callable
+ The function being registered.
"""
def _register_methods(func):
@@ -113,16 +125,20 @@ def tasmax_below_tasmin(
tasmax: xarray.DataArray,
tasmin: xarray.DataArray,
) -> xarray.DataArray:
- """Check if tasmax values are below tasmin values for any given day.
+ """
+ Check if tasmax values are below tasmin values for any given day.
Parameters
----------
tasmax : xarray.DataArray
+ Maximum temperature.
tasmin : xarray.DataArray
+ Minimum temperature.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where tasmax is below tasmin.
Examples
--------
@@ -146,16 +162,20 @@ def tas_exceeds_tasmax(
tas: xarray.DataArray,
tasmax: xarray.DataArray,
) -> xarray.DataArray:
- """Check if tas values tasmax values for any given day.
+ """
+ Check if tas values tasmax values for any given day.
Parameters
----------
tas : xarray.DataArray
+ Mean temperature.
tasmax : xarray.DataArray
+ Maximum temperature.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where tas is above tasmax.
Examples
--------
@@ -178,16 +198,20 @@ def tas_exceeds_tasmax(
def tas_below_tasmin(
tas: xarray.DataArray, tasmin: xarray.DataArray
) -> xarray.DataArray:
- """Check if tas values are below tasmin values for any given day.
+ """
+ Check if tas values are below tasmin values for any given day.
Parameters
----------
tas : xarray.DataArray
+ Mean temperature.
tasmin : xarray.DataArray
+ Minimum temperature.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where tas is below tasmin.
Examples
--------
@@ -210,16 +234,21 @@ def tas_below_tasmin(
def temperature_extremely_low(
da: xarray.DataArray, *, thresh: Quantified = "-90 degC"
) -> xarray.DataArray:
- """Check if temperatures values are below -90 degrees Celsius for any given day.
+ """
+ Check if temperatures values are below -90 degrees Celsius for any given day.
Parameters
----------
da : xarray.DataArray
+ Temperature.
thresh : str
+ Threshold below which temperatures are considered problematic and a flag is raised.
+ Default is -90 degrees Celsius.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where temperatures are below the threshold.
Examples
--------
@@ -244,16 +273,20 @@ def temperature_extremely_low(
def temperature_extremely_high(
da: xarray.DataArray, *, thresh: Quantified = "60 degC"
) -> xarray.DataArray:
- """Check if temperatures values exceed 60 degrees Celsius for any given day.
+ """
+ Check if temperatures values exceed 60 degrees Celsius for any given day.
Parameters
----------
da : xarray.DataArray
+ Temperature.
thresh : str
+ Threshold above which temperatures are considered problematic and a flag is raised. Default is 60 degrees Celsius.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where temperatures are above the threshold.
Examples
--------
@@ -277,15 +310,18 @@ def temperature_extremely_high(
def negative_accumulation_values(
da: xarray.DataArray,
) -> xarray.DataArray:
- """Check if variable values are negative for any given day.
+ """
+ Check if variable values are negative for any given day.
Parameters
----------
da : xarray.DataArray
+ Variable array.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where values are negative.
Examples
--------
@@ -308,18 +344,20 @@ def negative_accumulation_values(
def very_large_precipitation_events(
da: xarray.DataArray, *, thresh: Quantified = "300 mm d-1"
) -> xarray.DataArray:
- """Check if precipitation values exceed 300 mm/day for any given day.
+ """
+ Check if precipitation values exceed 300 mm/day for any given day.
Parameters
----------
da : xarray.DataArray
- The DataArray being examined.
+ Precipitation.
thresh : str
Threshold to search array for that will trigger flag if any day exceeds value.
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where precipitation values exceed the threshold.
Examples
--------
@@ -343,14 +381,15 @@ def very_large_precipitation_events(
def values_op_thresh_repeating_for_n_or_more_days(
da: xarray.DataArray, *, n: int, thresh: Quantified, op: str = "=="
) -> xarray.DataArray:
- """Check if array values repeat at a given threshold for `N` or more days.
+ """
+ Check if array values repeat at a given threshold for `N` or more days.
Parameters
----------
da : xarray.DataArray
- The DataArray being examined.
+ Variable array.
n : int
- Number of days needed to trigger flag.
+ Number of repeating days needed to trigger flag.
thresh : str
Repeating values to search for that will trigger flag.
op : {">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne"}
@@ -359,6 +398,7 @@ def values_op_thresh_repeating_for_n_or_more_days(
Returns
-------
xarray.DataArray, [bool]
+ Boolean array of True where values repeat at threshold for `N` or more days.
Examples
--------
@@ -395,20 +435,22 @@ def wind_values_outside_of_bounds(
lower: Quantified = "0 m s-1",
upper: Quantified = "46 m s-1",
) -> xarray.DataArray:
- """Check if variable values fall below 0% or rise above 100% for any given day.
+ """
+ Check if wind speed values exceed reasonable bounds for any given day.
Parameters
----------
da : xarray.DataArray
- The DataArray being examined.
+ Wind speed.
lower : str
- The lower limit for wind speed.
+ The lower limit for wind speed. Default is 0 m s-1.
upper : str
- The upper limit for wind speed.
+ The upper limit for wind speed. Default is 46 m s-1.
Returns
-------
xarray.DataArray, [bool]
+ The boolean array of True where values exceed the bounds.
Examples
--------
@@ -439,24 +481,30 @@ def outside_n_standard_deviations_of_climatology(
n: int,
window: int = 5,
) -> xarray.DataArray:
- """Check if any daily value is outside `n` standard deviations from the day of year mean.
+ """
+ Check if any daily value is outside `n` standard deviations from the day of year mean.
Parameters
----------
da : xarray.DataArray
- The DataArray being examined.
+ Variable array.
n : int
Number of standard deviations.
window : int
- Moving window used to determining climatological mean. Default: 5.
+ Moving window used in determining the climatological mean. Default: `5`.
Returns
-------
xarray.DataArray, [bool]
+ The boolean array of True where values exceed the bounds.
Notes
-----
- A moving window of 5 days is suggested for tas data flag calculations according to ICCLIM data quality standards.
+ A moving window of five (5) days is suggested for `tas` data flag calculations according to ICCLIM data quality standards.
+
+ References
+ ----------
+ :cite:cts:`project_team_eca&d_algorithm_2013`
Examples
--------
@@ -469,10 +517,6 @@ def outside_n_standard_deviations_of_climatology(
>>> flagged = outside_n_standard_deviations_of_climatology(
... ds.tas, n=std_devs, window=average_over
... )
-
- References
- ----------
- :cite:cts:`project_team_eca&d_algorithm_2013`
"""
mu, sig = climatological_mean_doy(da, window=window)
within_bounds = _sanitize_attrs(
@@ -492,18 +536,20 @@ def outside_n_standard_deviations_of_climatology(
def values_repeating_for_n_or_more_days(
da: xarray.DataArray, *, n: int
) -> xarray.DataArray:
- """Check if exact values are found to be repeating for at least 5 or more days.
+ """
+ Check if exact values are found to be repeating for at least 5 or more days.
Parameters
----------
da : xarray.DataArray
- The DataArray being examined.
+ Variable array.
n : int
Number of days to trigger flag.
Returns
-------
xarray.DataArray, [bool]
+ The boolean array of True where values repeat for `n` or more days.
Examples
--------
@@ -523,15 +569,18 @@ def values_repeating_for_n_or_more_days(
@register_methods()
@update_xclim_history
def percentage_values_outside_of_bounds(da: xarray.DataArray) -> xarray.DataArray:
- """Check if variable values fall below 0% or rise above 100% for any given day.
+ """
+ Check if variable values fall below 0% or exceed 100% for any given day.
Parameters
----------
da : xarray.DataArray
+ Variable array.
Returns
-------
xarray.DataArray, [bool]
+ The boolean array of True where values exceed the bounds.
Examples
--------
@@ -554,7 +603,8 @@ def data_flags( # noqa: C901
freq: str | None = None,
raise_flags: bool = False,
) -> xarray.Dataset:
- """Evaluate the supplied DataArray for a set of data flag checks.
+ """
+ Evaluate the supplied DataArray for a set of data flag checks.
Test triggers depend on variable name and availability of extra variables within Dataset for comparison.
If called with `raise_flags=True`, will raise a DataQualityException with comments for each failed quality check.
@@ -581,6 +631,7 @@ def data_flags( # noqa: C901
Returns
-------
xarray.Dataset
+ The Dataset of boolean flag arrays.
Examples
--------
@@ -601,14 +652,14 @@ def data_flags( # noqa: C901
... )
"""
- def get_variable_name(function, kwargs):
- fmtargs = {}
- kwargs = kwargs or {}
+ def _get_variable_name(function, _kwargs):
+ format_args = {}
+ _kwargs = _kwargs or {}
for arg, param in signature(function).parameters.items():
- val = kwargs.get(arg, param.default)
+ val = _kwargs.get(arg, param.default)
kind = infer_kind_from_parameter(param)
if arg == "op":
- fmtargs[arg] = val if val not in binary_ops else binary_ops[val]
+ format_args[arg] = val if val not in binary_ops else binary_ops[val]
elif kind in [
InputKind.FREQ_STR,
InputKind.NUMBER,
@@ -617,10 +668,10 @@ def get_variable_name(function, kwargs):
InputKind.DATE,
InputKind.BOOL,
]:
- fmtargs[arg] = val
+ format_args[arg] = val
elif kind == InputKind.QUANTIFIED:
if isinstance(val, xarray.DataArray):
- fmtargs[arg] = "array"
+ format_args[arg] = "array"
else:
val = str2pint(val).magnitude
if Decimal(val) % 1 == 0:
@@ -628,8 +679,8 @@ def get_variable_name(function, kwargs):
else:
val = str(val).replace(".", "point")
val = val.replace("-", "minus")
- fmtargs[arg] = str(val)
- return function.variable_name.format(**fmtargs)
+ format_args[arg] = str(val)
+ return function.variable_name.format(**format_args)
def _missing_vars(function, dataset: xarray.Dataset, var_provided: str):
"""Handle missing variables in passed datasets."""
@@ -678,7 +729,7 @@ def _missing_vars(function, dataset: xarray.Dataset, var_provided: str):
for flag_func in flag_funcs:
for name, kwargs in flag_func.items():
func = _REGISTRY[name]
- variable_name = get_variable_name(func, kwargs)
+ variable_name = _get_variable_name(func, kwargs)
named_da_variable = None
try:
@@ -705,13 +756,13 @@ def _missing_vars(function, dataset: xarray.Dataset, var_provided: str):
flags[variable_name] = out
- dsflags = xarray.Dataset(data_vars=flags)
+ ds_flags = xarray.Dataset(data_vars=flags)
if raise_flags:
- if np.any([dsflags[dv] for dv in dsflags.data_vars]):
- raise DataQualityException(dsflags)
+ if np.any([ds_flags[dv] for dv in ds_flags.data_vars]):
+ raise DataQualityException(ds_flags)
- return dsflags
+ return ds_flags
def ecad_compliant(
@@ -720,15 +771,16 @@ def ecad_compliant(
raise_flags: bool = False,
append: bool = True,
) -> xarray.DataArray | xarray.Dataset | None:
- """Run ECAD compliance tests.
+ """
+ Run ECAD compliance tests.
Assert file adheres to ECAD-based quality assurance checks.
Parameters
----------
ds : xarray.Dataset
- Dataset containing variables to be examined.
- dims : {"all", None} or str or a sequence of strings
+ Variable-containing dataset.
+ dims : {"all"} or str or a sequence of strings, optional
Dimensions upon which aggregation should be performed. Default: ``"all"``.
raise_flags : bool
Raise exception if any of the quality assessment flags are raised, otherwise returns None. Default: ``False``.
@@ -739,6 +791,7 @@ def ecad_compliant(
Returns
-------
xarray.DataArray or xarray.Dataset or None
+ Flag array or Dataset with flag array(s) appended.
"""
flags = xarray.Dataset()
history: list[str] = []
diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py
index 45e545090..e76e6502c 100644
--- a/xclim/core/formatting.py
+++ b/xclim/core/formatting.py
@@ -38,9 +38,21 @@
class AttrFormatter(string.Formatter):
- """A formatter for frequently used attribute values.
+ """
+ A formatter for frequently used attribute values.
- See the doc of format_field() for more details.
+ Parameters
+ ----------
+ mapping : dict of str, sequence of str
+ A mapping from values to their possible variations.
+ modifiers : sequence of str
+ The list of modifiers.
+ Must at least match the length of the longest value of `mapping`.
+ Cannot include reserved modifier 'r'.
+
+ Notes
+ -----
+ See the doc of :py:meth:`format_field` for more details.
"""
def __init__(
@@ -48,14 +60,16 @@ def __init__(
mapping: dict[str, Sequence[str]],
modifiers: Sequence[str],
) -> None:
- """Initialize the formatter.
+ """
+ Initialize the formatter.
Parameters
----------
mapping : dict[str, Sequence[str]]
A mapping from values to their possible variations.
modifiers : Sequence[str]
- The list of modifiers, must be the as long as the longest value of `mapping`.
+ The list of modifiers.
+ Must at least match the length of the longest value of `mapping`.
Cannot include reserved modifier 'r'.
"""
super().__init__()
@@ -64,31 +78,49 @@ def __init__(
self.modifiers = modifiers
self.mapping = mapping
- def format(self, format_string: str, /, *args: Any, **kwargs: Any) -> str:
- r"""Format a string.
+ def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str:
+ r"""
+ Format a string.
Parameters
----------
- format_string: str
- \*args: Any
- \*\*kwargs: Any
+ format_string : str
+ The string to format.
+ *args : Any
+ Arguments to format.
+ **kwargs : dict
+ Keyword arguments to format.
Returns
-------
str
+ The formatted string.
"""
for k, v in DEFAULT_FORMAT_PARAMS.items():
if k not in kwargs:
kwargs.update({k: v})
return super().format(format_string, *args, **kwargs)
- def format_field(self, value, format_spec):
- """Format a value given a formatting spec.
+ def format_field(self, value, format_spec: str) -> str:
+ """
+ Format a value given a formatting spec.
If `format_spec` is in this Formatter's modifiers, the corresponding variation
of value is given. If `format_spec` is 'r' (raw), the value is returned unmodified.
If `format_spec` is not specified but `value` is in the mapping, the first variation is returned.
+ Parameters
+ ----------
+ value : Any
+ The value to format.
+ format_spec : str
+ The formatting spec.
+
+ Returns
+ -------
+ str
+ The formatted value.
+
Examples
--------
Let's say the string "The dog is {adj1}, the goose is {adj2}" is to be translated
@@ -206,8 +238,9 @@ def _match_value(self, value):
)
-def parse_doc(doc: str) -> dict[str, str]:
- """Crude regex parsing reading an indice docstring and extracting information needed in indicator construction.
+def parse_doc(doc: str) -> dict:
+ """
+ Crude regex parsing reading an indice docstring and extracting information needed in indicator construction.
The appropriate docstring syntax is detailed in :ref:`notebooks/extendxclim:Defining new indices`.
@@ -253,7 +286,8 @@ def parse_doc(doc: str) -> dict[str, str]:
def _parse_parameters(section):
- """Parse the 'parameters' section of a docstring into a dictionary.
+ """
+ Parse the 'parameters' section of a docstring into a dictionary.
Works by mapping the parameter name to its description and, potentially, to its set of choices.
The type annotation are not parsed, except for fixed sets of values (listed as "{'a', 'b', 'c'}").
@@ -309,7 +343,8 @@ def merge_attributes(
missing_str: str | None = None,
**inputs_kws: xr.DataArray | xr.Dataset,
) -> str:
- r"""Merge attributes from several DataArrays or Datasets.
+ r"""
+ Merge attributes from several DataArrays or Datasets.
If more than one input is given, its name (if available) is prepended as: " : ".
@@ -317,7 +352,7 @@ def merge_attributes(
----------
attribute : str
The attribute to merge.
- inputs_list : xr.DataArray or xr.Dataset
+ *inputs_list : xr.DataArray or xr.Dataset
The datasets or variables that were used to produce the new object.
Inputs given that way will be prefixed by their `name` attribute if available.
new_line : str
@@ -326,7 +361,7 @@ def merge_attributes(
missing_str : str
A string that is printed if an input doesn't have the attribute. Defaults to None, in which
case the input is simply skipped.
- \*\*inputs_kws : xr.DataArray or xr.Dataset
+ **inputs_kws : xr.DataArray or xr.Dataset
Mapping from names to the datasets or variables that were used to produce the new object.
Inputs given that way will be prefixes by the passed name.
@@ -361,7 +396,8 @@ def update_history(
new_name: str | None = None,
**inputs_kws: xr.DataArray | xr.Dataset,
) -> str:
- r"""Return a history string with the timestamped message and the combination of the history of all inputs.
+ r"""
+ Return a history string with the timestamped message and the combination of the history of all inputs.
The new history entry is formatted as "[] : - xclim version: ."
@@ -369,12 +405,12 @@ def update_history(
----------
hist_str : str
The string describing what has been done on the data.
- \*inputs_list : xr.DataArray or xr.Dataset
+ *inputs_list : xr.DataArray or xr.Dataset
The datasets or variables that were used to produce the new object.
Inputs given that way will be prefixed by their "name" attribute if available.
new_name : str, optional
The name of the newly created variable or dataset to prefix hist_msg.
- \*\*inputs_kws : xr.DataArray or xr.Dataset
+ **inputs_kws : xr.DataArray or xr.Dataset
Mapping from names to the datasets or variables that were used to produce the new object.
Inputs given that way will be prefixes by the passed name.
@@ -385,7 +421,7 @@ def update_history(
See Also
--------
- merge_attributes
+ merge_attributes : Merge attributes from several DataArrays or Datasets.
"""
from xclim import ( # pylint: disable=cyclic-import,import-outside-toplevel
__version__,
@@ -407,12 +443,23 @@ def update_history(
return merged_history
-def update_xclim_history(func: Callable):
- """Decorator that auto-generates and fills the history attribute.
+def update_xclim_history(func: Callable) -> Callable:
+ """
+ Decorator that auto-generates and fills the history attribute.
The history is generated from the signature of the function and added to the first output.
Because of a limitation of the `boltons` wrapper, all arguments passed to the wrapped function
will be printed as keyword arguments.
+
+ Parameters
+ ----------
+ func : Callable
+ The function to decorate.
+
+ Returns
+ -------
+ Callable
+ The decorated function.
"""
@wraps(func)
@@ -451,7 +498,8 @@ def _call_and_add_history(*args, **kwargs):
def gen_call_string(funcname: str, *args, **kwargs) -> str:
- r"""Generate a signature string for use in the history attribute.
+ r"""
+ Generate a signature string for use in the history attribute.
DataArrays and Dataset are replaced with their name, while Nones, floats, ints and strings are printed directly.
All other objects have their type printed between < >.
@@ -462,12 +510,19 @@ def gen_call_string(funcname: str, *args, **kwargs) -> str:
Parameters
----------
funcname : str
- Name of the function
- \*args, \*\*kwargs
+ Name of the function.
+ *args : Any
Arguments given to the function.
+ **kwargs : dict
+ Keyword arguments given to the function.
- Example
+ Returns
-------
+ str
+ The formatted string.
+
+ Examples
+ --------
>>> A = xr.DataArray([1], dims=("x",), name="A")
>>> gen_call_string("func", A, b=2.0, c="3", d=[10] * 100)
"func(A, b=2.0, c='3', d=)"
@@ -493,7 +548,8 @@ def gen_call_string(funcname: str, *args, **kwargs) -> str:
def prefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict:
- """Rename some keys of a dictionary by adding a prefix.
+ """
+ Rename some keys of a dictionary by adding a prefix.
Parameters
----------
@@ -519,7 +575,8 @@ def prefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict:
def unprefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict:
- """Remove prefix from keys in a dictionary.
+ """
+ Remove prefix from keys in a dictionary.
Parameters
----------
@@ -567,7 +624,8 @@ def unprefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict:
def _gen_parameters_section(
parameters: dict[str, dict[str, Any]], allowed_periods: list[str] | None = None
) -> str:
- """Generate the "parameters" section of the indicator docstring.
+ """
+ Generate the "parameters" section of the indicator docstring.
Parameters
----------
@@ -579,6 +637,7 @@ def _gen_parameters_section(
Returns
-------
str
+ The formatted section.
"""
section = "Parameters\n----------\n"
for name, param in parameters.items():
@@ -613,7 +672,8 @@ def _gen_parameters_section(
def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str:
- """Generate the "Returns" section of an indicator's docstring.
+ """
+ Generate the "Returns" section of an indicator's docstring.
Parameters
----------
@@ -623,6 +683,7 @@ def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str:
Returns
-------
str
+ The formatted section.
"""
section = "Returns\n-------\n"
for attrs in cf_attrs:
@@ -647,16 +708,18 @@ def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str:
def generate_indicator_docstring(ind) -> str:
- """Generate an indicator's docstring from keywords.
+ """
+ Generate an indicator's docstring from keywords.
Parameters
----------
- ind
- Indicator instance
+ ind : Indicator
+ An Indicator instance.
Returns
-------
str
+ The docstring.
"""
header = f"{ind.title} (realm: {ind.realm})\n\n{ind.abstract}\n"
@@ -691,7 +754,8 @@ def generate_indicator_docstring(ind) -> str:
def get_percentile_metadata(data: xr.DataArray, prefix: str) -> dict[str, str]:
- """Get the metadata related to percentiles from the given DataArray as a dictionary.
+ """
+ Get the metadata related to percentiles from the given DataArray as a dictionary.
Parameters
----------
diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py
index 1822d04c4..d27103765 100644
--- a/xclim/core/indicator.py
+++ b/xclim/core/indicator.py
@@ -4,7 +4,7 @@
The `Indicator` class wraps indices computations with pre- and post-processing functionality. Prior to computations,
the class runs data and metadata health checks. After computations, the class masks values that should be considered
-missing and adds metadata attributes to the object.
+missing and adds metadata attributes to the object.
There are many ways to construct indicators. A good place to start is
`this notebook `_.
@@ -19,10 +19,8 @@
YAML file structure
~~~~~~~~~~~~~~~~~~~
-Indicator-defining yaml files are structured in the following way.
-Most entries of the `indicators` section are mirroring attributes of
-the :py:class:`Indicator`, please refer to its documentation for more
-details on each.
+Indicator-defining yaml files are structured in the following way. Most entries of the `indicators` section are
+mirroring attributes of the :py:class:`Indicator`, please refer to its documentation for more details on each.
.. code-block:: yaml
@@ -49,7 +47,7 @@
# Available classes are listed in `xclim.core.indicator.registry` and
# `xclim.core.indicator.base_registry`.
- # General metadata, usually parsed from the `compute`'s docstring when possible.
+ # General metadata, usually parsed from the `compute`s docstring when possible.
realm: # defaults to module-wide realm. One of "atmos", "land", "seaIce", "ocean".
title:
abstract:
@@ -68,7 +66,7 @@
input: # When "compute" is a generic function, this is a mapping from argument name to the expected variable.
# This will allow the input units and CF metadata checks to run on the inputs.
# Can also be used to modify the expected variable, as long as it has the same dimensionality
- # Ex: tas instead of tasmin.
+ # e.g. "tas" instead of "tasmin".
# Can refer to a variable declared in the `variables` section above.
:
...
@@ -98,8 +96,7 @@
As xclim has strict definitions of possible input variables (see :py:data:`xclim.core.utils.variables`),
the mapping of `data.input` simply links an argument name from the function given in "compute"
to one of those official variables.
-
-"""
+""" # numpydoc ignore=GL07
from __future__ import annotations
@@ -183,12 +180,13 @@ class _empty: # pylint: disable=too-few-public-methods
@dataclass
class Parameter:
- """Class for storing an indicator's controllable parameter.
+ """
+ Class for storing an indicator's controllable parameter.
For convenience, this class implements a special "contains".
- Example
- -------
+ Examples
+ --------
>>> p = Parameter(InputKind.NUMBER, default=2, description="A simple number")
>>> p.units is Parameter._empty # has not been set
True
@@ -210,7 +208,14 @@ class Parameter:
value: Any = _empty
def update(self, other: dict) -> None:
- """Update a parameter's values from a dict."""
+ """
+ Update a parameter's values from a dict.
+
+ Parameters
+ ----------
+ other : dict
+ A dictionary of parameters to update the current.
+ """
for k, v in other.items():
if hasattr(self, k):
setattr(self, k, v)
@@ -219,7 +224,19 @@ def update(self, other: dict) -> None:
@classmethod
def is_parameter_dict(cls, other: dict) -> bool:
- """Return whether other can update a parameter dictionary."""
+ """
+ Return whether `other` can update a parameter dictionary.
+
+ Parameters
+ ----------
+ other : dict
+ A dictionary of parameters.
+
+ Returns
+ -------
+ bool
+ Whether `other` can update a parameter dictionary.
+ """
# Passing compute_name is forbidden.
# name is valid, but is handled by the indicator
return set(other.keys()).issubset(
@@ -231,12 +248,26 @@ def __contains__(self, key) -> bool:
return getattr(self, key, _empty) is not _empty
def asdict(self) -> dict:
- """Format indicators as a dictionary."""
+ """
+ Format indicators as a dictionary.
+
+ Returns
+ -------
+ dict
+ The indicators as a dictionary.
+ """
return {k: v for k, v in asdict(self).items() if v is not _empty}
@property
def injected(self) -> bool:
- """Indicate whether values are injected."""
+ """
+ Indicate whether values are injected.
+
+ Returns
+ -------
+ bool
+ Whether values are injected.
+ """
return self.value is not _empty
@@ -266,10 +297,18 @@ def __init__(self):
_indicators_registry[self.__class__].append(weakref.ref(self))
@classmethod
- def get_instance(cls):
- """Return first found instance.
+ def get_instance(cls) -> Any: # numpydoc ignore=RT05
+ """
+ Return first found instance.
- Raises `ValueError` if no instance exists.
+ Returns
+ -------
+ Indicator
+ First instance found of this class in the indicators registry.
+
+ Raises
+ ------
+ ValueError : if no instance exists.
"""
for inst_ref in _indicators_registry[cls]:
inst = inst_ref()
@@ -282,7 +321,8 @@ def get_instance(cls):
class Indicator(IndicatorRegistrar):
- r"""Climate indicator base class.
+ r"""
+ Climate indicator base class.
Climate indicator object that, when called, computes an indicator and assigns its output a number of
CF-compliant attributes. Some of these attributes can be *templated*, allowing metadata to reflect
@@ -312,7 +352,7 @@ class Indicator(IndicatorRegistrar):
identifier : str
Unique ID for class registry, should be a valid slug.
realm : {'atmos', 'seaIce', 'land', 'ocean'}
- General domain of validity of the indicator. Indicators created outside xclim.indicators must set this attribute.
+ General domain of validity of the indicator. Indicators created outside ``xclim.indicators`` must set this attribute.
compute : func
The function computing the indicators. It should return one or more DataArray.
cf_attrs : list of dicts
@@ -333,13 +373,13 @@ class Indicator(IndicatorRegistrar):
src_freq : str, sequence of strings, optional
The expected frequency of the input data. Can be a list for multiple frequencies, or None if irrelevant.
context : str
- The `pint` unit context, for example use 'hydro' to allow conversion from kg m-2 s-1 to mm/day.
+ The `pint` unit context, for example use 'hydro' to allow conversion from 'kg m-2 s-1' to 'mm/day'.
Notes
-----
All subclasses created are available in the `registry` attribute and can be used to define custom subclasses
or parse all available instances.
- """
+ """ # numpydoc ignore=PR01,PR02
# Officially-supported metadata attributes on the output variables
_cf_names = [
@@ -506,12 +546,17 @@ def _parse_indice(compute, passed_parameters): # noqa: F841
'passed_parameters' is only needed when compute is a generic function
(not decorated by `declare_units`) and it takes a string parameter. In that case
we need to check if that parameter has units (which have been passed explicitly).
-
"""
docmeta = parse_doc(compute.__doc__)
params_dict = docmeta.pop("parameters", {}) # override parent's parameters
compute_sig = signature(compute)
+ # Remove the \\* symbols from the parameter names
+ _sanitized_params_dict = {}
+ for param in params_dict.keys():
+ _sanitized_params_dict[param.replace("*", "")] = params_dict[param]
+ params_dict = _sanitized_params_dict
+
# Check that the `Parameters` section of the docstring does not include parameters
# that are not in the `compute` function signature.
if not set(params_dict.keys()).issubset(compute_sig.parameters.keys()):
@@ -690,8 +735,9 @@ def from_dict(
data: dict,
identifier: str,
module: str | None = None,
- ):
- """Create an indicator subclass and instance from a dictionary of parameters.
+ ) -> Indicator:
+ """
+ Create an indicator subclass and instance from a dictionary of parameters.
Most parameters are passed directly as keyword arguments to the class constructor, except:
@@ -704,13 +750,18 @@ def from_dict(
Parameters
----------
- data: dict
- The exact structure of this dictionary is detailed in the submodule documentation.
+ data : dict
+ The exact structure of this dictionary is detailed in the submodule documentation.
identifier : str
- The name of the subclass and internal indicator name.
+ The name of the subclass and internal indicator name.
module : str
- The module name of the indicator. This is meant to be used only if the indicator
- is part of a dynamically generated submodule, to override the module of the base class.
+ The module name of the indicator. This is meant to be used only if the indicator
+ is part of a dynamically generated submodule, to override the module of the base class.
+
+ Returns
+ -------
+ Indicator
+ A new Indicator instance.
"""
data = data.copy()
if "base" in data:
@@ -1121,8 +1172,11 @@ def _check_identifier(identifier: str) -> None:
)
@classmethod
- def translate_attrs(cls, locale: str | Sequence[str], fill_missing: bool = True):
- """Return a dictionary of unformatted translated translatable attributes.
+ def translate_attrs(
+ cls, locale: str | Sequence[str], fill_missing: bool = True
+ ) -> dict:
+ """
+ Return a dictionary of unformatted translated translatable attributes.
Translatable attributes are defined in :py:const:`xclim.core.locales.TRANSLATABLE_ATTRS`.
@@ -1130,9 +1184,14 @@ def translate_attrs(cls, locale: str | Sequence[str], fill_missing: bool = True)
----------
locale : str or sequence of str
The POSIX name of the locale or a tuple of a locale name and a path to a json file defining translations.
- See `xclim.locale` for details.
+ See :py:mod:`xclim.locale` for details.
fill_missing : bool
If True (default) fill the missing attributes by their english values.
+
+ Returns
+ -------
+ dict
+ A dictionary of translated attributes.
"""
def _translate(cf_attrs, names, var_id=None):
@@ -1170,8 +1229,9 @@ def _translate(cf_attrs, names, var_id=None):
return attrs
@classmethod
- def json(cls, args=None):
- """Return a serializable dictionary representation of the class.
+ def json(cls, args: dict | None = None) -> dict:
+ """
+ Return a serializable dictionary representation of the class.
Parameters
----------
@@ -1179,10 +1239,14 @@ def json(cls, args=None):
Arguments as passed to the call method of the indicator.
If not given, the default arguments will be used when formatting the attributes.
+ Returns
+ -------
+ dict
+ A dictionary representation of the class.
+
Notes
-----
This is meant to be used by a third-party library wanting to wrap this class into another interface.
-
"""
names = ["identifier", "title", "abstract", "keywords"]
out = {key: getattr(cls, key) for key in names}
@@ -1217,7 +1281,8 @@ def _format(
args: dict | None = None,
formatter: AttrFormatter = default_formatter,
) -> dict:
- """Format attributes including {} tags with arguments.
+ """
+ Format attributes including {} tags with arguments.
Parameters
----------
@@ -1282,20 +1347,27 @@ def _format(
# The following static methods are meant to be replaced to define custom indicators.
@staticmethod
def compute(*args, **kwds):
- """Compute the indicator.
+ """
+ Compute the indicator.
This would typically be a function from `xclim.indices`.
- """
- raise NotImplementedError
+ """ # numpydoc ignore=PR01
+ raise NotImplementedError()
- def cfcheck(self, **das):
- """Compare metadata attributes to CF-Convention standards.
+ def cfcheck(self, **das) -> None:
+ r"""
+ Compare metadata attributes to CF-Convention standards.
Default cfchecks use the specifications in `xclim.core.utils.VARIABLES`,
assuming the indicator's inputs are using the CMIP6/xclim variable names correctly.
Variables absent from these default specs are silently ignored.
When subclassing this method, use functions decorated using `xclim.core.options.cfcheck`.
+
+ Parameters
+ ----------
+ **das : dict
+ A dictionary of DataArrays to check.
"""
for varname, vardata in das.items():
try:
@@ -1304,18 +1376,30 @@ def cfcheck(self, **das):
# Silently ignore unknown variables.
pass
- def datacheck(self, **das):
- """Verify that input data is valid.
+ def datacheck(self, **das) -> None:
+ r"""
+ Verify that input data is valid.
When subclassing this method, use functions decorated using `xclim.core.options.datacheck`.
For example, checks could include:
-
* assert no precipitation is negative
* assert no temperature has the same value 5 days in a row
This base datacheck checks that the input data has a valid sampling frequency, as given in self.src_freq.
If there are multiple inputs, it also checks if they all have the same frequency and the same anchor.
+
+ Parameters
+ ----------
+ **das : dict
+ A dictionary of DataArrays to check.
+
+ Raises
+ ------
+ ValidationError
+ - if the frequency of any input can't be inferred.
+ - if inputs have different frequencies.
+ - if inputs have a daily or hourly frequency, but they are not given at the same time of day.
"""
if self.src_freq is not None:
for da in das.values():
@@ -1340,15 +1424,28 @@ def __getattr__(self, attr):
raise AttributeError(attr)
@property
- def n_outs(self):
- """Return the length of all cf_attrs."""
+ def n_outs(self) -> int:
+ """
+ Return the length of all cf_attrs.
+
+ Returns
+ -------
+ int
+ The number of outputs.
+ """
return len(self.cf_attrs)
@property
- def parameters(self):
- """Create a dictionary of controllable parameters.
+ def parameters(self) -> dict:
+ """
+ Create a dictionary of controllable parameters.
Similar to :py:attr:`Indicator._all_parameters`, but doesn't include injected parameters.
+
+ Returns
+ -------
+ dict
+ A dictionary of controllable parameters.
"""
return {
name: param
@@ -1357,10 +1454,16 @@ def parameters(self):
}
@property
- def injected_parameters(self):
- """Return a dictionary of all injected parameters.
+ def injected_parameters(self) -> dict:
+ """
+ Return a dictionary of all injected parameters.
+
+ Inverse of :py:meth:`Indicator.parameters`.
- Opposite of :py:meth:`Indicator.parameters`.
+ Returns
+ -------
+ dict
+ A dictionary of all injected parameters.
"""
return {
name: param.value
@@ -1369,8 +1472,15 @@ def injected_parameters(self):
}
@property
- def is_generic(self):
- """Return True if the indicator is "generic", meaning that it can accept variables with any units."""
+ def is_generic(self) -> bool:
+ """
+ Return True if the indicator is "generic", meaning that it can accept variables with any units.
+
+ Returns
+ -------
+ bool
+ True if the indicator is generic.
+ """
return not hasattr(self.compute, "in_units")
def _show_deprecation_warning(self):
@@ -1383,8 +1493,9 @@ def _show_deprecation_warning(self):
)
-class CheckMissingIndicator(Indicator):
- """Class adding missing value checks to indicators.
+class CheckMissingIndicator(Indicator): # numpydoc ignore=PR01,PR02
+ r"""
+ Class adding missing value checks to indicators.
This should not be used as-is, but subclassed by implementing the `_get_missing_freq` method.
This method will be called in `_postprocess` using the compute parameters as only argument.
@@ -1469,8 +1580,9 @@ def _postprocess(self, outs, das, params):
return outs
-class ReducingIndicator(CheckMissingIndicator):
- """Indicator that performs a time-reducing computation.
+class ReducingIndicator(CheckMissingIndicator): # numpydoc ignore=PR01,PR02
+ """
+ Indicator that performs a time-reducing computation.
Compared to the base Indicator, this adds the handling of missing data.
@@ -1490,8 +1602,9 @@ def _get_missing_freq(self, params):
return None
-class ResamplingIndicator(CheckMissingIndicator):
- """Indicator that performs a resampling computation.
+class ResamplingIndicator(CheckMissingIndicator): # numpydoc ignore=PR02
+ """
+ Indicator that performs a resampling computation.
Compared to the base Indicator, this adds the handling of missing data,
and the check of allowed periods.
@@ -1597,8 +1710,15 @@ class Hourly(ResamplingIndicator):
base_registry["Daily"] = Daily
-def add_iter_indicators(module):
- """Create an iterable of loaded indicators."""
+def add_iter_indicators(module: ModuleType):
+ """
+ Create an iterable of loaded indicators.
+
+ Parameters
+ ----------
+ module : ModuleType
+ The module to add the iterator to.
+ """
if not hasattr(module, "iter_indicators"):
def iter_indicators():
@@ -1617,7 +1737,8 @@ def build_indicator_module(
doc: str | None = None,
reload: bool = False,
) -> ModuleType:
- """Create or update a module from imported objects.
+ """
+ Create or update a module from imported objects.
The module is inserted as a submodule of :py:mod:`xclim.indicators`.
@@ -1680,7 +1801,8 @@ def build_indicator_module_from_yaml( # noqa: C901
reload: bool = False,
validate: bool | PathLike = True,
) -> ModuleType:
- """Build or extend an indicator module from a YAML file.
+ """
+ Build or extend an indicator module from a YAML file.
The module is inserted as a submodule of :py:mod:`xclim.indicators`.
When given only a base filename (no 'yml' extension), this tries to find custom indices in a module
@@ -1691,16 +1813,15 @@ def build_indicator_module_from_yaml( # noqa: C901
filename : PathLike
Path to a YAML file or to the stem of all module files. See Notes for behaviour when passing a basename only.
name : str, optional
- The name of the new or existing module, defaults to the basename of the file.
- (e.g: `atmos.yml` -> `atmos`)
+ The name of the new or existing module, defaults to the basename of the file (e.g: `atmos.yml` -> `atmos`).
indices : Mapping of callables or module or path, optional
A mapping or module of indice functions or a python file declaring such a file.
When creating the indicator, the name in the `index_function` field is first sought
- here, then the indicator class will search in xclim.indices.generic and finally in xclim.indices.
+ here, then the indicator class will search in :py:mod:`xclim.indices.generic` and finally in :py:mod:`xclim.indices`.
translations : Mapping of dicts or path, optional
- Translated metadata for the new indicators. Keys of the mapping must be 2-char language tags.
+ Translated metadata for the new indicators. Keys of the mapping must be two-character language tags.
Values can be translations dictionaries as defined in :ref:`internationalization:Internationalization`.
- They can also be a path to a json file defining the translations.
+ They can also be a path to a JSON file defining the translations.
mode : {'raise', 'warn', 'ignore'}
How to deal with broken indice definitions.
encoding : str
@@ -1710,14 +1831,19 @@ def build_indicator_module_from_yaml( # noqa: C901
If reload is True and the module already exists, it is first removed before being rebuilt.
If False (default), indicators are added or updated, but not removed.
validate : bool or path
- If True (default), the yaml module is validated against xclim's schema.
- Can also be the path to a yml schema against which to validate.
+ If True (default), the yaml module is validated against the `xclim` schema.
+ Can also be the path to a YAML schema against which to validate;
Or False, in which case validation is simply skipped.
Returns
-------
ModuleType
- A submodule of `pym:mod:`xclim.indicators`.
+ A submodule of :py:mod:`xclim.indicators`.
+
+ See Also
+ --------
+ xclim.core.indicator : Indicator build logic.
+ build_module : Function to build a module from a dictionary of indicators.
Notes
-----
@@ -1731,12 +1857,6 @@ def build_indicator_module_from_yaml( # noqa: C901
- `example.py` : defining a few indice functions.
- `example.fr.json` : French translations
- `example.tlh.json` : Klingon translations.
-
- See Also
- --------
- xclim.core.indicator
-
- build_module
"""
filepath = Path(filename)
diff --git a/xclim/core/locales.py b/xclim/core/locales.py
index f68edff1f..bde4c411f 100644
--- a/xclim/core/locales.py
+++ b/xclim/core/locales.py
@@ -70,8 +70,17 @@
_LOCALES = {}
-def list_locales():
- """List of loaded locales. Includes all loaded locales, no matter how complete the translations are."""
+def list_locales() -> list:
+ """
+ List of loaded locales.
+
+ Includes all loaded locales, no matter how complete the translations are.
+
+ Returns
+ -------
+ list
+ A list of available locales.
+ """
return list(_LOCALES.keys())
@@ -93,25 +102,26 @@ def _valid_locales(locales):
def get_local_dict(locale: str | Sequence[str] | tuple[str, dict]) -> tuple[str, dict]:
- """Return all translated metadata for a given locale.
+ """
+ Return all translated metadata for a given locale.
Parameters
----------
- locale: str or sequence of str
+ locale : str or sequence of str
IETF language tag or a tuple of the language tag and a translation dict, or a tuple of the language
tag and a path to a json file defining translation of attributes.
- Raises
- ------
- UnavailableLocaleError
- If the given locale is not available.
-
Returns
-------
str
- The best fitting locale string
+ The best fitting locale string.
dict
The available translations in this locale.
+
+ Raises
+ ------
+ UnavailableLocaleError
+ If the given locale is not available.
"""
_valid_locales([locale])
@@ -141,7 +151,8 @@ def get_local_attrs(
names: Sequence[str] | None = None,
append_locale_name: bool = True,
) -> dict:
- """Get all attributes of an indicator in the requested locales.
+ r"""
+ Get all attributes of an indicator in the requested locales.
Parameters
----------
@@ -149,7 +160,7 @@ def get_local_attrs(
Indicator's class name, usually the same as in `xc.core.indicator.registry`.
If multiple names are passed, the attrs from each indicator are merged,
with the highest priority set to the first name.
- locales : str or tuple of str
+ *locales : str or tuple of str
IETF language tag or a tuple of the language tag and a translation dict, or a tuple of the language tag
and a path to a json file defining translation of attributes.
names : sequence of str, optional
@@ -157,16 +168,16 @@ def get_local_attrs(
append_locale_name : bool
If True (default), append the language tag (as "{attr_name}_{locale}") to the returned attributes.
- Raises
- ------
- ValueError
- If `append_locale_name` is False and multiple `locales` are requested.
-
Returns
-------
dict
All CF attributes available for given indicator and locales.
Warns and returns an empty dict if none were available.
+
+ Raises
+ ------
+ ValueError
+ If `append_locale_name` is False and multiple `locales` are requested.
"""
if isinstance(indicator, str):
indicator = [indicator]
@@ -198,13 +209,19 @@ def get_local_attrs(
def get_local_formatter(
locale: str | Sequence[str] | tuple[str, dict]
) -> AttrFormatter:
- """Return an AttrFormatter instance for the given locale.
+ """
+ Return an AttrFormatter instance for the given locale.
Parameters
----------
- locale: str or tuple of str
+ locale : str or tuple of str
IETF language tag or a tuple of the language tag and a translation dict, or a tuple of the language tag
and a path to a json file defining translation of attributes.
+
+ Returns
+ -------
+ AttrFormatter
+ A locale-based formatter object instance.
"""
_, loc_dict = get_local_dict(locale)
if "attrs_mapping" in loc_dict:
@@ -219,7 +236,14 @@ def get_local_formatter(
class UnavailableLocaleError(ValueError):
- """Error raised when a locale is requested but doesn't exist."""
+ """
+ Error raised when a locale is requested but doesn't exist.
+
+ Parameters
+ ----------
+ locale : str
+ The locale code.
+ """
def __init__(self, locale):
super().__init__(
@@ -230,7 +254,8 @@ def __init__(self, locale):
def read_locale_file(
filename, module: str | None = None, encoding: str = "UTF8"
) -> dict[str, dict]:
- """Read a locale file (.json) and return its dictionary.
+ """
+ Read a locale file (.json) and return its dictionary.
Parameters
----------
@@ -241,7 +266,12 @@ def read_locale_file(
Defaults to None, and no module name is added (as if the indicator was an official xclim indicator).
encoding : str
The encoding to use when reading the file.
- Defaults to UTF-8, overriding python's default mechanism which is machine dependent.
+ Defaults to `UTF-8`, overriding Python's default mechanism which is machine dependent.
+
+ Returns
+ -------
+ dict
+ The locale dictionary.
"""
locdict: dict[str, dict]
with open(filename, encoding=encoding) as f:
@@ -255,8 +285,9 @@ def read_locale_file(
return locdict
-def load_locale(locdata: str | Path | dict[str, dict], locale: str):
- """Load translations from a json file into xclim.
+def load_locale(locdata: str | Path | dict[str, dict], locale: str) -> None:
+ """
+ Load translations from a json file into xclim.
Parameters
----------
@@ -276,15 +307,20 @@ def load_locale(locdata: str | Path | dict[str, dict], locale: str):
def generate_local_dict(locale: str, init_english: bool = False) -> dict:
- """Generate a dictionary with keys for each indicator and translatable attributes.
+ """
+ Generate a dictionary with keys for each indicator and translatable attributes.
Parameters
----------
locale : str
- Locale in the IETF format
+ Locale in the IETF format.
init_english : bool
- If True, fills the initial dictionary with the english versions of the attributes.
- Defaults to False.
+ If True, fills the initial dictionary with the english versions of the attributes. Defaults to False.
+
+ Returns
+ -------
+ dict
+ Indicator translation dictionary.
"""
from ..core.indicator import registry # pylint: disable=import-outside-toplevel
diff --git a/xclim/core/missing.py b/xclim/core/missing.py
index d2d6d6f24..ed644b3ce 100644
--- a/xclim/core/missing.py
+++ b/xclim/core/missing.py
@@ -55,12 +55,25 @@
class MissingBase:
- """Base class used to determined where Indicator outputs should be masked.
+ r"""Base class used to determined where Indicator outputs should be masked.
Subclasses should implement `is_missing` and `validate` methods.
Decorate subclasses with `xclim.core.options.register_missing_method` to add them
to the registry before using them in an Indicator.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ Input data.
+ freq : str
+ Resampling frequency.
+ src_timestep : str, Optional
+ The expected input frequency. If not given, it will be inferred from the input array.
+ **indexer : Indexer
+ Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
+ values, month=1 to select January, or month=[6,7,8] to select summer months.
+ If not indexer is given, all values are considered.
"""
def __init__(self, da, freq, src_timestep, **indexer):
@@ -73,13 +86,53 @@ def __init__(self, da, freq, src_timestep, **indexer):
self.null, self.count = self.prepare(da, freq, src_timestep, **indexer)
@classmethod
- def execute(cls, da, freq, src_timestep, options, indexer):
- """Create the instance and call it in one operation."""
+ def execute(
+ cls,
+ da: xr.DataArray,
+ freq: str,
+ src_timestep: str,
+ options: dict,
+ indexer: dict,
+ ) -> xr.DataArray:
+ """Create the instance and call it in one operation.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ Input data.
+ freq : str
+ Resampling frequency.
+ src_timestep : str
+ The expected input frequency. If not given, it will be inferred from the input array.
+ options : dict
+ Dictionary of options to pass to the instance.
+ indexer : dict
+ Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
+ values, month=1 to select January, or month=[6,7,8] to select summer months.
+ If no indexer is given, all values are considered.
+
+ Returns
+ -------
+ xr.DataArray
+ The executed DataArray.
+ """
obj = cls(da, freq, src_timestep, **indexer)
return obj(**options)
@staticmethod
- def split_freq(freq):
+ def split_freq(freq: str) -> list[str] | tuple[str, None]:
+ """Split the frequency into a base frequency and a suffix.
+
+ Parameters
+ ----------
+ freq : str
+ The frequency string.
+
+ Returns
+ -------
+ list of str or (str, None)
+ A list of the base frequency and the suffix, or a tuple with the base frequency and None.
+ """
if freq is None:
return "", None
@@ -89,8 +142,31 @@ def split_freq(freq):
return freq, None
@staticmethod
- def is_null(da, freq, **indexer):
- """Return a boolean array indicating which values are null."""
+ def is_null(
+ da: xr.DataArray, freq: str, **indexer
+ ) -> xr.DataArray | xr.DataArray.resample:
+ r"""Return a boolean array indicating which values are null.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ Input data.
+ freq : str
+ Resampling frequency, from the periods defined in :ref:`timeseries.resampling`.
+ **indexer : {dim: indexer}, optional
+ The time attribute and values over which to subset the array. For example, use season='DJF' to select winter
+ values, month=1 to select January, or month=[6,7,8] to select summer months.
+
+ Returns
+ -------
+ xr.DataArray
+ Boolean array indicating which values are null.
+
+ Raises
+ ------
+ ValueError
+ If no data is available for the selected period.
+ """
indexer.update({"drop": True})
selected = select_time(da, **indexer)
if selected.time.size == 0:
@@ -102,7 +178,9 @@ def is_null(da, freq, **indexer):
return null
- def prepare(self, da, freq, src_timestep, **indexer):
+ def prepare(
+ self, da: xr.DataArray, freq: str, src_timestep: str, **indexer
+ ) -> tuple[xr.DataArray, xr.DataArray]:
r"""Prepare arrays to be fed to the `is_missing` function.
Parameters
@@ -113,7 +191,7 @@ def prepare(self, da, freq, src_timestep, **indexer):
Resampling frequency, from the periods defined in :ref:`timeseries.resampling`.
src_timestep : str
Expected input frequency, from the periods defined in :ref:`timeseries.resampling`.
- \*\*indexer : {dim: indexer}, optional
+ **indexer : {dim: indexer}, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
values, month=1 to select January, or month=[6,7,8] to select summer months. If not indexer is given,
all values are considered.
@@ -123,6 +201,11 @@ def prepare(self, da, freq, src_timestep, **indexer):
xr.DataArray, xr.DataArray
Boolean array indicating which values are null, array of expected number of valid values.
+ Raises
+ ------
+ NotImplementedError
+ If no frequency is provided and the source timestep is not a divisor of the resampling frequency.
+
Notes
-----
If `freq=None` and an indexer is given, then missing values during period at the start or end of array won't be
@@ -186,13 +269,24 @@ def prepare(self, da, freq, src_timestep, **indexer):
return null, count
- def is_missing(self, null, count, **kwargs):
+ def is_missing(self, null, count, **kwargs): # numpydoc ignore=PR01
"""Return whether the values within each period should be considered missing or not."""
- raise NotImplementedError
+ raise NotImplementedError()
@staticmethod
- def validate(**kwargs):
- """Return whether options arguments are valid or not."""
+ def validate(**kwargs) -> bool:
+ r"""Return whether options arguments are valid or not.
+
+ Parameters
+ ----------
+ **kwargs : dict
+ Options arguments.
+
+ Returns
+ -------
+ bool
+ Whether the options are valid or not.
+ """
return True
def __call__(self, **kwargs):
@@ -210,15 +304,15 @@ def __call__(self, **kwargs):
class MissingAny(MissingBase):
r"""Return whether there are missing days in the array.
- Attributes
+ Parameters
----------
da : xr.DataArray
Input array.
- freq: str
+ freq : str
Resampling frequency.
- src_timestep: {"D", "h", "M"}
+ src_timestep : {"D", "h", "M"}
Expected input frequency.
- indexer: {dim: indexer, }, optional
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
values, month=1 to select January, or month=[6,7,8] to select summer months.
If not indexer is given, all values are considered.
@@ -229,7 +323,28 @@ class MissingAny(MissingBase):
A boolean array set to True if period has missing values.
"""
- def is_missing(self, null, count): # noqa
+ def __init__(self, da, freq, src_timestep, **indexer):
+ super().__init__(da, freq, src_timestep, **indexer)
+
+ def is_missing(
+ self, null: xr.DataArray, count: xr.DataArray, **kwargs
+ ) -> xr.DataArray: # noqa
+ r"""Return whether the values within each period should be considered missing or not.
+
+ Parameters
+ ----------
+ null : xr.DataArray
+ Boolean array indicating which values are null.
+ count : xr.DataArray
+ Array of expected number of valid values.
+ **kwargs : dict
+ Additional arguments.
+
+ Returns
+ -------
+ xr.DataArray
+ A boolean array set to True if period has missing values.
+ """
cond0 = null.count(dim="time") != count # Check total number of days
cond1 = null.sum(dim="time") > 0 # Check if any is missing
return cond0 | cond1
@@ -248,7 +363,7 @@ class MissingWMO(MissingAny):
Stricter criteria are sometimes used in practice, with a tolerance of 5 missing values or 3 consecutive missing
values.
- Attributes
+ Parameters
----------
da : DataArray
Input array.
@@ -260,7 +375,7 @@ class MissingWMO(MissingAny):
Number of consecutive missing values per month that should not be exceeded.
src_timestep : {"D"}
Expected input frequency. Only daily values are supported.
- indexer: {dim: indexer, }, optional
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
values, month=1 to select January, or month=[6,7,8] to select summer months.
@@ -290,8 +405,36 @@ def __init__(self, da, freq, src_timestep, **indexer):
super().__init__(da, freq, src_timestep, **indexer)
@classmethod
- def execute(cls, da, freq, src_timestep, options, indexer):
- """Create the instance and call it in one operation."""
+ def execute(
+ cls,
+ da: xr.DataArray,
+ freq: str,
+ src_timestep: str,
+ options: dict,
+ indexer: dict,
+ ) -> xr.DataArray:
+ """Create the instance and call it in one operation.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ Input data.
+ freq : str
+ Resampling frequency.
+ src_timestep : str
+ The expected input frequency. If not given, it will be inferred from the input array.
+ options : dict
+ Dictionary of options to pass to the instance.
+ indexer : dict
+ Time attribute and values over which to subset the array. For example, use season='DJF' to select winter
+ values, month=1 to select January, or month=[6,7,8] to select summer months.
+ If not indexer is given, all values are considered.
+
+ Returns
+ -------
+ xr.DataArray
+ The executed WMO-missing standards-compliant DataArray.
+ """
if freq[0] not in ["Y", "A", "Q", "M"]:
raise ValueError(
"MissingWMO can only be used with Monthly or longer frequencies."
@@ -327,7 +470,7 @@ def validate(nm, nc):
class MissingPct(MissingBase):
r"""Return whether there are more missing days in the array than a given percentage.
- Attributes
+ Parameters
----------
da : DataArray
Input array.
@@ -337,7 +480,7 @@ class MissingPct(MissingBase):
Fraction of missing values that are tolerated [0,1].
src_timestep : {"D", "h"}
Expected input frequency.
- indexer : {dim: indexer, }, optional
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter values,
month=1 to select January, or month=[6,7,8] to select summer months.
If not indexer is given, all values are considered.
@@ -422,8 +565,8 @@ class FromContext(MissingBase):
See Also
--------
- xclim.set_options
- xclim.core.options.register_missing_method
+ xclim.set_options : For modifying run configurations
+ xclim.core.options.register_missing_method : For adding new missing value detection algorithms.
"""
@classmethod
@@ -442,29 +585,49 @@ def execute(cls, da, freq, src_timestep, options, indexer):
# user-friendly. This can also be useful for testing.
-def missing_any(da, freq, src_timestep=None, **indexer): # noqa: D103
+def missing_any( # noqa: D103 # numpydoc ignore=GL08
+ da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer
+) -> xr.DataArray:
+ """Return whether there are missing days in the array."""
src_timestep = src_timestep or xr.infer_freq(da.time)
return MissingAny(da, freq, src_timestep, **indexer)()
-def missing_wmo(da, freq, nm=11, nc=5, src_timestep=None, **indexer): # noqa: D103
+def missing_wmo( # noqa: D103 # numpydoc ignore=GL08
+ da: xr.DataArray,
+ freq: str,
+ nm: int = 11,
+ nc: int = 5,
+ src_timestep: str | None = None,
+ **indexer,
+) -> xr.DataArray:
src_timestep = src_timestep or xr.infer_freq(da.time)
return MissingWMO.execute(
da, freq, src_timestep, options={"nm": nm, "nc": nc}, indexer=indexer
)
-def missing_pct(da, freq, tolerance, src_timestep=None, **indexer): # noqa: D103
+def missing_pct( # noqa: D103 # numpydoc ignore=GL08
+ da: xr.DataArray,
+ freq: str,
+ tolerance: float,
+ src_timestep: str | None = None,
+ **indexer,
+) -> xr.DataArray:
src_timestep = src_timestep or xr.infer_freq(da.time)
return MissingPct(da, freq, src_timestep, **indexer)(tolerance=tolerance)
-def at_least_n_valid(da, freq, n=1, src_timestep=None, **indexer): # noqa: D103
+def at_least_n_valid( # noqa: D103 # numpydoc ignore=GL08
+ da: xr.DataArray, freq: str, n: int = 1, src_timestep: str | None = None, **indexer
+) -> xr.DataArray:
src_timestep = src_timestep or xr.infer_freq(da.time)
return AtLeastNValid(da, freq, src_timestep, **indexer)(n=n)
-def missing_from_context(da, freq, src_timestep=None, **indexer): # noqa: D103
+def missing_from_context( # noqa: D103 # numpydoc ignore=GL08
+ da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer
+) -> xr.DataArray:
src_timestep = src_timestep or xr.infer_freq(da.time)
return FromContext.execute(da, freq, src_timestep, options={}, indexer=indexer)
diff --git a/xclim/core/options.py b/xclim/core/options.py
index 47cab12c5..dcfd8cf3b 100644
--- a/xclim/core/options.py
+++ b/xclim/core/options.py
@@ -96,7 +96,19 @@ def _set_metadata_locales(locales):
def register_missing_method(name: str) -> Callable:
- """Register missing method."""
+ """
+ Register missing method.
+
+ Parameters
+ ----------
+ name : str
+ Name of missing method.
+
+ Returns
+ -------
+ Callable
+ Decorator function.
+ """
def _register_missing_method(cls):
sig = signature(cls.is_missing)
@@ -113,8 +125,26 @@ def _register_missing_method(cls):
return _register_missing_method
-def _run_check(func, option, *args, **kwargs):
- """Run function and customize exception handling based on option."""
+def run_check(func, option, *args, **kwargs):
+ r"""
+ Run function and customize exception handling based on option.
+
+ Parameters
+ ----------
+ func : Callable
+ Function to run.
+ option : str
+ Option to use.
+ *args : tuple
+ Positional arguments to pass to the function.
+ **kwargs : dict
+ Keyword arguments to pass to the function.
+
+ Raises
+ ------
+ ValidationError
+ If the function raises a ValidationError and the option is set to "raise".
+ """
try:
func(*args, **kwargs)
except ValidationError as err:
@@ -122,32 +152,56 @@ def _run_check(func, option, *args, **kwargs):
def datacheck(func: Callable) -> Callable:
- """Decorate functions checking data inputs validity."""
+ """
+ Decorate functions checking data inputs validity.
+
+ Parameters
+ ----------
+ func : Callable
+ Function to decorate.
+
+ Returns
+ -------
+ Callable
+ Decorated function.
+ """
@wraps(func)
- def run_check(*args, **kwargs):
- return _run_check(func, DATA_VALIDATION, *args, **kwargs)
+ def _run_check(*args, **kwargs):
+ return run_check(func, DATA_VALIDATION, *args, **kwargs)
- return run_check
+ return _run_check
def cfcheck(func: Callable) -> Callable:
- """Decorate functions checking CF-compliance of DataArray attributes.
+ """
+ Decorate functions checking CF-compliance of DataArray attributes.
Functions should raise ValidationError exceptions whenever attributes are non-conformant.
+
+ Parameters
+ ----------
+ func : Callable
+ Function to decorate.
+
+ Returns
+ -------
+ Callable
+ Decorated function.
"""
@wraps(func)
- def run_check(*args, **kwargs):
- return _run_check(func, CF_COMPLIANCE, *args, **kwargs)
+ def _run_check(*args, **kwargs):
+ return run_check(func, CF_COMPLIANCE, *args, **kwargs)
- return run_check
+ return _run_check
-class set_options:
- """Set options for xclim in a controlled context.
+class set_options: # numpydoc ignore=PR01,PR02
+ r"""
+ Set options for xclim in a controlled context.
- Attributes
+ Parameters
----------
metadata_locales : list[Any]
List of IETF language tags or tuples of language tags and a translation dict, or
@@ -168,14 +222,15 @@ class set_options:
Dictionary of options to pass to the missing method. Keys must the name of
missing method and values must be mappings from option names to values.
run_length_ufunc : str
- Whether to use the 1D ufunc version of run length algorithms or the dask-ready broadcasting version.
- Default is ``"auto"``, which means the latter is used for dask-backed and large arrays.
+ Whether to use the 1D ufunc version of run length algorithms or the dask-ready broadcasting version.
+ Default is ``"auto"``, which means the latter is used for dask-backed and large arrays.
sdba_extra_output : bool
- Whether to add diagnostic variables to outputs of sdba's `train`, `adjust`
- and `processing` operations. Details about these additional variables are given in the object's
- docstring. When activated, `adjust` will return a Dataset with `scen` and those extra diagnostics
- For `processing` functions, see the doc, the output type might change, or not depending on the
- algorithm. Default: ``False``.
+ Whether to add diagnostic variables to outputs of sdba's `train`, `adjust` and `processing` operations.
+ Details about these additional variables are given in the object's docstring.
+ When activated, `adjust` will return a Dataset with `scen` and those extra diagnostics.
+ For `processing` functions, see the documentation, the output type might change, or not depending on the
+ algorithm.
+ Default: ``False``.
sdba_encode_cf : bool
Whether to encode cf coordinates in the ``map_blocks`` optimization that most adjustment methods are based on.
This should have no impact on the results, but should run much faster in the graph creation phase.
@@ -183,14 +238,14 @@ class set_options:
Controls attributes handling in indicators. If True, attributes from all inputs are merged
using the `drop_conflicts` strategy and then updated with xclim-provided attributes.
If ``as_dataset`` is also True and a dataset was passed to the ``ds`` argument of the Indicator,
- the dataset's attributes are copied to the indicator's output.
- If False, attributes from the inputs are ignored. If "xarray", xclim will use xarray's `keep_attrs` option.
+ the dataset's attributes are copied to the indicator's output. If False, attributes from the inputs are ignored.
+ If "xarray", xclim will use xarray's `keep_attrs` option.
Note that xarray's "default" is equivalent to False. Default: ``"xarray"``.
as_dataset : bool
If True, indicators output datasets. If False, they output DataArrays. Default :``False``.
- resample_map_blocks: bool
+ resample_map_blocks : bool
If True, some indicators will wrap their resampling operations with `xr.map_blocks`, using :py:func:`xclim.indices.helpers.resample_map`.
- This requires `flox` to be installed in order to ensure the chunking is appropriate.git
+ This requires `flox` to be installed in order to ensure the chunking is appropriate.
Examples
--------
diff --git a/xclim/core/units.py b/xclim/core/units.py
index 38b21a3ff..909808a8d 100644
--- a/xclim/core/units.py
+++ b/xclim/core/units.py
@@ -36,6 +36,7 @@
__all__ = [
"amount2lwethickness",
"amount2rate",
+ "cf_conversion",
"check_units",
"convert_units_to",
"declare_relative_units",
@@ -47,6 +48,7 @@
"infer_context",
"infer_sampling_units",
"lwethickness2amount",
+ "pint2cfattrs",
"pint2cfunits",
"pint_multiply",
"rate2amount",
@@ -108,15 +110,16 @@
units.add_context(hydro)
-with (files("xclim.data") / "variables.yml").open() as f:
- CF_CONVERSIONS = safe_load(f)["conversions"]
+with (files("xclim.data") / "variables.yml").open() as variables:
+ CF_CONVERSIONS = safe_load(variables)["conversions"]
_CONVERSIONS = {}
# FIXME: This needs to be properly annotated for mypy compliance.
# See: https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
def _register_conversion(conversion, direction):
- """Register a conversion function to be automatically picked up in `convert_units_to`.
+ """
+ Register a conversion function to be automatically picked up in `convert_units_to`.
The function must correspond to a name in `CF_CONVERSIONS`, so to a section in
`xclim/data/variables.yml::conversions`.
@@ -140,7 +143,8 @@ def _func_register(func: Callable) -> Callable:
def units2pint(
value: xr.DataArray | units.Unit | units.Quantity | dict | str,
) -> pint.Unit:
- """Return the pint Unit for the DataArray units.
+ """
+ Return the pint Unit for the DataArray units.
Parameters
----------
@@ -204,7 +208,8 @@ def units2pint(
def pint2cfunits(value: units.Quantity | units.Unit) -> str:
- """Return a CF-compliant unit string from a `pint` unit.
+ """
+ Return a CF-compliant unit string from a `pint` unit.
Parameters
----------
@@ -224,7 +229,8 @@ def pint2cfunits(value: units.Quantity | units.Unit) -> str:
def pint2cfattrs(value: units.Quantity | units.Unit, is_difference=None) -> dict:
- """Return CF-compliant units attributes from a `pint` unit.
+ """
+ Return CF-compliant units attributes from a `pint` unit.
Parameters
----------
@@ -258,9 +264,20 @@ def pint2cfattrs(value: units.Quantity | units.Unit, is_difference=None) -> dict
def ensure_cf_units(ustr: str) -> str:
- """Ensure the passed unit string is CF-compliant.
+ """
+ Ensure the passed unit string is CF-compliant.
+
+ The string will be parsed to `pint` then recast to a string by :py:func:`xclim.core.units.pint2cfunits`.
- The string will be parsed to pint then recast to a string by xclim's `pint2cfunits`.
+ Parameters
+ ----------
+ ustr : str
+ A unit string.
+
+ Returns
+ -------
+ str
+ The unit string in CF-compliant form.
"""
return pint2cfunits(units2pint(ustr))
@@ -268,7 +285,8 @@ def ensure_cf_units(ustr: str) -> str:
def pint_multiply(
da: xr.DataArray, q: Any, out_units: str | None = None
) -> xr.DataArray:
- """Multiply xarray.DataArray by pint.Quantity.
+ """
+ Multiply xarray.DataArray by pint.Quantity.
Parameters
----------
@@ -282,6 +300,7 @@ def pint_multiply(
Returns
-------
xr.DataArray
+ The product DataArray.
"""
a = 1 * units2pint(da) # noqa
f = a * q.to_base_units()
@@ -295,13 +314,14 @@ def pint_multiply(
def str2pint(val: str) -> pint.Quantity:
- """Convert a string to a pint.Quantity, splitting the magnitude and the units.
+ """
+ Convert a string to a pint.Quantity, splitting the magnitude and the units.
Parameters
----------
val : str
A quantity in the form "[{magnitude} ]{units}", where magnitude can be cast to a float and
- units is understood by `units2pint`.
+ units is understood by :py:func:`xclim.core.units.units2pint`.
Returns
-------
@@ -323,7 +343,8 @@ def convert_units_to( # noqa: C901
target: Quantified | units.Unit | dict,
context: Literal["infer", "hydro", "none"] | None = None,
) -> xr.DataArray | float:
- """Convert a mathematical expression into a value with the same units as a DataArray.
+ """
+ Convert a mathematical expression into a value with the same units as a DataArray.
If the dimensionalities of source and target units differ, automatic CF conversions
will be applied when possible. See :py:func:`xclim.core.units.cf_conversion`.
@@ -350,11 +371,11 @@ def convert_units_to( # noqa: C901
See Also
--------
- cf_conversion
- amount2rate
- rate2amount
- amount2lwethickness
- lwethickness2amount
+ cf_conversion : Get the standard name of the specific conversion for the given standard name.
+ amount2rate : Convert an amount to a rate.
+ rate2amount : Convert a rate to an amount.
+ amount2lwethickness : Convert an amount to a liquid water equivalent thickness.
+ lwethickness2amount : Convert a liquid water equivalent thickness to an amount.
"""
context = context or "none"
@@ -435,7 +456,8 @@ def convert_units_to( # noqa: C901
def cf_conversion(
standard_name: str, conversion: str, direction: Literal["to", "from"]
) -> str | None:
- """Get the standard name of the specific conversion for the given standard name.
+ """
+ Get the standard name of the specific conversion for the given standard name.
Parameters
----------
@@ -479,7 +501,8 @@ def infer_sampling_units(
deffreq: str | None = "D",
dim: str = "time",
) -> tuple[int, str]:
- """Infer a multiplier and the units corresponding to one sampling period.
+ """
+ Infer a multiplier and the units corresponding to one sampling period.
Parameters
----------
@@ -490,17 +513,17 @@ def infer_sampling_units(
dim : str
Dimension from which to infer the frequency.
- Raises
- ------
- ValueError
- If the frequency has no exact corresponding units.
-
Returns
-------
int
- The magnitude (number of base periods per period)
+ The magnitude (number of base periods per period).
str
Units as a string, understandable by pint.
+
+ Raises
+ ------
+ ValueError
+ If the frequency has no exact corresponding units.
"""
dimmed = getattr(da, dim)
freq = xr.infer_freq(dimmed)
@@ -527,13 +550,24 @@ def infer_sampling_units(
def ensure_absolute_temperature(units: str) -> str:
- """Convert temperature units to their absolute counterpart, assuming they represented a difference (delta).
+ """
+ Convert temperature units to their absolute counterpart, assuming they represented a difference (delta).
Celsius becomes Kelvin, Fahrenheit becomes Rankine. Does nothing for other units.
+ Parameters
+ ----------
+ units : str
+ Units to transform.
+
+ Returns
+ -------
+ str
+ The transformed units.
+
See Also
--------
- :py:func:`ensure_delta`
+ ensure_delta : Ensure a unit is a delta unit.
"""
a = str2pint(units)
# ensure a delta pint unit
@@ -544,7 +578,8 @@ def ensure_absolute_temperature(units: str) -> str:
def ensure_delta(unit: xr.DataArray | str | units.Quantity) -> str:
- """Return delta units for temperature.
+ """
+ Return delta units for temperature.
For dimensions where delta exist in pint (Temperature), it replaces the temperature unit by delta_degC or
delta_degF based on the input unit. For other dimensionality, it just gives back the input units.
@@ -552,7 +587,12 @@ def ensure_delta(unit: xr.DataArray | str | units.Quantity) -> str:
Parameters
----------
unit : str
- unit to transform in delta (or not)
+ Unit to transform in delta (or not).
+
+ Returns
+ -------
+ str
+ The transformed units.
"""
u = units2pint(unit)
d = 1 * u
@@ -571,7 +611,8 @@ def ensure_delta(unit: xr.DataArray | str | units.Quantity) -> str:
def to_agg_units(
out: xr.DataArray, orig: xr.DataArray, op: str, dim: str = "time"
) -> xr.DataArray:
- """Set and convert units of an array after an aggregation operation along the sampling dimension (time).
+ """
+ Set and convert units of an array after an aggregation operation along the sampling dimension (time).
Parameters
----------
@@ -589,6 +630,7 @@ def to_agg_units(
Returns
-------
xr.DataArray
+ The DataArray with aggregated values.
Examples
--------
@@ -805,7 +847,8 @@ def rate2amount(
sampling_rate_from_coord: bool = False,
out_units: str | None = None,
) -> xr.DataArray:
- """Convert a rate variable to an amount by multiplying by the sampling period length.
+ """
+ Convert a rate variable to an amount by multiplying by the sampling period length.
If the sampling period length cannot be inferred, the rate values
are multiplied by the duration between their time coordinate and the next one. The last period
@@ -815,25 +858,30 @@ def rate2amount(
Parameters
----------
- rate : xr.DataArray, pint.Quantity or string
+ rate : xr.DataArray or pint.Quantity or str
"Rate" variable, with units of "amount" per time. Ex: Precipitation in "mm / d".
dim : str or DataArray
The name of time dimension or the coordinate itself.
- sampling_rate_from_coord : boolean
+ sampling_rate_from_coord : bool
For data with irregular time coordinates. If True, the diff of the time coordinate will be used as the sampling rate,
meaning each data point will be assumed to apply for the interval ending at the next point. See notes.
Defaults to False, which raises an error if the time coordinate is irregular.
out_units : str, optional
Specific output units, if needed.
+ Returns
+ -------
+ xr.DataArray or Quantity
+ The converted variable. The standard_name of `rate` is modified if a conversion is found.
+
Raises
------
ValueError
If the time coordinate is irregular and `sampling_rate_from_coord` is False (default).
- Returns
- -------
- xr.DataArray or Quantity
+ See Also
+ --------
+ amount2rate : Convert an amount to a rate.
Examples
--------
@@ -865,10 +913,6 @@ def rate2amount(
>>> pram = rate2amount(pr, out_units="pc") # Get rain amount in parsecs. Why not.
>>> pram.values
array([7.00008327e-18, 1.63335276e-17, 1.63335276e-17])
-
- See Also
- --------
- amount2rate
"""
return _rate_and_amount_converter(
rate,
@@ -886,7 +930,8 @@ def amount2rate(
sampling_rate_from_coord: bool = False,
out_units: str | None = None,
) -> xr.DataArray:
- """Convert an amount variable to a rate by dividing by the sampling period length.
+ """
+ Convert an amount variable to a rate by dividing by the sampling period length.
If the sampling period length cannot be inferred, the amount values
are divided by the duration between their time coordinate and the next one. The last period
@@ -896,11 +941,11 @@ def amount2rate(
Parameters
----------
- amount : xr.DataArray, pint.Quantity or string
+ amount : xr.DataArray or pint.Quantity or str
"amount" variable. Ex: Precipitation amount in "mm".
dim : str or xr.DataArray
The name of the time dimension or the time coordinate itself.
- sampling_rate_from_coord : boolean
+ sampling_rate_from_coord : bool
For data with irregular time coordinates.
If True, the diff of the time coordinate will be used as the sampling rate,
meaning each data point will be assumed to span the interval ending at the next point.
@@ -909,18 +954,19 @@ def amount2rate(
out_units : str, optional
Specific output units, if needed.
+ Returns
+ -------
+ xr.DataArray or Quantity
+ The converted variable. The standard_name of `amount` is modified if a conversion is found.
+
Raises
------
ValueError
If the time coordinate is irregular and `sampling_rate_from_coord` is False (default).
- Returns
- -------
- xr.DataArray or Quantity
-
See Also
--------
- rate2amount
+ rate2amount : Convert a rate to an amount.
"""
return _rate_and_amount_converter(
amount,
@@ -935,7 +981,8 @@ def amount2rate(
def amount2lwethickness(
amount: xr.DataArray, out_units: str | None = None
) -> xr.DataArray | Quantified:
- """Convert a liquid water amount (mass over area) to its equivalent area-averaged thickness (length).
+ """
+ Convert a liquid water amount (mass over area) to its equivalent area-averaged thickness (length).
This will simply divide the amount by the density of liquid water, 1000 kg/m³.
This is equivalent to using the "hydro" context of :py:data:`xclim.core.units.units`.
@@ -956,7 +1003,7 @@ def amount2lwethickness(
See Also
--------
- lwethickness2amount
+ lwethickness2amount : Convert a liquid water equivalent thickness to an amount.
"""
water_density = str2pint("1000 kg m-3")
out = pint_multiply(amount, 1 / water_density)
@@ -972,7 +1019,8 @@ def amount2lwethickness(
def lwethickness2amount(
thickness: xr.DataArray, out_units: str | None = None
) -> xr.DataArray | Quantified:
- """Convert a liquid water thickness (length) to its equivalent amount (mass over area).
+ """
+ Convert a liquid water thickness (length) to its equivalent amount (mass over area).
This will simply multiply the thickness by the density of liquid water, 1000 kg/m³.
This is equivalent to using the "hydro" context of :py:data:`xclim.core.units.units`.
@@ -992,7 +1040,7 @@ def lwethickness2amount(
See Also
--------
- amount2lwethickness
+ amount2lwethickness : Convert an amount to a liquid water equivalent thickness.
"""
water_density = str2pint("1000 kg m-3")
out = pint_multiply(thickness, water_density)
@@ -1055,23 +1103,29 @@ def rate2flux(
density: Quantified,
out_units: str | None = None,
) -> xr.DataArray:
- """Convert a rate variable to a flux by multiplying with a density.
+ """
+ Convert a rate variable to a flux by multiplying with a density.
This is the inverse operation of :py:func:`xclim.core.units.flux2rate`.
Parameters
----------
rate : xr.DataArray
- "Rate" variable. Ex: Snowfall rate in "mm / d".
+ "Rate" variable, e.g. Snowfall rate in "mm / d".
density : Quantified
- Density used to convert from a rate to a flux. Ex: Snowfall density "312 kg m-3".
+ Density used to convert from a rate to a flux, e.g. Snowfall density "312 kg m-3".
Density can also be an array with the same shape as `rate`.
out_units : str, optional
Specific output units, if needed.
Returns
-------
- flux: xr.DataArray
+ xr.DataArray
+ The converted flux value.
+
+ See Also
+ --------
+ flux2rate : Convert a flux to a rate.
Examples
--------
@@ -1087,10 +1141,6 @@ def rate2flux(
'kg m-2 s-1'
>>> float(prsn[0])
0.1
-
- See Also
- --------
- flux2rate
"""
return _flux_and_rate_converter(
rate,
@@ -1105,23 +1155,29 @@ def flux2rate(
density: Quantified,
out_units: str | None = None,
) -> xr.DataArray:
- """Convert a flux variable to a rate by dividing with a density.
+ """
+ Convert a flux variable to a rate by dividing with a density.
This is the inverse operation of :py:func:`xclim.core.units.rate2flux`.
Parameters
----------
flux : xr.DataArray
- "flux" variable. Ex: Snowfall flux in "kg m-2 s-1".
+ "flux" variable, e.g. Snowfall flux in "kg m-2 s-1".
density : Quantified
- Density used to convert from a flux to a rate. Ex: Snowfall density "312 kg m-3".
+ Density used to convert from a flux to a rate, e.g. Snowfall density "312 kg m-3".
Density can also be an array with the same shape as `flux`.
out_units : str, optional
Specific output units, if needed.
Returns
-------
- rate: xr.DataArray
+ xr.DataArray
+ The converted rate value.
+
+ See Also
+ --------
+ rate2flux : Convert a rate to a flux.
Examples
--------
@@ -1140,10 +1196,6 @@ def flux2rate(
'mm s-1'
>>> float(prsnd[0])
1.0
-
- See Also
- --------
- rate2flux
"""
return _flux_and_rate_converter(
flux,
@@ -1157,7 +1209,8 @@ def flux2rate(
def check_units(
val: str | xr.DataArray | None, dim: str | xr.DataArray | None = None
) -> None:
- """Check that units are compatible with dimensions, otherwise raise a `ValidationError`.
+ """
+ Check that units are compatible with dimensions, otherwise raise a `ValidationError`.
Parameters
----------
@@ -1237,7 +1290,8 @@ def check_units(
def _check_output_has_units(
out: xr.DataArray | tuple[xr.DataArray] | xr.Dataset,
) -> None:
- """Perform very basic sanity check on the output.
+ """
+ Perform very basic sanity check on the output.
Indices are responsible for unit management. If this fails, it's a developer's error.
"""
@@ -1255,21 +1309,27 @@ def _check_output_has_units(
# FIXME: This needs to be properly annotated for mypy compliance.
# See: https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
def declare_relative_units(**units_by_name) -> Callable:
- r"""Function decorator checking the units of arguments.
+ r"""
+ Function decorator checking the units of arguments.
The decorator checks that input values have units that are compatible with each other.
It also stores the input units as a 'relative_units' attribute.
Parameters
----------
- \*\*kwargs
+ **units_by_name : dict
Mapping from the input parameter names to dimensions relative to other parameters.
The dimensions can be a single parameter name as `` or more complex expressions,
- like: ` * [time]`.
+ such as ` * [time]`.
Returns
-------
Callable
+ The decorated function.
+
+ See Also
+ --------
+ declare_units : A decorator to check units of function arguments.
Examples
--------
@@ -1291,13 +1351,9 @@ def func(da, thresh, thresh2): ...
temperature_func = declare_units(da="[temperature]")(func)
This call will replace the "" by "[temperature]" everywhere needed.
-
- See Also
- --------
- declare_units
"""
- def dec(func):
+ def dec(func): # numpydoc ignore=GL08
sig = signature(func)
# Check if units are valid
@@ -1331,7 +1387,7 @@ def dec(func):
) from e
@wraps(func)
- def wrapper(*args, **kwargs):
+ def wrapper(*args, **kwargs): # numpydoc ignore=GL08
# Match all passed values to their proper arguments, so we can check units
bound_args = sig.bind(*args, **kwargs)
for name, dim in units_by_name.items():
@@ -1366,14 +1422,15 @@ def wrapper(*args, **kwargs):
# FIXME: This needs to be properly annotated for mypy compliance.
# See: https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
def declare_units(**units_by_name) -> Callable:
- r"""Create a decorator to check units of function arguments.
+ r"""
+ Create a decorator to check units of function arguments.
The decorator checks that input and output values have units that are compatible with expected dimensions.
- It also stores the input units as a 'in_units' attribute.
+ It also stores the input units as an 'in_units' attribute.
Parameters
----------
- \*\*units_by_name
+ **units_by_name : dict
Mapping from the input parameter names to their units or dimensionality ("[...]").
If this decorates a function previously decorated with :py:func:`declare_relative_units`,
the relative unit declarations are made absolute with the information passed here.
@@ -1381,6 +1438,11 @@ def declare_units(**units_by_name) -> Callable:
Returns
-------
Callable
+ The decorated function.
+
+ See Also
+ --------
+ declare_relative_units : A decorator to check for relative units of function arguments.
Examples
--------
@@ -1392,13 +1454,9 @@ def declare_units(**units_by_name) -> Callable:
def func(tas): ...
The decorator will check that `tas` has units of temperature (C, K, F).
-
- See Also
- --------
- declare_relative_units
"""
- def dec(func):
+ def dec(func): # numpydoc ignore=GL08
# The `_in_units` attr denotes a previously partially-declared function, update with that info.
if hasattr(func, "relative_units"):
# Make relative declarations absolute if possible
@@ -1425,7 +1483,7 @@ def dec(func):
raise ValueError(f"Argument {name} has no declared dimensions.")
@wraps(func)
- def wrapper(*args, **kwargs):
+ def wrapper(*args, **kwargs): # numpydoc ignore=GL08
# Match all passed in value to their proper arguments, so we can check units
bound_args = sig.bind(*args, **kwargs)
for name, dim in units_by_name.items():
@@ -1446,7 +1504,8 @@ def wrapper(*args, **kwargs):
def infer_context(
standard_name: str | None = None, dimension: str | None = None
) -> str:
- """Return units context based on either the variable's standard name or the pint dimension.
+ """
+ Return units context based on either the variable's standard name or the pint dimension.
Valid standard names for the hydro context are those including the terms "rainfall",
"lwe" (liquid water equivalent) and "precipitation". The latter is technically incorrect,
diff --git a/xclim/core/utils.py b/xclim/core/utils.py
index 16e24dab7..feb3513e2 100644
--- a/xclim/core/utils.py
+++ b/xclim/core/utils.py
@@ -17,6 +17,7 @@
from inspect import _empty # noqa
from io import StringIO
from pathlib import Path
+from types import ModuleType
import numpy as np
import xarray as xr
@@ -36,7 +37,8 @@
def deprecated(from_version: str | None, suggested: str | None = None) -> Callable:
- """Mark an index as deprecated and optionally suggest a replacement.
+ """
+ Mark an index as deprecated and optionally suggest a replacement.
Parameters
----------
@@ -48,11 +50,12 @@ def deprecated(from_version: str | None, suggested: str | None = None) -> Callab
Returns
-------
Callable
+ The decorated function.
"""
- def decorator(func):
+ def _decorator(func):
@functools.wraps(func)
- def wrapper(*args, **kwargs):
+ def _wrapper(*args, **kwargs):
msg = (
f"`{func.__name__}` is deprecated"
f"{f' from version {from_version}' if from_version else ''} "
@@ -68,13 +71,27 @@ def wrapper(*args, **kwargs):
return func(*args, **kwargs)
- return wrapper
+ return _wrapper
+
+ return _decorator
- return decorator
+def load_module(path: os.PathLike, name: str | None = None) -> ModuleType:
+ """
+ Load a python module from a python file, optionally changing its name.
-def load_module(path: os.PathLike, name: str | None = None):
- """Load a python module from a python file, optionally changing its name.
+ Parameters
+ ----------
+ path : os.PathLike
+ The path to the python file.
+ name : str, optional
+ The name to give to the module.
+ If None, the module name will be the stem of the path.
+
+ Returns
+ -------
+ ModuleType
+ The loaded module.
Examples
--------
@@ -106,7 +123,8 @@ def load_module(path: os.PathLike, name: str | None = None):
def ensure_chunk_size(da: xr.DataArray, **minchunks: int) -> xr.DataArray:
- r"""Ensure that the input DataArray has chunks of at least the given size.
+ r"""
+ Ensure that the input DataArray has chunks of at least the given size.
If only one chunk is too small, it is merged with an adjacent chunk.
If many chunks are too small, they are grouped together by merging adjacent chunks.
@@ -115,13 +133,14 @@ def ensure_chunk_size(da: xr.DataArray, **minchunks: int) -> xr.DataArray:
----------
da : xr.DataArray
The input DataArray, with or without the dask backend. Does nothing when passed a non-dask array.
- \*\*minchunks : dict[str, int]
+ **minchunks : dict[str, int]
A kwarg mapping from dimension name to minimum chunk size.
Pass -1 to force a single chunk along that dimension.
Returns
-------
xr.DataArray
+ The input DataArray, possibly rechunked.
"""
if not uses_dask(da):
return da
@@ -158,11 +177,12 @@ def ensure_chunk_size(da: xr.DataArray, **minchunks: int) -> xr.DataArray:
def uses_dask(*das: xr.DataArray | xr.Dataset) -> bool:
- """Evaluate whether dask is installed and array is loaded as a dask array.
+ r"""
+ Evaluate whether dask is installed and array is loaded as a dask array.
Parameters
----------
- das: xr.DataArray or xr.Dataset
+ *das : xr.DataArray or xr.Dataset
DataArrays or Datasets to check.
Returns
@@ -188,7 +208,27 @@ def calc_perc(
beta: float = 1.0,
copy: bool = True,
) -> np.ndarray:
- """Compute percentiles using nan_calc_percentiles and move the percentiles' axis to the end."""
+ """
+ Compute percentiles using nan_calc_percentiles and move the percentiles' axis to the end.
+
+ Parameters
+ ----------
+ arr : array-like
+ The input array.
+ percentiles : sequence of float, optional
+ The percentiles to compute. If None, only the median is computed.
+ alpha : float
+ A constant used to correct the index computed.
+ beta : float
+ A constant used to correct the index computed.
+ copy : bool
+ If True, the input array is copied before computation. Default is True.
+
+ Returns
+ -------
+ np.ndarray
+ The percentiles along the last axis.
+ """
if percentiles is None:
_percentiles = [50.0]
else:
@@ -216,7 +256,29 @@ def nan_calc_percentiles(
beta: float = 1.0,
copy: bool = True,
) -> np.ndarray:
- """Convert the percentiles to quantiles and compute them using _nan_quantile."""
+ """
+ Convert the percentiles to quantiles and compute them using _nan_quantile.
+
+ Parameters
+ ----------
+ arr : array-like
+ The input array.
+ percentiles : sequence of float, optional
+ The percentiles to compute. If None, only the median is computed.
+ axis : int
+ The axis along which to compute the percentiles.
+ alpha : float
+ A constant used to correct the index computed.
+ beta : float
+ A constant used to correct the index computed.
+ copy : bool
+ If True, the input array is copied before computation. Default is True.
+
+ Returns
+ -------
+ np.ndarray
+ The percentiles along the specified axis.
+ """
if percentiles is None:
_percentiles = [50.0]
else:
@@ -233,13 +295,14 @@ def nan_calc_percentiles(
def _compute_virtual_index(
n: np.ndarray, quantiles: np.ndarray, alpha: float, beta: float
):
- """Compute the floating point indexes of an array for the linear interpolation of quantiles.
+ """
+ Compute the floating point indexes of an array for the linear interpolation of quantiles.
Based on the approach used by :cite:t:`hyndman_sample_1996`.
Parameters
----------
- n : array_like
+ n : array-like
The sample sizes.
quantiles : array_like
The quantiles values.
@@ -260,14 +323,15 @@ def _compute_virtual_index(
def _get_gamma(virtual_indexes: np.ndarray, previous_indexes: np.ndarray):
- """Compute gamma (AKA 'm' or 'weight') for the linear interpolation of quantiles.
+ """
+ Compute gamma (AKA 'm' or 'weight') for the linear interpolation of quantiles.
Parameters
----------
- virtual_indexes: array_like
- The indexes where the percentile is supposed to be found in the sorted sample.
- previous_indexes: array_like
- The floor values of virtual_indexes.
+ virtual_indexes : array-like
+ The indexes where the percentile is supposed to be found in the sorted sample.
+ previous_indexes : array-like
+ The floor values of virtual_indexes.
Notes
-----
@@ -280,22 +344,26 @@ def _get_gamma(virtual_indexes: np.ndarray, previous_indexes: np.ndarray):
def _get_indexes(
arr: np.ndarray, virtual_indexes: np.ndarray, valid_values_count: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
- """Get the valid indexes of arr neighbouring virtual_indexes.
-
- Notes
- -----
- This is a companion function to linear interpolation of quantiles.
+ """
+ Get the valid indexes of arr neighbouring virtual_indexes.
Parameters
----------
arr : array-like
+ The input array.
virtual_indexes : array-like
+ The indexes where the percentile is supposed to be found in the sorted sample.
valid_values_count : array-like
+ The number of valid values in the sorted array.
Returns
-------
array-like, array-like
- A tuple of virtual_indexes neighbouring indexes (previous and next)
+ A tuple of virtual_indexes neighbouring indexes (previous and next).
+
+ Notes
+ -----
+ This is a companion function to linear interpolation of quantiles.
"""
previous_indexes = np.asanyarray(np.floor(virtual_indexes))
next_indexes = np.asanyarray(previous_indexes + 1)
@@ -325,20 +393,22 @@ def _linear_interpolation(
right: np.ndarray,
gamma: np.ndarray,
) -> np.ndarray:
- """Compute the linear interpolation weighted by gamma on each point of two same shape arrays.
+ """
+ Compute the linear interpolation weighted by gamma on each point of two same shape arrays.
Parameters
----------
- left : array_like
+ left : array-like
Left bound.
- right : array_like
+ right : array-like
Right bound.
- gamma : array_like
+ gamma : array-like
The interpolation weight.
Returns
-------
- array_like
+ array-like
+ The linearly interpolated array.
"""
diff_b_a = np.subtract(right, left)
lerp_interpolation = np.asanyarray(np.add(left, diff_b_a * gamma))
@@ -357,14 +427,15 @@ def _nan_quantile(
alpha: float = 1.0,
beta: float = 1.0,
) -> float | np.ndarray:
- """Get the quantiles of the array for the given axis.
+ """
+ Get the quantiles of the array for the given axis.
A linear interpolation is performed using alpha and beta.
Notes
-----
By default, alpha == beta == 1 which performs the 7th method of :cite:t:`hyndman_sample_1996`.
- with alpha == beta == 1/3 we get the 8th method.
+ With alpha == beta == 1/3 we get the 8th method.
"""
# --- Setup
data_axis_length = arr.shape[axis]
@@ -418,7 +489,8 @@ def _nan_quantile(
class InputKind(IntEnum):
- """Constants for input parameter kinds.
+ """
+ Constants for input parameter kinds.
For use by external parses to determine what kind of data the indicator expects.
On the creation of an indicator, the appropriate constant is stored in
@@ -444,8 +516,8 @@ class InputKind(IntEnum):
QUANTIFIED = 2
"""A quantity with units, either as a string (scalar), a pint.Quantity (scalar) or a DataArray (with units set).
- Annotation : ``xclim.core.utils.Quantified`` and an entry in the :py:func:`xclim.core.units.declare_units`
- decorator. "Quantified" translates to ``str | xr.DataArray | pint.util.Quantity``.
+ Annotation : ``xclim.core.utils.Quantified`` and an entry in the :py:func:`xclim.core.units.declare_units` decorator.
+ "Quantified" translates to ``str | xr.DataArray | pint.util.Quantity``.
"""
FREQ_STR = 3
"""A string representing an "offset alias", as defined by pandas.
@@ -510,11 +582,18 @@ class InputKind(IntEnum):
def infer_kind_from_parameter(param) -> InputKind:
- """Return the appropriate InputKind constant from an ``inspect.Parameter`` object.
+ """
+ Return the appropriate InputKind constant from an ``inspect.Parameter`` object.
Parameters
----------
param : Parameter
+ An inspect.Parameter instance.
+
+ Returns
+ -------
+ InputKind
+ The appropriate InputKind constant.
Notes
-----
@@ -571,10 +650,20 @@ def infer_kind_from_parameter(param) -> InputKind:
return InputKind.OTHER_PARAMETER
+# FIXME: Should we be using logging instead of print?
def adapt_clix_meta_yaml( # noqa: C901
raw: os.PathLike | StringIO | str, adapted: os.PathLike
-):
- """Read in a clix-meta yaml representation and refactor it to fit xclim's yaml specifications."""
+) -> None:
+ """
+ Read in a clix-meta yaml representation and refactor it to fit xclim YAML specifications.
+
+ Parameters
+ ----------
+ raw : os.PathLike or StringIO or str
+ The path to the clix-meta yaml file or the string representation of the yaml.
+ adapted : os.PathLike
+ The path to the adapted yaml file.
+ """
from ..indices import generic # pylint: disable=import-outside-toplevel
freq_defs = {"annual": "YS", "seasonal": "QS-DEC", "monthly": "MS", "weekly": "W"}
@@ -725,10 +814,21 @@ def adapt_clix_meta_yaml( # noqa: C901
def is_percentile_dataarray(source: xr.DataArray) -> bool:
- """Evaluate whether a DataArray is a Percentile.
+ """
+ Evaluate whether a DataArray is a Percentile.
- A percentile dataarray must have climatology_bounds attributes and either a
+ A percentile DataArray must have 'climatology_bounds' attributes and either a
quantile or percentiles coordinate, the window is not mandatory.
+
+ Parameters
+ ----------
+ source : xr.DataArray
+ The DataArray to evaluate.
+
+ Returns
+ -------
+ bool
+ True if the DataArray is a percentile.
"""
return (
isinstance(source, xr.DataArray)
@@ -763,27 +863,29 @@ def _chunk_like(*inputs: xr.DataArray | xr.Dataset, chunks: dict[str, int] | Non
def split_auxiliary_coordinates(
obj: xr.DataArray | xr.Dataset,
) -> tuple[xr.DataArray | xr.Dataset, xr.Dataset]:
- """Split auxiliary coords from the dataset.
+ """
+ Split auxiliary coords from the dataset.
An auxiliary coordinate is a coordinate variable that does not define a dimension and thus is not necessarily needed for dataset alignment.
- Any coordinate that has a name different than its dimension(s) is flagged as auxiliary. All scalar coordinates are flagged as auxiliary.
+ Any coordinate that has a name different from its dimension(s) is flagged as auxiliary.
+ All scalar coordinates are flagged as auxiliary.
Parameters
----------
- obj : DataArray or Dataset
- Xarray object
+ obj : xr.DataArray or xr.Dataset
+ An xarray object.
Returns
-------
- clean_obj : DataArray or Dataset
+ clean_obj : xr.DataArray or xr.Dataset
Same as `obj` but without any auxiliary coordinate.
- aux_coords : Dataset
+ aux_crd_ds : xr.Dataset
The auxiliary coordinates as a dataset. Might be empty.
- Note
- ----
- This is useful to circumvent xarray's alignment checks that will sometimes look the auxiliary coordinate's data, which can trigger
- unwanted dask computations.
+ Notes
+ -----
+ This is useful to circumvent xarray's alignment checks that will sometimes look the auxiliary coordinate's data,
+ which can trigger unwanted dask computations.
The auxiliary coordinates can be merged back with the dataset with
:py:meth:`xarray.Dataset.assign_coords` or :py:meth:`xarray.DataArray.assign_coords`.
diff --git a/xclim/data/__init__.py b/xclim/data/__init__.py
index 380b73fae..7c9d8c523 100644
--- a/xclim/data/__init__.py
+++ b/xclim/data/__init__.py
@@ -1,5 +1,4 @@
"""
-====================
Data files for xclim
====================
diff --git a/xclim/ensembles/__init__.py b/xclim/ensembles/__init__.py
index 2077741ec..8c503b842 100644
--- a/xclim/ensembles/__init__.py
+++ b/xclim/ensembles/__init__.py
@@ -1,5 +1,4 @@
"""
-==============
Ensemble tools
==============
diff --git a/xclim/ensembles/_base.py b/xclim/ensembles/_base.py
index 36c5f9560..52047e03d 100644
--- a/xclim/ensembles/_base.py
+++ b/xclim/ensembles/_base.py
@@ -37,7 +37,8 @@ def create_ensemble(
cal_kwargs: dict | None = None,
**xr_kwargs,
) -> xr.Dataset:
- r"""Create an xarray dataset of an ensemble of climate simulation from a list of netcdf files.
+ r"""
+ Create an xarray dataset of an ensemble of climate simulation from a list of netcdf files.
Input data is concatenated along a newly created data dimension ('realization'). Returns an xarray dataset object
containing input data from the list of netcdf files concatenated along a new dimension (name:'realization').
@@ -48,7 +49,7 @@ def create_ensemble(
Parameters
----------
- datasets : list or dict or string
+ datasets : list or dict or str
List of netcdf file paths or xarray Dataset/DataArray objects . If `multifile` is True, ncfiles should be a
list of lists where each sublist contains input .nc files of an xarray multifile Dataset.
If DataArray objects are passed, they should have a name in order to be transformed into Datasets.
@@ -72,14 +73,13 @@ def create_ensemble(
cal_kwargs : dict, optional
Additional arguments to pass to py:func:`xclim.core.calendar.convert_calendar`.
For conversions involving '360_day', the align_on='date' option is used by default.
- \*\*xr_kwargs
- Any keyword arguments to be given to `xr.open_dataset` when opening the files
- (or to `xr.open_mfdataset` if `multifile` is True)
+ **xr_kwargs : dict
+ Any keyword arguments to be given to `xr.open_dataset` when opening the files (or to `xr.open_mfdataset` if `multifile` is True).
Returns
-------
xr.Dataset
- Dataset containing concatenated data from all input files.
+ A Dataset containing concatenated data from all input files.
Notes
-----
@@ -139,7 +139,8 @@ def create_ensemble(
def ensemble_mean_std_max_min(
ens: xr.Dataset, min_members: int | None = 1, weights: xr.DataArray | None = None
) -> xr.Dataset:
- """Calculate ensemble statistics between a results from an ensemble of climate simulations.
+ """
+ Calculate ensemble statistics between a results from an ensemble of climate simulations.
Returns an xarray Dataset containing ensemble mean, standard-deviation, minimum and maximum for input climate
simulations.
@@ -226,7 +227,8 @@ def ensemble_percentiles(
"normal_unbiased",
] = "linear",
) -> xr.DataArray | xr.Dataset:
- """Calculate ensemble statistics between a results from an ensemble of climate simulations.
+ """
+ Calculate ensemble statistics between a results from an ensemble of climate simulations.
Returns a Dataset containing ensemble percentiles for input climate simulations.
@@ -259,8 +261,7 @@ def ensemble_percentiles(
Returns
-------
xr.Dataset or xr.DataArray
- If split is True, same type as ens; dataset otherwise,
- containing data variable(s) of requested ensemble statistics
+ If split is True, same type as ens; dataset otherwise, containing data variable(s) of requested ensemble statistics.
Examples
--------
@@ -392,7 +393,8 @@ def _ens_align_datasets(
cal_kwargs: dict | None = None,
**xr_kwargs,
) -> list[xr.Dataset]:
- r"""Create a list of aligned xarray Datasets for ensemble Dataset creation.
+ r"""
+ Create a list of aligned xarray Datasets for ensemble Dataset creation.
Parameters
----------
@@ -412,7 +414,7 @@ def _ens_align_datasets(
the align_on='date' option is used.
See :py:func:`xclim.core.calendar.convert_calendar`.
'default' is the standard calendar using np.datetime64 objects.
- \*\*xr_kwargs
+ **xr_kwargs : dict
Any keyword arguments to be given to xarray when opening the files.
Returns
diff --git a/xclim/ensembles/_filters.py b/xclim/ensembles/_filters.py
index 338f307ae..bd5d352d0 100644
--- a/xclim/ensembles/_filters.py
+++ b/xclim/ensembles/_filters.py
@@ -1,3 +1,8 @@
+"""
+Ensemble filters for data processing
+====================================
+"""
+
from __future__ import annotations
import numpy as np
@@ -5,13 +10,14 @@
def _concat_hist(da: xr.DataArray, **hist) -> xr.DataArray:
- r"""Concatenate historical scenario with future scenarios along the time dimension.
+ r"""
+ Concatenate historical scenario with future scenarios along the time dimension.
Parameters
----------
da : xr.DataArray
Input data where the historical scenario is stored alongside other, future, scenarios.
- \*\*hist : dict
+ **hist : dict
Mapping of the scenario dimension name to the historical scenario coordinate, e.g. `scenario="historical"`.
Returns
@@ -62,7 +68,8 @@ def _concat_hist(da: xr.DataArray, **hist) -> xr.DataArray:
def _model_in_all_scens(
da: xr.DataArray, dimensions: dict | None = None
) -> xr.DataArray:
- """Return data with only simulations that have at least one member in each scenario.
+ """
+ Return data with only simulations that have at least one member in each scenario.
Parameters
----------
@@ -103,7 +110,8 @@ def _model_in_all_scens(
def _single_member(da: xr.DataArray, dimensions: dict | None = None) -> xr.DataArray:
- """Return data for a single member per model.
+ """
+ Return data for a single member per model.
Parameters
----------
@@ -150,5 +158,17 @@ def _single_member(da: xr.DataArray, dimensions: dict | None = None) -> xr.DataA
def reverse_dict(d: dict) -> dict:
- """Reverse dictionary."""
+ """
+ Reverse dictionary.
+
+ Parameters
+ ----------
+ d : dict
+ Dictionary to reverse.
+
+ Returns
+ -------
+ dict
+ Reversed dictionary.
+ """
return {v: k for (k, v) in d.items()}
diff --git a/xclim/ensembles/_partitioning.py b/xclim/ensembles/_partitioning.py
index 477629568..4e9ed51b7 100644
--- a/xclim/ensembles/_partitioning.py
+++ b/xclim/ensembles/_partitioning.py
@@ -62,7 +62,8 @@ def hawkins_sutton(
baseline: tuple[str, str] = ("1971", "2000"),
kind: str = "+",
) -> tuple[xr.DataArray, xr.DataArray]:
- """Return the mean and partitioned variance of an ensemble based on method from Hawkins & Sutton (2009).
+ """
+ Return the mean and partitioned variance of an ensemble based on method from Hawkins & Sutton (2009).
Parameters
----------
@@ -81,7 +82,7 @@ def hawkins_sutton(
Returns
-------
- xr.DataArray, xr.DataArray
+ (xr.DataArray, xr.DataArray)
The mean relative to the baseline, and the components of variance of the ensemble. These components are
coordinates along the `uncertainty` dimension: `variability`, `model`, `scenario`, and `total`.
@@ -172,7 +173,8 @@ def hawkins_sutton(
def hawkins_sutton_09_weighting(da, obs, baseline=("1971", "2000")):
- """Return weights according to the ability of models to simulate observed climate change.
+ """
+ Return weights according to the ability of models to simulate observed climate change.
Weights are computed by comparing the 2000 value to the baseline mean: w_m = 1 / (x_{obs} + | x_{m,
2000} - x_obs | )
@@ -189,7 +191,7 @@ def hawkins_sutton_09_weighting(da, obs, baseline=("1971", "2000")):
Returns
-------
xr.DataArray
- Weights over the model dimension.
+ Weights over the model dimension.
"""
mm = da.sel(time=slice(*baseline)).mean("time")
xm = da.sel(time=baseline[1]) - mm
@@ -202,7 +204,8 @@ def lafferty_sriver(
sm: xr.DataArray | None = None,
bb13: bool = False,
) -> tuple[xr.DataArray, xr.DataArray]:
- """Return the mean and partitioned variance of an ensemble based on method from Lafferty and Sriver (2023).
+ """
+ Return the mean and partitioned variance of an ensemble based on method from Lafferty and Sriver (2023).
Parameters
----------
@@ -300,7 +303,8 @@ def lafferty_sriver(
def fractional_uncertainty(u: xr.DataArray) -> xr.DataArray:
- """Return the fractional uncertainty.
+ """
+ Return the fractional uncertainty.
Parameters
----------
diff --git a/xclim/ensembles/_reduce.py b/xclim/ensembles/_reduce.py
index 797b9f9fd..fabe81b04 100644
--- a/xclim/ensembles/_reduce.py
+++ b/xclim/ensembles/_reduce.py
@@ -28,7 +28,8 @@
def make_criteria(ds: xarray.Dataset | xarray.DataArray):
- """Reshapes the input into a criteria 2D DataArray.
+ """
+ Reshape the input into a criteria 2D DataArray.
The reshaping preserves the "realization" dimension but stacks all other dimensions and variables into a new
"criteria" dimension, as expected by functions :py:func:`xclim.ensembles._reduce.kkz_reduce_ensemble` and
@@ -36,14 +37,14 @@ def make_criteria(ds: xarray.Dataset | xarray.DataArray):
Parameters
----------
- ds : Dataset or DataArray
+ ds : xr.Dataset or xr.DataArray
Must at least have a "realization" dimension.
All values are considered independent "criterion" for the ensemble reduction.
If a Dataset, variables may have different sizes, but all must include the "realization" dimension.
Returns
-------
- crit : DataArray
+ xr.DataArray
Same data, reshaped. Old coordinates (and variables) are available as a multiindex.
Notes
@@ -113,7 +114,7 @@ def _make_crit(da):
# Previous ops gave the first variable's attributes, replace by the original dataset ones.
crit.attrs = ds.attrs
else:
- # Easy peasy, skip all the convoluted stuff
+ # Easy-peasy, skip all the convoluted stuff
crit = _make_crit(ds)
# drop criteria that are all NaN
@@ -129,7 +130,8 @@ def kkz_reduce_ensemble(
standardize: bool = True,
**cdist_kwargs,
) -> list:
- r"""Return a sample of ensemble members using KKZ selection.
+ r"""
+ Return a sample of ensemble members using KKZ selection.
The algorithm selects `num_select` ensemble members spanning the overall range of the ensemble.
The selection is ordered, smaller groups are always subsets of larger ones for given criteria.
@@ -140,8 +142,8 @@ def kkz_reduce_ensemble(
Parameters
----------
data : xr.DataArray
- Selection criteria data : 2-D xr.DataArray with dimensions 'realization' (N) and
- 'criteria' (P). These are the values used for clustering. Realizations represent the individual original
+ Selection criteria data : 2-D xr.DataArray with dimensions 'realization' (N) and 'criteria' (P).
+ These are the values used for clustering. Realizations represent the individual original
ensemble members and criteria the variables/indicators used in the grouping algorithm.
num_select : int
The number of members to select.
@@ -150,7 +152,7 @@ def kkz_reduce_ensemble(
standardize : bool
Whether to standardize the input before running the selection or not.
Standardization consists in translation as to have a zero mean and scaling as to have a unit standard deviation.
- \*\*cdist_kwargs
+ **cdist_kwargs : dict
All extra arguments are passed as-is to `scipy.spatial.distance.cdist`, see its docs for more information.
Returns
@@ -203,7 +205,8 @@ def kmeans_reduce_ensemble(
sample_weights: np.ndarray | None = None,
random_state: int | np.random.RandomState | None = None,
) -> tuple[list, np.ndarray, dict]:
- """Return a sample of ensemble members using k-means clustering.
+ """
+ Return a sample of ensemble members using k-means clustering.
The algorithm attempts to reduce the total number of ensemble members while maintaining adequate coverage of
the ensemble uncertainty in an N-dimensional data space. K-Means clustering is carried out on the input
@@ -218,27 +221,36 @@ def kmeans_reduce_ensemble(
ensemble members and criteria the variables/indicators used in the grouping algorithm.
method : dict, optional
Dictionary defining selection method and associated value when required. See Notes.
+ make_graph : bool
+ Output a dictionary of input for displays a plot of R² vs. the number of clusters.
+ Defaults to True if matplotlib is installed in the runtime environment.
max_clusters : int, optional
Maximum number of members to include in the output ensemble selection.
When using 'rsq_optimize' or 'rsq_cutoff' methods, limit the final selection to a maximum number even if method
results indicate a higher value. Defaults to N.
variable_weights : np.ndarray, optional
- An array of size P. This weighting can be used to influence of weight of the climate indices
- (criteria dimension) on the clustering itself.
+ An array of size P.
+ This weighting can be used to influence of weight of the climate indices (criteria dimension) on the clustering itself.
model_weights : np.ndarray, optional
An array of size N. This weighting can be used to influence which realization is selected
from within each cluster. This parameter has no influence on the clustering itself.
sample_weights : np.ndarray, optional
- An array of size N. sklearn.cluster.KMeans() sample_weights parameter. This weighting can be
- used to influence of weight of simulations on the clustering itself.
- See: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
- random_state: int or np.random.RandomState, optional
- sklearn.cluster.KMeans() random_state parameter. Determines random number generation for centroid
- initialization. Use to make the randomness deterministic.
- See: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
- make_graph: bool
- output a dictionary of input for displays a plot of R² vs. the number of clusters.
- Defaults to True if matplotlib is installed in runtime environment.
+ An array of size N. sklearn.cluster.KMeans() sample_weights parameter.
+ This weighting can be used to influence of weight of simulations on the clustering itself.
+ See: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html.
+ random_state : int or np.random.RandomState, optional
+ A sklearn.cluster.KMeans() random_state parameter. Determines random number generation for centroid initialization.
+ Use to make the randomness deterministic.
+ See: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html.
+
+ Returns
+ -------
+ list
+ Selected model indexes (positions).
+ np.ndarray
+ KMeans clustering results.
+ dict
+ Dictionary of input data for creating R² profile plot. 'None' when make_graph=False.
Notes
-----
@@ -266,15 +278,6 @@ def kmeans_reduce_ensemble(
method={'n_clusters': val}
- Returns
- -------
- list
- Selected model indexes (positions)
- np.ndarray
- KMeans clustering results
- dict
- Dictionary of input data for creating R² profile plot. 'None' when make_graph=False
-
References
----------
:cite:cts:`casajus_objective_2016`
@@ -420,8 +423,14 @@ def kmeans_reduce_ensemble(
return out, clusters, fig_data
-def _calc_rsq(z, method, make_graph, n_sim, random_state, sample_weights):
- """Sub-function to kmeans_reduce_ensemble. Calculates r-square profile (r-square versus number of clusters."""
+def _calc_rsq(
+ z, method: dict, make_graph: bool, n_sim: np.ndarray, random_state, sample_weights
+):
+ """
+ Sub-function to kmeans_reduce_ensemble.
+
+ Calculates r-square profile (r-square versus number of clusters).
+ """
rsq = None
if list(method.keys())[0] != "n_clusters" or make_graph is True:
# generate r2 profile data
@@ -445,7 +454,7 @@ def _calc_rsq(z, method, make_graph, n_sim, random_state, sample_weights):
return rsq
-def _get_nclust(method: dict, n_sim, rsq, max_clusters):
+def _get_nclust(method: dict, n_sim: int, rsq: float, max_clusters: int):
"""Sub-function to kmeans_reduce_ensemble. Determine number of clusters to create depending on various methods."""
# if we actually need to find the optimal number of clusters, this is where it is done
if list(method.keys())[0] == "rsq_cutoff":
@@ -476,12 +485,18 @@ def _get_nclust(method: dict, n_sim, rsq, max_clusters):
return n_clusters
-def plot_rsqprofile(fig_data):
- """Create an R² profile plot using kmeans_reduce_ensemble output.
+def plot_rsqprofile(fig_data: dict) -> None:
+ """
+ Create an R² profile plot using `kmeans_reduce_ensemble` output.
The R² plot allows evaluation of the proportion of total uncertainty in the original ensemble that is provided
by the reduced selected.
+ Parameters
+ ----------
+ fig_data : dict
+ Dictionary of input data for creating R² profile plot.
+
Examples
--------
>>> from xclim.ensembles import kmeans_reduce_ensemble, plot_rsqprofile
diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py
index cfa9f1061..fda47b4a6 100644
--- a/xclim/ensembles/_robustness.py
+++ b/xclim/ensembles/_robustness.py
@@ -9,6 +9,7 @@
from __future__ import annotations
+from collections.abc import Callable
from inspect import Parameter, signature
import numpy as np
@@ -39,9 +40,23 @@
"""
-def significance_test(func):
- """Register a significance test for use in :py:func:`robustness_fractions`.
+def significance_test(func: Callable) -> Callable:
+ """
+ Register a significance test for use in :py:func:`robustness_fractions`.
+
+ Parameters
+ ----------
+ func : function
+ The significance test function.
+ See :py:func:`robustness_fractions` for requirements.
+
+ Returns
+ -------
+ Callable
+ The registered function.
+ Notes
+ -----
See :py:data:`SIGNIFICANCE_TESTS`.
"""
SIGNIFICANCE_TESTS[func.__name__[1:].replace("_", "-")] = func
@@ -58,7 +73,8 @@ def robustness_fractions( # noqa: C901
weights: xr.DataArray | None = None,
**kwargs,
) -> xr.Dataset:
- r"""Robustness statistics qualifying how members of an ensemble agree on the existence of change and on its sign.
+ r"""
+ Calculate robustness statistics qualifying how members of an ensemble agree on the existence of change and on its sign.
Parameters
----------
@@ -75,33 +91,38 @@ def robustness_fractions( # noqa: C901
Name of the statistical test used to determine if there was significant change. See notes.
weights : xr.DataArray
Weights to apply along the 'realization' dimension. This array cannot contain missing values.
- \*\*kwargs
+ **kwargs : dict
Other arguments specific to the statistical test. See notes.
Returns
-------
xr.Dataset
- Same coordinates as `fut` and `ref`, but no `time` and no `realization`.
-
- Variables:
-
- changed :
- The weighted fraction of valid members showing significant change.
- Passing `test=None` yields change_frac = 1 everywhere. Same type as `fut`.
- positive :
- The weighted fraction of valid members showing strictly positive change, no matter if it is significant or not.
- changed_positive :
- The weighted fraction of valid members showing significant and positive change.
- negative :
- The weighted fraction of valid members showing strictly negative change, no matter if it is significant or not.
- changed_negative :
- The weighted fraction of valid members showing significant and negative change.
- agree :
- The weighted fraction of valid members agreeing on the sign of change. It is the maximum between positive, negative and the rest.
- valid :
- The weighted fraction of valid members. A member is valid is there are no NaNs along the time axes of `fut` and `ref`.
- pvals :
- The p-values estimated by the significance tests. Only returned if the test uses `pvals`. Has the `realization` dimension.
+ Same coordinates as `fut` and `ref`, but no `time` and no `realization`. Variables returned are:
+
+ - changed
+ - The weighted fraction of valid members showing significant change.
+ Passing `test=None` yields change_frac = 1 everywhere. Same type as `fut`.
+
+ - positive
+ - The weighted fraction of valid members showing strictly positive change, no matter if it is significant or not.
+
+ - changed_positive
+ - The weighted fraction of valid members showing significant and positive change.
+
+ - negative
+ - The weighted fraction of valid members showing strictly negative change, no matter if it is significant or not.
+
+ - changed_negative
+ - The weighted fraction of valid members showing significant and negative change.
+
+ - agree
+ - The weighted fraction of valid members agreeing on the sign of change. It is the maximum between positive, negative and the rest.
+
+ - valid
+ - The weighted fraction of valid members. A member is valid is there are no NaNs along the time axes of `fut` and `ref`.
+
+ - pvals
+ - The p-values estimated by the significance tests. Only returned if the test uses `pvals`. Has the `realization` dimension.
Notes
-----
@@ -125,20 +146,20 @@ def robustness_fractions( # noqa: C901
Available statistical tests are :
{tests_doc}
- threshold :
- Change is considered significant when it exceeds an absolute or relative threshold.
- Accepts one argument, either "abs_thresh" or "rel_thresh".
- None :
- Significant change is not tested. Members showing any positive change are
- included in the `pos_frac` output.
+
+ - threshold
+ - Change is considered significant when it exceeds an absolute or relative threshold.
+ Accepts one argument, either "abs_thresh" or "rel_thresh".
+
+ - None
+ - Significant change is not tested. Members showing any positive change are included in the `pos_frac` output.
References
----------
- :cite:cts:`tebaldi_mapping_2011`
- :cite:cts:`ipccatlas_ar6wg1`
+ :cite:cts:`tebaldi_mapping_2011,ipccatlas_ar6wg1`.
- Example
- -------
+ Examples
+ --------
This example computes the mean temperature in an ensemble and compares two time
periods, qualifying significant change through a single sample T-test.
@@ -300,7 +321,8 @@ def robustness_categories(
ops: list[tuple[str, str]] | None = None,
thresholds: list[tuple[float, float]] | None = None,
) -> xr.DataArray:
- """Create a categorical robustness map for mapping hatching patterns.
+ """
+ Create a categorical robustness map for mapping hatching patterns.
Each robustness category is defined by a double threshold, one on the fraction of members showing significant
change (`change_frac`) and one on the fraction of member agreeing on the sign of change (`agree_frac`).
@@ -384,7 +406,8 @@ def robustness_categories(
def robustness_coefficient(
fut: xr.DataArray | xr.Dataset, ref: xr.DataArray | xr.Dataset
) -> xr.DataArray | xr.Dataset:
- """Robustness coefficient quantifying the robustness of a climate change signal in an ensemble.
+ """
+ Calculate the robustness coefficient quantifying the robustness of a climate change signal in an ensemble.
Taken from :cite:ts:`knutti_robustness_2013`.
@@ -399,10 +422,10 @@ def robustness_coefficient(
Parameters
----------
- fut : Union[xr.DataArray, xr.Dataset]
- Future ensemble values along 'realization' and 'time' (nr, nt). Can be a dataset,
- in which case the coefficient is computed on each variable.
- ref : Union[xr.DataArray, xr.Dataset]
+ fut : xr.DataArray or xr.Dataset
+ Future ensemble values along 'realization' and 'time' (nr, nt).
+ Can be a dataset, in which case the coefficient is computed on each variable.
+ ref : xr.DataArray or xr.Dataset
Reference period values along 'time' (nt). Same type as `fut`.
Returns
@@ -468,9 +491,11 @@ def diff_cdf_sq_area_int(x1, x2):
@significance_test
def _ttest(fut, ref, *, p_change=0.05):
- """Single sample T-test. Same test as used by :cite:t:`tebaldi_mapping_2011`.
+ """
+ Single sample T-test. Same test as used by :cite:t:`tebaldi_mapping_2011`.
The future values are compared against the reference mean (over 'time').
+
Accepts argument p_change (float, default : 0.05) the p-value threshold for rejecting the hypothesis of no significant change.
"""
@@ -499,7 +524,8 @@ def _ttest_func(f, r):
@significance_test
def _welch_ttest(fut, ref, *, p_change=0.05):
- """Two-sided T-test, without assuming equal population variance.
+ """
+ Two-sided T-test, without assuming equal population variance.
Same significance criterion and argument as 'ttest'.
"""
@@ -555,7 +581,8 @@ def mwu_wrapper(f, r): # This specific test can't manage an all-NaN slice
@significance_test
def _brownforsythe_test(fut, ref, *, p_change=0.05):
- """Brown-Forsythe test assuming skewed, non-normal distributions.
+ """
+ Brown-Forsythe test assuming skewed, non-normal distributions.
Same significance criterion and argument as 'ttest'.
"""
@@ -577,7 +604,8 @@ def _brownforsythe_test(fut, ref, *, p_change=0.05):
@significance_test
def _ipcc_ar6_c(fut, ref, *, ref_pi=None):
- r"""The advanced approach used in the IPCC Atlas chapter (:cite:t:`ipccatlas_ar6wg1`).
+ r"""
+ The advanced approach used in the IPCC Atlas chapter (:cite:t:`ipccatlas_ar6wg1`).
Change is considered significant if the delta exceeds a threshold related to the internal variability.
If pre-industrial data is given in argument `ref_pi`, the threshold is defined as
@@ -608,10 +636,12 @@ def _ipcc_ar6_c(fut, ref, *, ref_pi=None):
def _gen_test_entry(namefunc):
name, func = namefunc
doc = func.__doc__.replace("\n ", "\n\t\t").rstrip()
- return f"\t{name}:\n\t\t{doc}"
+ return f" - {name}\n\t - {doc}"
robustness_fractions.__doc__ = robustness_fractions.__doc__.format(
tests_list="{" + ", ".join(list(SIGNIFICANCE_TESTS.keys()) + ["threshold"]) + "}",
- tests_doc="\n".join(map(_gen_test_entry, SIGNIFICANCE_TESTS.items())),
+ tests_doc="\n".join(map(_gen_test_entry, SIGNIFICANCE_TESTS.items()))[
+ 4: # strip first 4 chars
+ ],
)
diff --git a/xclim/indicators/atmos/__init__.py b/xclim/indicators/atmos/__init__.py
index 07498d6db..918120ff1 100644
--- a/xclim/indicators/atmos/__init__.py
+++ b/xclim/indicators/atmos/__init__.py
@@ -8,7 +8,6 @@
The concept followed here is to define Indicator subclasses for each input variable, then create instances
for each indicator.
-
"""
from __future__ import annotations
diff --git a/xclim/indicators/atmos/_conversion.py b/xclim/indicators/atmos/_conversion.py
index f44fb6c3f..4d7ba5f9f 100644
--- a/xclim/indicators/atmos/_conversion.py
+++ b/xclim/indicators/atmos/_conversion.py
@@ -37,7 +37,15 @@
class Converter(Indicator):
"""Class for indicators doing variable conversion (dimension-independent 1-to-1 computation)."""
- def cfcheck(self, **das):
+ def cfcheck(self, **das) -> None:
+ r"""
+ Verify the CF-compliance of the input data.
+
+ Parameters
+ ----------
+ **das : Mapping[str, xarray.DataArray]
+ The input data arrays.
+ """
for varname, vardata in das.items():
try:
# Only check standard_name, and not cell_methods which depends on the variable's frequency.
diff --git a/xclim/indicators/atmos/_precip.py b/xclim/indicators/atmos/_precip.py
index 79ba58d61..a05d28d22 100644
--- a/xclim/indicators/atmos/_precip.py
+++ b/xclim/indicators/atmos/_precip.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from xarray import DataArray
+
from xclim import indices
from xclim.core import cfchecks
from xclim.core.indicator import (
@@ -97,7 +99,18 @@ class PrTasxWithIndexing(ResamplingIndicatorWithIndexing):
context = "hydro"
keywords = "precipitation"
- def cfcheck(self, pr, tas):
+ @staticmethod
+ def cfcheck(pr: DataArray, tas: DataArray):
+ r"""
+ Verify the CF-compliance of the input data.
+
+ Parameters
+ ----------
+ pr : xarray.DataArray
+ Precipitation data.
+ tas : xarray.DataArray
+ Temperature data.
+ """
cfchecks.cfcheck_from_name("pr", pr)
cfchecks.check_valid(tas, "standard_name", "air_temperature")
diff --git a/xclim/indicators/atmos/_temperature.py b/xclim/indicators/atmos/_temperature.py
index 2f0cc1e87..bc652a1bb 100644
--- a/xclim/indicators/atmos/_temperature.py
+++ b/xclim/indicators/atmos/_temperature.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from xarray import DataArray
+
from xclim import indices
from xclim.core import cfchecks
from xclim.core.indicator import (
@@ -1314,7 +1316,17 @@ class FireSeasonBase(Indicator):
keywords = "fire"
- def cfcheck(self, tas, snd=None):
+ def cfcheck(self, tas: DataArray, snd: DataArray = None):
+ r"""
+ Verify the CF-compliance of the input data.
+
+ Parameters
+ ----------
+ tas : xarray.DataArray
+ Near-surface air temperature.
+ snd : xarray.DataArray, optional
+ Snow depth.
+ """
cfchecks.check_valid(tas, "standard_name", "air_temperature")
cfchecks.cfcheck_from_name("snd", snd)
diff --git a/xclim/indicators/atmos/_wind.py b/xclim/indicators/atmos/_wind.py
index 3e63036ad..831811b52 100644
--- a/xclim/indicators/atmos/_wind.py
+++ b/xclim/indicators/atmos/_wind.py
@@ -1,3 +1,5 @@
+"""Wind indicator definitions."""
+
from __future__ import annotations
from xclim import indices
diff --git a/xclim/indicators/generic/_stats.py b/xclim/indicators/generic/_stats.py
index e213ba99a..2ddd7921b 100644
--- a/xclim/indicators/generic/_stats.py
+++ b/xclim/indicators/generic/_stats.py
@@ -1,3 +1,5 @@
+"""Statistical indicator definitions."""
+
from __future__ import annotations
from xclim.core.indicator import ReducingIndicator, ResamplingIndicator
diff --git a/xclim/indicators/land/_snow.py b/xclim/indicators/land/_snow.py
index ff9de4da1..ece6aca16 100644
--- a/xclim/indicators/land/_snow.py
+++ b/xclim/indicators/land/_snow.py
@@ -1,3 +1,5 @@
+"""Snow indicator definitions."""
+
from __future__ import annotations
from xclim import indices as xci
diff --git a/xclim/indicators/land/_streamflow.py b/xclim/indicators/land/_streamflow.py
index fc73699af..1dc5b0786 100644
--- a/xclim/indicators/land/_streamflow.py
+++ b/xclim/indicators/land/_streamflow.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from xarray import DataArray
+
from xclim.core.cfchecks import check_valid
from xclim.core.indicator import (
ReducingIndicator,
@@ -35,9 +37,16 @@ class Streamflow(ResamplingIndicator):
src_freq = "D"
keywords = "streamflow hydrology"
- # TODO: TJS: The signature of this method seems wrong. Should it be `def cfcheck(cls, q):` or something else? Is it a static method?
@staticmethod
- def cfcheck(q):
+ def cfcheck(q: DataArray):
+ r"""
+ Verify the CF-compliance of the input data.
+
+ Parameters
+ ----------
+ q : xarray.DataArray
+ The input data array.
+ """
check_valid(q, "standard_name", "water_volume_transport_in_river_channel")
diff --git a/xclim/indicators/seaIce/_seaice.py b/xclim/indicators/seaIce/_seaice.py
index d791e788d..e12dbf194 100644
--- a/xclim/indicators/seaIce/_seaice.py
+++ b/xclim/indicators/seaIce/_seaice.py
@@ -1,7 +1,4 @@
-"""
-Sea ice indicators
-------------------
-"""
+"""Sea ice indicator definitions."""
from __future__ import annotations
diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py
index 4fe48a4e9..c66408f0d 100644
--- a/xclim/indices/_agro.py
+++ b/xclim/indices/_agro.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Agroclimatic indice definitions."""
+
from __future__ import annotations
from typing import cast
@@ -65,7 +66,8 @@ def corn_heat_units(
thresh_tasmin: Quantified = "4.44 degC",
thresh_tasmax: Quantified = "10 degC",
) -> xarray.DataArray:
- r"""Corn heat units.
+ r"""
+ Corn heat units.
Temperature-based index used to estimate the development of corn crops.
Formula adapted from :cite:t:`bootsma_risk_1999`.
@@ -151,7 +153,8 @@ def huglin_index(
end_date: DayOfYearStr = "10-01",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Huglin Heliothermal Index.
+ r"""
+ Huglin Heliothermal Index.
Growing-degree days with a base of 10°C and adjusted for latitudes between 40°N and 50°N for April-September
(Northern Hemisphere; October-March in Southern Hemisphere). Originally proposed in :cite:t:`huglin_nouveau_1978`.
@@ -315,7 +318,8 @@ def biologically_effective_degree_days(
end_date: DayOfYearStr = "11-01",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Biologically effective growing degree days.
+ r"""
+ Biologically effective growing degree days.
Growing-degree days with a base of 10°C and an upper limit of 19°C and adjusted for latitudes between 40°N and 50°N
for April to October (Northern Hemisphere; October to April in Southern Hemisphere). A temperature range adjustment
@@ -355,7 +359,7 @@ def biologically_effective_degree_days(
Returns
-------
xarray.DataArray, [K days]
- Biologically effective growing degree days (BEDD)
+ Biologically effective growing degree days (BEDD).
Warnings
--------
@@ -468,16 +472,12 @@ def cool_night_index(
lat: xarray.DataArray | str | None = None,
freq: str = "YS",
) -> xarray.DataArray:
- """Cool Night Index.
+ """
+ Cool Night Index.
Mean minimum temperature for September (northern hemisphere) or March (Southern hemisphere).
Used in calculating the Géoviticulture Multicriteria Classification System (:cite:t:`tonietto_multicriteria_2004`).
- Warnings
- --------
- This indice is calculated using minimum temperature resampled to monthly average, and therefore will accept monthly
- averaged data as inputs.
-
Parameters
----------
tasmin : xarray.DataArray
@@ -493,6 +493,11 @@ def cool_night_index(
xarray.DataArray, [degC]
Mean of daily minimum temperature for month of interest.
+ Warnings
+ --------
+ This indice is calculated using minimum temperature resampled to monthly average, and therefore will accept monthly
+ averaged data as inputs.
+
Notes
-----
Given that this indice only examines September and March months, it is possible to send in DataArrays containing
@@ -505,15 +510,15 @@ def cool_night_index(
with xclim.set_options(check_missing="skip"):
cni = cool_night_index(tasmin)
+ References
+ ----------
+ :cite:cts:`tonietto_multicriteria_2004`
+
Examples
--------
>>> from xclim.indices import cool_night_index
>>> tasmin = xr.open_dataset(path_to_tasmin_file).tasmin
>>> cni = cool_night_index(tasmin)
-
- References
- ----------
- :cite:cts:`tonietto_multicriteria_2004`
"""
tasmin = convert_units_to(tasmin, "degC")
@@ -542,26 +547,21 @@ def cool_night_index(
@declare_units(pr="[precipitation]", evspsblpot="[precipitation]", wo="[length]")
-def dryness_index(
+def dryness_index( # numpydoc ignore=SS05
pr: xarray.DataArray,
evspsblpot: xarray.DataArray,
lat: xarray.DataArray | str | None = None,
wo: Quantified = "200 mm",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Dryness Index.
+ r"""
+ Dryness Index.
Approximation of the water balance for the categorizing the winegrowing season. Uses both precipitation and an
adjustment of potential evapotranspiration between April and September (Northern Hemisphere) or October and March
(Southern hemisphere). Used in calculating the Géoviticulture Multicriteria Classification System
(:cite:t:`tonietto_multicriteria_2004`).
- Warnings
- --------
- Dryness Index expects CF-Convention conformant potential evapotranspiration (positive up). This indice is calculated
- using evapotranspiration and precipitation resampled and converted to monthly total accumulations, and therefore
- will accept monthly fluxes as inputs.
-
Parameters
----------
pr : xarray.DataArray
@@ -581,6 +581,12 @@ def dryness_index(
xarray.DataArray, [mm]
Dryness Index.
+ Warnings
+ --------
+ Dryness Index expects CF-Convention conformant potential evapotranspiration (positive up). This indice is calculated
+ using evapotranspiration and precipitation resampled and converted to monthly total accumulations, and therefore
+ will accept monthly fluxes as inputs.
+
Notes
-----
Given that this indice only examines monthly total accumulations for six-month periods depending on the hemisphere,
@@ -637,15 +643,14 @@ def dryness_index(
JPm = \max\left( P / 5, N \right)
- Examples
- --------
- >>> from xclim.indices import dryness_index
- >>> dryi = dryness_index(pr_dataset, evspsblpot_dataset, wo="200 mm")
-
References
----------
:cite:cts:`tonietto_multicriteria_2004,riou_determinisme_1994`
+ Examples
+ --------
+ >>> from xclim.indices import dryness_index
+ >>> dryi = dryness_index(pr_dataset, evspsblpot_dataset, wo="200 mm")
"""
if parse_offset(freq) != (1, "Y", True, "JAN"):
raise ValueError(f"Freq not allowed: {freq}. Must be `YS` or `YS-JAN`")
@@ -746,7 +751,8 @@ def latitude_temperature_index(
lat_factor: float = 75,
freq: str = "YS",
) -> xarray.DataArray:
- """Latitude-Temperature Index.
+ """
+ Latitude-Temperature Index.
Mean temperature of the warmest month with a latitude-based scaling factor :cite:p:`jackson_prediction_1988`.
Used for categorizing wine-growing regions.
@@ -830,7 +836,8 @@ def water_budget(
sfcWind: xarray.DataArray | None = None,
method: str = "BR65",
) -> xarray.DataArray:
- r"""Precipitation minus potential evapotranspiration.
+ r"""
+ Precipitation minus potential evapotranspiration.
Precipitation minus potential evapotranspiration as a measure of an approximated surface water budget,
where the potential evapotranspiration can be calculated with a given method.
@@ -839,8 +846,8 @@ def water_budget(
----------
pr : xarray.DataArray
Daily precipitation.
- evspsblpot: xarray.DataArray, optional
- Potential evapotranspiration
+ evspsblpot : xarray.DataArray, optional
+ Potential evapotranspiration.
tasmin : xarray.DataArray, optional
Minimum daily temperature.
tasmax : xarray.DataArray, optional
@@ -853,26 +860,26 @@ def water_budget(
hurs : xarray.DataArray, optional
Relative humidity.
rsds : xarray.DataArray, optional
- Surface Downwelling Shortwave Radiation
+ Surface Downwelling Shortwave Radiation.
rsus : xarray.DataArray, optional
- Surface Upwelling Shortwave Radiation
+ Surface Upwelling Shortwave Radiation.
rlds : xarray.DataArray, optional
- Surface Downwelling Longwave Radiation
+ Surface Downwelling Longwave Radiation.
rlus : xarray.DataArray, optional
- Surface Upwelling Longwave Radiation
+ Surface Upwelling Longwave Radiation.
sfcWind : xarray.DataArray, optional
- Surface wind velocity (at 10 m)
+ Surface wind velocity (at 10 m).
method : str
Method to use to calculate the potential evapotranspiration.
- See Also
- --------
- xclim.indicators.atmos.potential_evapotranspiration
-
Returns
-------
xarray.DataArray
Precipitation minus potential evapotranspiration.
+
+ See Also
+ --------
+ xclim.indicators.atmos.potential_evapotranspiration : Potential evapotranspiration calculation.
"""
pr = convert_units_to(pr, "kg m-2 s-1", context="hydro")
@@ -927,7 +934,8 @@ def rain_season(
date_max_end: DayOfYearStr = "12-31",
freq="YS-JAN",
) -> tuple[xarray.DataArray, xarray.DataArray, xarray.DataArray]:
- """Find the length of the rain season and the day of year of its start and its end.
+ """
+ Find the length of the rain season and the day of year of its start and its end.
The rain season begins when two conditions are met: 1) There must be a number of wet days with precipitations above
or equal to a given threshold; 2) There must be another sequence following, where, for a given period in time, there
@@ -942,7 +950,7 @@ def rain_season(
Accumulated precipitation threshold associated with `window_wet_start`.
window_wet_start : int
Number of days when accumulated precipitation is above `thresh_wet_start`.
- Defines the first condition to start the rain season
+ Defines the first condition to start the rain season.
window_not_dry_start : int
Number of days, after `window_wet_start` days, during which no dry period must be found as a second and last
condition to start the rain season.
@@ -984,8 +992,11 @@ def rain_season(
Returns
-------
rain_season_start: xr.DataArray, [dimensionless]
+ The beginning of the rain season.
rain_season_end: xr.DataArray, [dimensionless]
+ The end of the rain season.
rain_season_length: xr.DataArray, [time]
+ The length of the rain season.
Notes
-----
@@ -1119,7 +1130,8 @@ def standardized_precipitation_index(
params: Quantified | None = None,
**indexer,
) -> xarray.DataArray:
- r"""Standardized Precipitation Index (SPI).
+ r"""
+ Standardized Precipitation Index (SPI).
Parameters
----------
@@ -1133,9 +1145,9 @@ def standardized_precipitation_index(
i.e. a monthly resampling, the window is an integer number of months.
dist : {"gamma", "fisk"}
Name of the univariate distribution. (see :py:mod:`scipy.stats`).
- method : {'APP', 'ML'}
+ method : {"APP", "ML"}
Name of the fitting method, such as `ML` (maximum likelihood), `APP` (approximate). The approximate method
- uses a deterministic function that doesn't involve any optimization.
+ uses a deterministic function that does not involve any optimization.
fitkwargs : dict, optional
Kwargs passed to ``xclim.indices.stats.fit`` used to impose values of certains parameters (`floc`, `fscale`).
cal_start : DateStr, optional
@@ -1148,7 +1160,7 @@ def standardized_precipitation_index(
Fit parameters.
The `params` can be computed using ``xclim.indices.stats.standardized_index_fit_params`` in advance.
The output can be given here as input, and it overrides other options.
- \*\*indexer
+ **indexer : {dim: indexer}, optional
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`.
@@ -1170,8 +1182,12 @@ def standardized_precipitation_index(
the inversion to the normal distribution.
* The results from `climate_indices` library can be reproduced with `method = "APP"` and `fitwkargs = {"floc": 0}`
- Example
- -------
+ References
+ ----------
+ :cite:cts:`mckee_relationship_1993`
+
+ Examples
+ --------
>>> from datetime import datetime
>>> from xclim.indices import standardized_precipitation_index
>>> ds = xr.open_dataset(path_to_pr_file)
@@ -1200,10 +1216,6 @@ def standardized_precipitation_index(
... zero_inflated=True,
... ) # First getting params
>>> spi_3 = standardized_precipitation_index(pr, params=params)
-
- References
- ----------
- :cite:cts:`mckee_relationship_1993`
"""
fitkwargs = fitkwargs or {}
dist_methods = {"gamma": ["ML", "APP"], "fisk": ["ML", "APP"]}
@@ -1250,7 +1262,8 @@ def standardized_precipitation_evapotranspiration_index(
params: Quantified | None = None,
**indexer,
) -> xarray.DataArray:
- r"""Standardized Precipitation Evapotranspiration Index (SPEI).
+ r"""
+ Standardized Precipitation Evapotranspiration Index (SPEI).
Precipitation minus potential evapotranspiration data (PET) fitted to a statistical distribution (dist), transformed
to a cdf, and inverted back to a gaussian normal pdf. The potential evapotranspiration is calculated with a given
@@ -1283,7 +1296,7 @@ def standardized_precipitation_evapotranspiration_index(
Fit parameters.
The `params` can be computed using ``xclim.indices.stats.standardized_index_fit_params`` in advance.
The output can be given here as input, and it overrides other options.
- \*\*indexer
+ **indexer : {dim: indexer}, optional
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`.
@@ -1294,7 +1307,7 @@ def standardized_precipitation_evapotranspiration_index(
See Also
--------
- standardized_precipitation_index
+ standardized_precipitation_index : Standardized Precipitation Index.
"""
fitkwargs = fitkwargs or {}
@@ -1330,7 +1343,8 @@ def standardized_precipitation_evapotranspiration_index(
def qian_weighted_mean_average(
tas: xarray.DataArray, dim: str = "time"
) -> xarray.DataArray:
- r"""Binomial smoothed, five-day weighted mean average temperature.
+ r"""
+ Binomial smoothed, five-day weighted mean average temperature.
Calculates a five-day weighted moving average with emphasis on temperatures closer to day of interest.
@@ -1387,7 +1401,8 @@ def effective_growing_degree_days(
dim: str = "time",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Effective growing degree days.
+ r"""
+ Effective growing degree days.
Growing degree days based on a dynamic start and end of the growing season,
as defined in :cite:p:`bootsma_impacts_2005`.
@@ -1478,10 +1493,11 @@ def effective_growing_degree_days(
@declare_units(tasmin="[temperature]")
-def hardiness_zones(
+def hardiness_zones( # numpydoc ignore=SS05
tasmin: xarray.DataArray, window: int = 30, method: str = "usda", freq: str = "YS"
):
- """Hardiness zones.
+ """
+ Hardiness zones.
Hardiness zones are a categorization of the annual extreme temperature minima, averaged over a certain period.
The USDA method defines 14 zones, each divided into two sub-zones, using steps of 5°F, starting at -60°F.
@@ -1493,7 +1509,7 @@ def hardiness_zones(
Minimum temperature.
window : int
The length of the averaging window, in years.
- method : {'usda', 'anbg'}
+ method : {"usda", "anbg"}
Whether to return the American (`usda`) or the Australian (`anbg`) classification zones.
freq : str
Resampling frequency.
@@ -1581,26 +1597,30 @@ def _apply_chill_portion_one_season(tas_K):
def chill_portions(
tas: xarray.DataArray, freq: str = "YS", **indexer
) -> xarray.DataArray:
- """Chill portion based on the dynamic model
+ r"""
+ Chill portion based on the dynamic model.
Chill portions are a measure to estimate the bud breaking potential of different crop.
- The constants and functions are taken from Luedeling et al. (2009) which formalises
- the method described in Fishman et al. (1987). The model computes the accumulation of
- an intermediate product that is transformed to the final product once it exceeds a
- certain concentration. The intermediate product can be broken down at higher temperatures
- but the final product is stable even at higher temperature. Thus the dynamic model is
- more accurate than the Utah model especially in moderate climates like Israel,
- California or Spain.
+ The constants and functions are taken from Luedeling et al. (2009) which formalises the method described in
+ Fishman et al. (1987). The model computes the accumulation of an intermediate product that is transformed to
+ the final product once it exceeds a certain concentration. The intermediate product can be broken down at higher
+ temperatures but the final product is stable even at higher temperature. Thus, the dynamic model is more accurate
+ than the Utah model especially in moderate climates like Israel, California, or Spain.
Parameters
----------
tas : xr.DataArray
Hourly temperature.
+ freq : str
+ Resampling frequency.
+ **indexer : {dim: indexer}, optional
+ 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`.
Returns
-------
xr.DataArray, [unitless]
- Chill portions after the Dynamic Model
+ Chill portions after the Dynamic Model.
Notes
-----
@@ -1613,6 +1633,10 @@ def chill_portions(
Note that incomplete periods will lead to NaNs.
+ References
+ ----------
+ :cite:cts:`fishman_chill_1987,luedeling_chill_2009`
+
Examples
--------
>>> from xclim.indices import chill_portions
@@ -1621,10 +1645,6 @@ def chill_portions(
>>> tasmax = xr.open_dataset(path_to_tasmax_file).tasmax
>>> tas_hourly = make_hourly_temperature(tasmin, tasmax)
>>> cp = chill_portions(tasmin)
-
- References
- ----------
- :cite:cts:`fishman_chill_1987,luedeling_chill_2009`
"""
tas_K: xarray.DataArray = select_time(
convert_units_to(tas, "K"), drop=True, **indexer
@@ -1638,7 +1658,8 @@ def chill_portions(
def chill_units(
tas: xarray.DataArray, positive_only: bool = False, freq: str = "YS"
) -> xarray.DataArray:
- """Chill units using the Utah model
+ """
+ Chill units using the Utah model.
Chill units are a measure to estimate the bud breaking potential of different crop based on Richardson et al. (1974).
The Utah model assigns a weight to each hour depending on the temperature recognising that high temperatures can actual decrease,
@@ -1650,11 +1671,17 @@ def chill_units(
Hourly temperature.
positive_only : bool
If `True`, only positive daily chill units are aggregated.
+ freq : str
+ Resampling frequency.
Returns
-------
xr.DataArray, [unitless]
- Chill units using the Utah model
+ Chill units using the Utah model.
+
+ References
+ ----------
+ :cite:cts:`richardson_chill_1974`
Examples
--------
@@ -1664,10 +1691,6 @@ def chill_units(
>>> tasmax = xr.open_dataset(path_to_tasmax_file).tasmax
>>> tas_hourly = make_hourly_temperature(tasmin, tasmax)
>>> cu = chill_units(tasmin)
-
- References
- ----------
- :cite:cts:`richardson_chill_1974`
"""
tas = convert_units_to(tas, "degC")
cu = xarray.where(
diff --git a/xclim/indices/_anuclim.py b/xclim/indices/_anuclim.py
index 14ef28afb..ff1dc4413 100644
--- a/xclim/indices/_anuclim.py
+++ b/xclim/indices/_anuclim.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""ANUCLIM indice definitions."""
+
from __future__ import annotations
from collections.abc import Callable
@@ -65,7 +66,8 @@
def isothermality(
tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Isothermality.
+ r"""
+ Isothermality.
The mean diurnal temperature range divided by the annual temperature range.
@@ -81,7 +83,7 @@ def isothermality(
Returns
-------
xarray.DataArray, [%]
- Isothermality
+ Isothermality.
Notes
-----
@@ -105,7 +107,8 @@ def isothermality(
def temperature_seasonality(
tas: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Temperature seasonality (coefficient of variation).
+ r"""
+ Temperature seasonality (coefficient of variation).
The annual temperature coefficient of variation expressed in percent. Calculated as the standard deviation
of temperature values for a given year expressed as a percentage of the mean of those temperatures.
@@ -120,19 +123,9 @@ def temperature_seasonality(
Returns
-------
xarray.DataArray, [%]
- Mean temperature coefficient of variation
+ Mean temperature coefficient of variation.
freq : str
- Resampling frequency.
-
- Examples
- --------
- The following would compute for each grid cell of file `tas.day.nc` the annual temperature seasonality:
-
- >>> import xclim.indices as xci
- >>> t = xr.open_dataset(path_to_tas_file).tas
- >>> tday_seasonality = xci.temperature_seasonality(t)
- >>> t_weekly = xci.tg_mean(t, freq="7D")
- >>> tweek_seasonality = xci.temperature_seasonality(t_weekly)
+ Resampling frequency.
Notes
-----
@@ -147,6 +140,16 @@ def temperature_seasonality(
References
----------
:cite:cts:`xu_anuclim_2010`
+
+ Examples
+ --------
+ The following would compute for each grid cell of file `tas.day.nc` the annual temperature seasonality:
+
+ >>> import xclim.indices as xci
+ >>> t = xr.open_dataset(path_to_tas_file).tas
+ >>> tday_seasonality = xci.temperature_seasonality(t)
+ >>> t_weekly = xci.tg_mean(t, freq="7D")
+ >>> tweek_seasonality = xci.temperature_seasonality(t_weekly)
"""
tas = convert_units_to(tas, "K")
@@ -157,7 +160,8 @@ def temperature_seasonality(
@declare_units(pr="[precipitation]")
def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Precipitation Seasonality (C of V).
+ r"""
+ 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.
@@ -173,20 +177,7 @@ def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArr
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)
+ Precipitation coefficient of variation.
Notes
-----
@@ -201,6 +192,19 @@ def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArr
References
----------
:cite:cts:`xu_anuclim_2010`
+
+ 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)
"""
# If units in mm/sec convert to mm/days to avoid potentially small denominator
if units2pint(pr) == units("mm / s"):
@@ -217,7 +221,8 @@ def tg_mean_warmcold_quarter(
op: str,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Mean temperature of warmest/coldest quarter.
+ r"""
+ Mean temperature of warmest/coldest quarter.
The warmest (or coldest) quarter of the year is determined, and the mean temperature of this period is calculated.
If the input data frequency is daily ("D") or weekly ("W"), quarters are defined as 13-week periods, otherwise as
@@ -235,16 +240,7 @@ def tg_mean_warmcold_quarter(
Returns
-------
xarray.DataArray, [same as tas]
- Mean temperature of {op} quarter
-
- Examples
- --------
- The following would compute for each grid cell of file `tas.day.nc` the annual temperature of the
- warmest quarter mean temperature:
-
- >>> from xclim.indices import tg_mean_warmcold_quarter
- >>> t = xr.open_dataset(path_to_tas_file)
- >>> t_warm_qrt = tg_mean_warmcold_quarter(tas=t.tas, op="warmest")
+ Mean temperature of {op} quarter.
Notes
-----
@@ -256,6 +252,15 @@ def tg_mean_warmcold_quarter(
References
----------
:cite:cts:`xu_anuclim_2010`
+
+ Examples
+ --------
+ The following would compute for each grid cell of file `tas.day.nc` the annual temperature of the
+ warmest quarter mean temperature:
+
+ >>> from xclim.indices import tg_mean_warmcold_quarter
+ >>> t = xr.open_dataset(path_to_tas_file)
+ >>> t_warm_qrt = tg_mean_warmcold_quarter(tas=t.tas, op="warmest")
"""
out = _to_quarter(tas=tas)
@@ -277,11 +282,12 @@ def tg_mean_wetdry_quarter(
op: str,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Mean temperature of wettest/driest quarter.
+ r"""
+ Mean temperature of wettest/driest quarter.
The wettest (or driest) quarter of the year is determined, and the mean temperature of this period is calculated.
If the input data frequency is daily ("D") or weekly ("W"), quarters are defined as 13-week periods,
- otherwise are 3 months.
+ otherwise as three (3) months.
Parameters
----------
@@ -297,7 +303,7 @@ def tg_mean_wetdry_quarter(
Returns
-------
xarray.DataArray, [same as tas]
- Mean temperature of {op} quarter
+ Mean temperature of {op} quarter.
Notes
-----
@@ -329,10 +335,11 @@ def tg_mean_wetdry_quarter(
def prcptot_wetdry_quarter(
pr: xarray.DataArray, op: str, freq: str = "YS"
) -> xarray.DataArray:
- r"""Total precipitation of wettest/driest quarter.
+ r"""
+ Total precipitation of wettest/driest quarter.
The wettest (or driest) quarter of the year is determined, and the total precipitation of this period is calculated.
- If the input data frequency is daily ("D") or weekly ("W") quarters are defined as 13-week periods, otherwise are
+ If the input data frequency is daily ("D") or weekly ("W") quarters are defined as 13-week periods, otherwise as
three (3) months.
Parameters
@@ -347,15 +354,7 @@ def prcptot_wetdry_quarter(
Returns
-------
xarray.DataArray, [length]
- Precipitation of {op} quarter
-
- Examples
- --------
- The following would compute for each grid cell of file `pr.day.nc` the annual wettest quarter total precipitation:
-
- >>> from xclim.indices import prcptot_wetdry_quarter
- >>> p = xr.open_dataset(path_to_pr_file)
- >>> pr_warm_qrt = prcptot_wetdry_quarter(pr=p.pr, op="wettest")
+ Precipitation of {op} quarter.
Notes
-----
@@ -367,6 +366,14 @@ def prcptot_wetdry_quarter(
References
----------
:cite:cts:`xu_anuclim_2010`
+
+ Examples
+ --------
+ The following would compute for each grid cell of file `pr.day.nc` the annual wettest quarter total precipitation:
+
+ >>> from xclim.indices import prcptot_wetdry_quarter
+ >>> p = xr.open_dataset(path_to_pr_file)
+ >>> pr_warm_qrt = prcptot_wetdry_quarter(pr=p.pr, op="wettest")
"""
# returns mm values
pr_qrt = _to_quarter(pr=pr)
@@ -389,11 +396,12 @@ def prcptot_warmcold_quarter(
op: str,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Total precipitation of warmest/coldest quarter.
+ r"""
+ Total precipitation of warmest/coldest quarter.
The warmest (or coldest) quarter of the year is determined, and the total precipitation of this period is
- calculated. If the input data frequency is daily ("D) or weekly ("W"), quarters are defined as 13-week periods,
- otherwise are 3 months.
+ calculated. If the input data frequency is daily ("D") or weekly ("W"), quarters are defined as 13-week periods,
+ otherwise as three (3) months.
Parameters
----------
@@ -409,7 +417,7 @@ def prcptot_warmcold_quarter(
Returns
-------
xarray.DataArray, [mm]
- Precipitation of {op} quarter
+ Precipitation of {op} quarter.
Notes
-----
@@ -442,7 +450,8 @@ def prcptot_warmcold_quarter(
def prcptot(
pr: xarray.DataArray, thresh: Quantified = "0 mm/d", freq: str = "YS"
) -> xarray.DataArray:
- r"""Accumulated total precipitation.
+ r"""
+ Accumulated total precipitation.
The total accumulated precipitation from days where precipitation exceeds a given amount. A threshold is provided in
order to allow the option of reducing the impact of days with trace precipitation amounts on period totals.
@@ -471,7 +480,8 @@ def prcptot(
def prcptot_wetdry_period(
pr: xarray.DataArray, *, op: str, freq: str = "YS"
) -> xarray.DataArray:
- r"""Precipitation of the wettest/driest day, week, or month, depending on the time step.
+ r"""
+ Precipitation of the wettest/driest day, week, or month, depending on the time step.
The wettest (or driest) period is determined, and the total precipitation of this period is calculated.
@@ -487,7 +497,7 @@ def prcptot_wetdry_period(
Returns
-------
xarray.DataArray, [length]
- Precipitation of {op} period
+ Precipitation of {op} period.
Notes
-----
@@ -560,7 +570,8 @@ def _to_quarter(
pr: xarray.DataArray | None = None,
tas: xarray.DataArray | None = None,
) -> xarray.DataArray:
- """Convert daily, weekly or monthly time series to quarterly time series according to ANUCLIM specifications.
+ """
+ Convert daily, weekly or monthly time series to quarterly time series according to ANUCLIM specifications.
Parameters
----------
diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py
index f5f13e0b4..b8868baef 100644
--- a/xclim/indices/_conversion.py
+++ b/xclim/indices/_conversion.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Conversion and approximation functions."""
+
from __future__ import annotations
from typing import cast
@@ -62,25 +63,26 @@ def humidex(
tdps: xr.DataArray | None = None,
hurs: xr.DataArray | None = None,
) -> xr.DataArray:
- r"""Humidex index.
+ 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.
+ 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.
+ Mean Temperature.
tdps : xarray.DataArray, optional
- Dewpoint temperature, used to compute the vapour pressure.
+ Dewpoint Temperature, used to compute the vapour pressure.
hurs : xarray.DataArray, optional
- Relative humidity, used as an alternative way to compute the vapour pressure if the dewpoint temperature is not
+ Relative Humidity, used as an alternative way to compute the vapour pressure if the dewpoint temperature is not
available.
Returns
-------
xarray.DataArray, [temperature]
- The humidex index.
+ The Humidex Index.
Notes
-----
@@ -155,7 +157,8 @@ def humidex(
@declare_units(tas="[temperature]", hurs="[]")
def heat_index(tas: xr.DataArray, hurs: xr.DataArray) -> xr.DataArray:
- r"""Heat index.
+ r"""
+ Heat index.
Perceived temperature after relative humidity is taken into account :cite:p:`blazejczyk_comparison_2012`.
The index is only valid for temperatures above 20°C.
@@ -163,25 +166,25 @@ def heat_index(tas: xr.DataArray, hurs: xr.DataArray) -> xr.DataArray:
Parameters
----------
tas : xr.DataArray
- Temperature. The equation assumes an instantaneous value.
+ Mean Temperature. The equation assumes an instantaneous value.
hurs : xr.DataArray
- Relative humidity. The equation assumes an instantaneous value.
+ Relative Humidity. The equation assumes an instantaneous value.
Returns
-------
xr.DataArray, [temperature]
Heat index for moments with temperature above 20°C.
- References
- ----------
- :cite:cts:`blazejczyk_comparison_2012`
-
Notes
-----
- While both the humidex and the heat index are calculated using dew point the humidex uses a dew point of 7 °C
+ 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 vapour pressure, which is used exclusively in the
- humidex calculation.
+ Humidex calculation.
+
+ References
+ ----------
+ :cite:cts:`blazejczyk_comparison_2012`
"""
thresh = 20 # degC
t = convert_units_to(tas, "degC")
@@ -205,21 +208,22 @@ def heat_index(tas: xr.DataArray, hurs: xr.DataArray) -> xr.DataArray:
@declare_units(tasmin="[temperature]", tasmax="[temperature]")
def tas(tasmin: xr.DataArray, tasmax: xr.DataArray) -> xr.DataArray:
- """Average temperature from minimum and maximum temperatures.
+ """
+ Average temperature from minimum and maximum temperatures.
- We assume a symmetrical distribution for the temperature and retrieve the average value as Tg = (Tx + Tn) / 2
+ We assume a symmetrical distribution for the temperature and retrieve the average value as Tg = (Tx + Tn) / 2.
Parameters
----------
tasmin : xarray.DataArray
- Minimum (daily) temperature
+ Minimum (daily) Temperature.
tasmax : xarray.DataArray
- Maximum (daily) temperature
+ Maximum (daily) Temperature.
Returns
-------
xarray.DataArray
- Mean (daily) temperature [same units as tasmin]
+ Mean (daily) Temperature [same units as tasmin].
Examples
--------
@@ -236,7 +240,8 @@ def tas(tasmin: xr.DataArray, tasmax: xr.DataArray) -> xr.DataArray:
def uas_vas_2_sfcwind(
uas: xr.DataArray, vas: xr.DataArray, calm_wind_thresh: Quantified = "0.5 m/s"
) -> tuple[xr.DataArray, xr.DataArray]:
- """Wind speed and direction from the eastward and northward wind components.
+ """
+ Wind speed and direction from the eastward and northward wind components.
Computes the magnitude and angle of the wind vector from its northward and eastward components,
following the meteorological convention that sets calm wind to a direction of 0° and northerly wind to 360°.
@@ -244,20 +249,23 @@ def uas_vas_2_sfcwind(
Parameters
----------
uas : xr.DataArray
- Eastward wind velocity
+ Eastward Wind Velocity.
vas : xr.DataArray
- Northward wind velocity
+ Northward Wind Velocity.
calm_wind_thresh : Quantified
- The threshold under which winds are considered "calm" and for which the direction
- is set to 0. On the Beaufort scale, calm winds are defined as < 0.5 m/s.
+ The threshold under which winds are considered "calm" and for which the direction is set to 0.
+ On the Beaufort scale, calm winds are defined as < 0.5 m/s.
Returns
-------
wind : xr.DataArray, [m s-1]
- Wind velocity
+ Wind Velocity.
wind_from_dir : xr.DataArray, [°]
- Direction from which the wind blows, following the meteorological convention where
- 360 stands for North and 0 for calm winds.
+ Direction from which the wind blows, following the meteorological convention where 360 stands for North and 0 for calm winds.
+
+ Notes
+ -----
+ Winds with a velocity less than `calm_wind_thresh` are given a wind direction of 0°, while stronger northerly winds are set to 360°.
Examples
--------
@@ -265,11 +273,6 @@ def uas_vas_2_sfcwind(
>>> sfcWind = uas_vas_2_sfcwind(
... uas=uas_dataset, vas=vas_dataset, calm_wind_thresh="0.5 m/s"
... )
-
- Notes
- -----
- Winds with a velocity less than `calm_wind_thresh` are given a wind direction of 0°,
- while stronger northerly winds are set to 360°.
"""
# Converts the wind speed to m s-1
uas = convert_units_to(uas, "m/s")
@@ -299,24 +302,24 @@ def uas_vas_2_sfcwind(
def sfcwind_2_uas_vas(
sfcWind: xr.DataArray, sfcWindfromdir: xr.DataArray # noqa
) -> tuple[xr.DataArray, xr.DataArray]:
- """Eastward and northward wind components from the wind speed and direction.
+ """
+ Eastward and northward wind components from the wind speed and direction.
Compute the eastward and northward wind components from the wind speed and direction.
Parameters
----------
sfcWind : xr.DataArray
- Wind velocity
+ Wind Velocity.
sfcWindfromdir : xr.DataArray
- Direction from which the wind blows, following the meteorological convention
- where 360 stands for North.
+ Direction from which the wind blows, following the meteorological convention, where "360" denotes "North".
Returns
-------
uas : xr.DataArray, [m s-1]
- Eastward wind velocity.
+ Eastward Wind Velocity.
vas : xr.DataArray, [m s-1]
- Northward wind velocity.
+ Northward Wind Velocity.
Examples
--------
@@ -354,12 +357,13 @@ def saturation_vapor_pressure(
ice_thresh: Quantified | None = None,
method: str = "sonntag90", # noqa
) -> xr.DataArray:
- """Saturation vapour pressure from temperature.
+ """
+ Saturation vapour pressure from temperature.
Parameters
----------
tas : xr.DataArray
- Temperature array.
+ Mean Temperature.
ice_thresh : Quantified, optional
Threshold temperature under which to switch to equations in reference to ice instead of water.
If None (default) everything is computed with reference to water.
@@ -369,7 +373,7 @@ def saturation_vapor_pressure(
Returns
-------
xarray.DataArray, [Pa]
- Saturation vapour pressure.
+ Saturation Vapour Pressure.
Notes
-----
@@ -382,16 +386,16 @@ def saturation_vapor_pressure(
- "wmo08" or "WMO08", taken from :cite:t:`world_meteorological_organization_guide_2008`.
- "its90" or "ITS90", taken from :cite:t:`hardy_its-90_1998`.
+ References
+ ----------
+ :cite:cts:`goff_low-pressure_1946,hardy_its-90_1998,sonntag_important_1990,tetens_uber_1930,vomel_saturation_2016,world_meteorological_organization_guide_2008`
+
Examples
--------
>>> from xclim.indices import saturation_vapor_pressure
>>> rh = saturation_vapor_pressure(
... tas=tas_dataset, ice_thresh="0 degC", method="wmo08"
... )
-
- References
- ----------
- :cite:cts:`goff_low-pressure_1946,hardy_its-90_1998,sonntag_important_1990,tetens_uber_1930,vomel_saturation_2016,world_meteorological_organization_guide_2008`
"""
if ice_thresh is not None:
thresh = convert_units_to(ice_thresh, "K")
@@ -503,7 +507,8 @@ def relative_humidity(
method: str = "sonntag90",
invalid_values: str = "clip",
) -> xr.DataArray:
- r"""Relative humidity.
+ r"""
+ Relative humidity.
Compute relative humidity from temperature and either dewpoint temperature or specific humidity and pressure through
the saturation vapour pressure.
@@ -511,13 +516,14 @@ def relative_humidity(
Parameters
----------
tas : xr.DataArray
- Temperature array
+ Mean Temperature.
tdps : xr.DataArray, optional
- Dewpoint temperature, if specified, overrides huss and ps.
+ Dewpoint Temperature.
+ If specified, overrides `huss` and `ps`.
huss : xr.DataArray, optional
- Specific humidity. Must be given if tdps is not given.
+ Specific Humidity. Must be given if `tdps` is not given.
ps : xr.DataArray, optional
- Air Pressure. Must be given if tdps is not given.
+ Air Pressure. Must be given if `tdps` is not given.
ice_thresh : Quantified, optional
Threshold temperature under which to switch to equations in reference to ice instead of water.
If None (default) everything is computed with reference to water. Does nothing if 'method' is "bohren98".
@@ -530,7 +536,7 @@ def relative_humidity(
Returns
-------
xr.DataArray, [%]
- Relative humidity.
+ Relative Humidity.
Notes
-----
@@ -567,6 +573,10 @@ def relative_humidity(
The methods differ by how :math:`e_{sat}` is computed. See the doc of :py:func:`xclim.core.utils.saturation_vapor_pressure`.
+ References
+ ----------
+ :cite:cts:`bohren_atmospheric_1998,lawrence_relationship_2005`
+
Examples
--------
>>> from xclim.indices import relative_humidity
@@ -579,10 +589,6 @@ def relative_humidity(
... method="wmo08",
... invalid_values="clip",
... )
-
- References
- ----------
- :cite:cts:`bohren_atmospheric_1998,lawrence_relationship_2005`
"""
hurs: xr.DataArray
if method in ("bohren98", "BA90"):
@@ -636,7 +642,8 @@ def specific_humidity(
method: str = "sonntag90",
invalid_values: str | None = None,
) -> xr.DataArray:
- r"""Specific humidity from temperature, relative humidity and pressure.
+ r"""
+ Specific humidity from temperature, relative humidity, and pressure.
Specific humidity is the ratio between the mass of water vapour
and the mass of moist air :cite:p:`world_meteorological_organization_guide_2008`.
@@ -644,7 +651,7 @@ def specific_humidity(
Parameters
----------
tas : xr.DataArray
- Temperature array
+ Mean Temperature.
hurs : xr.DataArray
Relative Humidity.
ps : xr.DataArray
@@ -663,7 +670,7 @@ def specific_humidity(
Returns
-------
xarray.DataArray, [dimensionless]
- Specific humidity.
+ Specific Humidity.
Notes
-----
@@ -685,6 +692,10 @@ def specific_humidity(
q_{sat} = w_{sat} / (1 + w_{sat})
+ References
+ ----------
+ :cite:cts:`world_meteorological_organization_guide_2008`
+
Examples
--------
>>> from xclim.indices import specific_humidity
@@ -696,10 +707,6 @@ def specific_humidity(
... method="wmo08",
... invalid_values="mask",
... )
-
- References
- ----------
- :cite:cts:`world_meteorological_organization_guide_2008`
"""
ps = convert_units_to(ps, "Pa")
hurs = convert_units_to(hurs, "")
@@ -730,7 +737,8 @@ def specific_humidity_from_dewpoint(
ps: xr.DataArray,
method: str = "sonntag90",
) -> xr.DataArray:
- r"""Specific humidity from dewpoint temperature and air pressure.
+ r"""
+ Specific humidity from dewpoint temperature and air pressure.
Specific humidity is the ratio between the mass of water vapour
and the mass of moist air :cite:p:`world_meteorological_organization_guide_2008`.
@@ -738,16 +746,16 @@ def specific_humidity_from_dewpoint(
Parameters
----------
tdps : xr.DataArray
- Dewpoint temperature array.
+ Dewpoint Temperature.
ps : xr.DataArray
- Air pressure array.
+ Air Pressure.
method : {"goffgratch46", "sonntag90", "tetens30", "wmo08"}
Method to compute the saturation vapour pressure.
Returns
-------
xarray.DataArray, [dimensionless]
- Specific humidity.
+ Specific Humidity.
Notes
-----
@@ -760,6 +768,10 @@ def specific_humidity_from_dewpoint(
where :math:`m_w` and :math:`m_a` are the molecular weights of water and dry air respectively. This formula is often
written with :math:`ε = m_w / m_a`, which simplifies to :math:`q = ε e / (p - e (1 - ε))`.
+ References
+ ----------
+ :cite:cts:`world_meteorological_organization_guide_2008`
+
Examples
--------
>>> from xclim.indices import specific_humidity_from_dewpoint
@@ -768,10 +780,6 @@ def specific_humidity_from_dewpoint(
... ps=ps_dataset,
... method="wmo08",
... )
-
- References
- ----------
- :cite:cts:`world_meteorological_organization_guide_2008`
"""
EPSILON = 0.6219569 # weight of water vs dry air []
e = saturation_vapor_pressure(tas=tdps, method=method) # vapour pressure [Pa]
@@ -789,16 +797,17 @@ def snowfall_approximation(
thresh: Quantified = "0 degC",
method: str = "binary",
) -> xr.DataArray:
- """Snowfall approximation from total precipitation and temperature.
+ """
+ Snowfall approximation from total precipitation and temperature.
Solid precipitation estimated from precipitation and temperature according to a given method.
Parameters
----------
pr : xarray.DataArray
- Mean daily precipitation flux.
+ Mean daily Precipitation Flux.
tas : xarray.DataArray, optional
- Mean, maximum, or minimum daily temperature.
+ Mean, Maximum, or Minimum daily Temperature.
thresh : Quantified
Freezing point temperature. Non-scalar values are not allowed with method "brown".
method : {"binary", "brown", "auer"}
@@ -807,7 +816,11 @@ def snowfall_approximation(
Returns
-------
xarray.DataArray, [same units as pr]
- Solid precipitation flux.
+ Solid Precipitation Flux.
+
+ See Also
+ --------
+ rain_approximation : Rainfall approximation from total precipitation and temperature.
Notes
-----
@@ -886,7 +899,8 @@ def rain_approximation(
thresh: Quantified = "0 degC",
method: str = "binary",
) -> xr.DataArray:
- """Rainfall approximation from total precipitation and temperature.
+ """
+ Rainfall approximation from total precipitation and temperature.
Liquid precipitation estimated from precipitation and temperature according to a given method.
This is a convenience method based on :py:func:`snowfall_approximation`, see the latter for details.
@@ -894,9 +908,9 @@ def rain_approximation(
Parameters
----------
pr : xarray.DataArray
- Mean daily precipitation flux.
+ Mean daily Precipitation Flux.
tas : xarray.DataArray, optional
- Mean, maximum, or minimum daily temperature.
+ Mean, Maximum, or Minimum daily Temperature.
thresh : Quantified
Freezing point temperature. Non-scalar values are not allowed with method 'brown'.
method : {"binary", "brown", "auer"}
@@ -907,14 +921,14 @@ def rain_approximation(
xarray.DataArray, [same units as pr]
Liquid precipitation rate.
+ See Also
+ --------
+ snowfall_approximation : Snowfall approximation from total precipitation and temperature.
+
Notes
-----
This method computes the snowfall approximation and subtracts it from the total
precipitation to estimate the liquid rain precipitation.
-
- See Also
- --------
- snowfall_approximation
"""
prra: xr.DataArray = pr - snowfall_approximation(
pr, tas, thresh=thresh, method=method
@@ -930,24 +944,26 @@ def snd_to_snw(
const: Quantified = "312 kg m-3",
out_units: str | None = None,
) -> xr.DataArray:
- """Snow amount from snow depth and density.
+ """
+ Snow amount from snow depth and density.
Parameters
----------
snd : xr.DataArray
- Snow depth.
+ Snow Depth.
snr : Quantified, optional
- Snow density.
- const: Quantified
- Constant snow density
- `const` is only used if `snr` is None.
- out_units: str, optional
- Desired units of the snow amount output. If `None`, output units simply follow from `snd * snr`.
+ Snow Density.
+ const : Quantified
+ Constant snow density.
+ `const` is only used if `snr` is `None`.
+ out_units : str, optional
+ Desired units of the snow amount output.
+ If `None`, output units simply follow from `snd * snr`.
Returns
-------
xr.DataArray
- Snow amount
+ Snow Amount.
Notes
-----
@@ -973,7 +989,8 @@ def snw_to_snd(
const: Quantified = "312 kg m-3",
out_units: str | None = None,
) -> xr.DataArray:
- """Snow depth from snow amount and density.
+ """
+ Snow depth from snow amount and density.
Parameters
----------
@@ -981,16 +998,16 @@ def snw_to_snd(
Snow amount.
snr : Quantified, optional
Snow density.
- const: Quantified
- Constant snow density
- `const` is only used if `snr` is None.
- out_units: str, optional
+ const : Quantified
+ Constant snow density.
+ `const` is only used if `snr` is `None`.
+ out_units : str, optional
Desired units of the snow depth output. If `None`, output units simply follow from `snw / snr`.
Returns
-------
xr.DataArray
- Snow depth
+ Snow Depth.
Notes
-----
@@ -1017,29 +1034,30 @@ def prsn_to_prsnd(
const: Quantified = "100 kg m-3",
out_units: str | None = None,
) -> xr.DataArray:
- """Snowfall rate from snowfall flux and density.
+ """
+ Snowfall rate from snowfall flux and density.
Parameters
----------
prsn : xr.DataArray
- Snowfall flux.
+ Snowfall Flux.
snr : xr.DataArray, optional
- Snow density.
- const: Quantified
+ Snow Density.
+ const : Quantified
Constant snow density.
- `const` is only used if `snr` is None.
- out_units: str, optional
- Desired units of the snowfall rate. If `None`, output units simply follow from `snd * snr`.
+ `const` is only used if `snr` is `None`.
+ out_units : str, optional
+ Desired units of the snowfall rate.
+ If `None`, output units simply follow from `snd * snr`.
Returns
-------
xr.DataArray
- Snowfall rate.
+ Snowfall Rate.
Notes
-----
- The estimated mean snow density value of 100 kg m-3 is taken from
- :cite:cts:`frei_snowfall_2018, cbcl_climate_2020`.
+ The estimated mean snow density value of 100 kg m-3 is taken from :cite:cts:`frei_snowfall_2018, cbcl_climate_2020`.
References
----------
@@ -1059,29 +1077,29 @@ def prsnd_to_prsn(
const: Quantified = "100 kg m-3",
out_units: str | None = None,
) -> xr.DataArray:
- """Snowfall flux from snowfall rate and density.
+ """
+ Snowfall flux from snowfall rate and density.
Parameters
----------
prsnd : xr.DataArray
- Snowfall rate.
+ Snowfall Rate.
snr : xr.DataArray, optional
- Snow density.
- const: Quantified
- Constant snow density.
- `const` is only used if `snr` is None.
- out_units: str, optional
+ Snow Density.
+ const : Quantified
+ Constant Snow Density.
+ `const` is only used if `snr` is `None`.
+ out_units : str, optional
Desired units of the snowfall rate. If `None`, output units simply follow from `snd * snr`.
Returns
-------
xr.DataArray
- Snowfall flux.
+ Snowfall Flux.
Notes
-----
- The estimated mean snow density value of 100 kg m-3 is taken from
- :cite:cts:`frei_snowfall_2018, cbcl_climate_2020`.
+ The estimated mean snow density value of 100 kg m-3 is taken from :cite:cts:`frei_snowfall_2018, cbcl_climate_2020`.
References
----------
@@ -1099,7 +1117,8 @@ def prsnd_to_prsn(
def longwave_upwelling_radiation_from_net_downwelling(
rls: xr.DataArray, rlds: xr.DataArray
) -> xr.DataArray:
- """Calculate upwelling thermal radiation from net thermal radiation and downwelling thermal radiation.
+ """
+ Calculate upwelling thermal radiation from net thermal radiation and downwelling thermal radiation.
Parameters
----------
@@ -1123,7 +1142,8 @@ def longwave_upwelling_radiation_from_net_downwelling(
def shortwave_upwelling_radiation_from_net_downwelling(
rss: xr.DataArray, rsds: xr.DataArray
) -> xr.DataArray:
- """Calculate upwelling solar radiation from net solar radiation and downwelling solar radiation.
+ """
+ Calculate upwelling solar radiation from net solar radiation and downwelling solar radiation.
Parameters
----------
@@ -1153,7 +1173,8 @@ def wind_chill_index(
method: str = "CAN",
mask_invalid: bool = True,
) -> xr.DataArray:
- r"""Wind chill index.
+ r"""
+ Wind chill index.
The Wind Chill Index is an estimation of how cold the weather feels to the average person.
It is computed from the air temperature and the 10-m wind. As defined by the Environment and Climate Change Canada
@@ -1239,7 +1260,8 @@ def clausius_clapeyron_scaled_precipitation(
pr_baseline: xr.DataArray,
cc_scale_factor: float = 1.07,
) -> xr.DataArray:
- r"""Scale precipitation according to the Clausius-Clapeyron relation.
+ r"""
+ Scale precipitation according to the Clausius-Clapeyron relation.
Parameters
----------
@@ -1255,6 +1277,12 @@ def clausius_clapeyron_scaled_precipitation(
xarray.DataArray
Baseline precipitation scaled to other climatology using Clausius-Clapeyron relationship.
+ Warnings
+ --------
+ Make sure that `delta_tas` is computed over a baseline compatible with `pr_baseline`. So for example,
+ if `delta_tas` is the climatological difference between a baseline and a future period, then `pr_baseline`
+ should be precipitations over a period within the same baseline.
+
Notes
-----
The Clausius-Clapeyron equation for water vapour under typical atmospheric conditions states that the saturation
@@ -1264,12 +1292,6 @@ def clausius_clapeyron_scaled_precipitation(
\frac{\mathrm{d}e_s(T)}{\mathrm{d}T} \approx 1.07 e_s(T)
This function assumes that precipitation can be scaled by the same factor.
-
- Warnings
- --------
- Make sure that `delta_tas` is computed over a baseline compatible with `pr_baseline`. So for example,
- if `delta_tas` is the climatological difference between a baseline and a future period, then `pr_baseline`
- should be precipitations over a period within the same baseline.
"""
# Get difference in temperature. Time-invariant baseline temperature (from above) is broadcast.
delta_tas = convert_units_to(delta_tas, "delta_degreeC")
@@ -1326,7 +1348,8 @@ def potential_evapotranspiration(
peta: float = 0.00516409319477,
petb: float = 0.0874972822289,
) -> xr.DataArray:
- r"""Potential evapotranspiration.
+ r"""
+ Potential evapotranspiration.
The potential for water evaporation from soil and transpiration by plants if the water supply is sufficient,
according to a given method.
@@ -1334,29 +1357,30 @@ def potential_evapotranspiration(
Parameters
----------
tasmin : xarray.DataArray, optional
- Minimum daily temperature.
+ Minimum daily Temperature.
tasmax : xarray.DataArray, optional
- Maximum daily temperature.
+ Maximum daily Temperature.
tas : xarray.DataArray, optional
- Mean daily temperature.
+ Mean daily Temperature.
lat : xarray.DataArray, optional
- Latitude. If not given, it is sought on tasmin or tas using cf-xarray accessors.
+ Latitude.
+ If not provided, it is sought on `tasmin` or `tas` using cf-xarray accessors.
hurs : xarray.DataArray, optional
- Relative humidity.
+ Relative Humidity.
rsds : xarray.DataArray, optional
- Surface Downwelling Shortwave Radiation
+ Surface Downwelling Shortwave Radiation.
rsus : xarray.DataArray, optional
- Surface Upwelling Shortwave Radiation
+ Surface Upwelling Shortwave Radiation.
rlds : xarray.DataArray, optional
- Surface Downwelling Longwave Radiation
+ Surface Downwelling Longwave Radiation.
rlus : xarray.DataArray, optional
- Surface Upwelling Longwave Radiation
+ Surface Upwelling Longwave Radiation.
sfcWind : xarray.DataArray, optional
- Surface wind velocity (at 10 m)
+ Surface Wind Velocity (at 10 m).
pr : xarray.DataArray
- Mean daily precipitation flux.
+ Mean daily Precipitation Flux.
method : {"baierrobertson65", "BR65", "hargreaves85", "HG85", "thornthwaite48", "TW48", "mcguinnessbordne05", "MB05", "allen98", "FAO_PM98", "droogersallen02", "DA02"}
- Which method to use, see notes.
+ Which method to use, see Notes.
peta : float
Used only with method MB05 as :math:`a` for calculation of PET, see Notes section.
Default value resulted from calibration of PET over the UK.
@@ -1367,6 +1391,7 @@ def potential_evapotranspiration(
Returns
-------
xarray.DataArray
+ Potential Evapotranspiration.
Notes
-----
@@ -1831,7 +1856,8 @@ def universal_thermal_climate_index(
mask_invalid: bool = True,
wind_cap_min: bool = False,
) -> xr.DataArray:
- r"""Universal thermal climate index (UTCI).
+ r"""
+ Universal thermal climate index (UTCI).
The UTCI is the equivalent temperature for the environment derived from a
reference environment and is used to evaluate heat stress in outdoor spaces.
@@ -1839,38 +1865,38 @@ def universal_thermal_climate_index(
Parameters
----------
tas : xarray.DataArray
- Mean temperature
+ Mean Temperature.
hurs : xarray.DataArray
- Relative Humidity
+ Relative Humidity.
sfcWind : xarray.DataArray
- Wind velocity
- mrt: xarray.DataArray, optional
- Mean radiant temperature
+ Wind Velocity.
+ mrt : xarray.DataArray, optional
+ Mean Radiant Temperature.
rsds : xr.DataArray, optional
- Surface Downwelling Shortwave Radiation
- This is necessary if mrt is not None.
+ Surface Downwelling Shortwave Radiation.
+ This is necessary if `mrt` is not `None`.
rsus : xr.DataArray, optional
- Surface Upwelling Shortwave Radiation
- This is necessary if mrt is not None.
+ Surface Upwelling Shortwave Radiation.
+ This is necessary if `mrt` is not `None`.
rlds : xr.DataArray, optional
- Surface Downwelling Longwave Radiation
- This is necessary if mrt is not None.
+ Surface Downwelling Longwave Radiation.
+ This is necessary if `mrt` is not `None`.
rlus : xr.DataArray, optional
- Surface Upwelling Longwave Radiation
- This is necessary if mrt is not None.
+ Surface Upwelling Longwave Radiation.
+ This is necessary if `mrt` is not `None`.
stat : {'instant', 'sunlit'}
- Which statistic to apply. If "instant", the instantaneous cosine
- of the solar zenith angle is calculated. If "sunlit", the cosine of the
- solar zenith angle is calculated during the sunlit period of each interval.
- This is necessary if mrt is not None.
- mask_invalid: bool
- If True (default), UTCI values are NaN where any of the inputs are outside
- their validity ranges : -50°C < tas < 50°C, -30°C < tas - mrt < 30°C
- and 0.5 m/s < sfcWind < 17.0 m/s.
- wind_cap_min: bool
- If True, wind velocities are capped to a minimum of 0.5 m/s following
- :cite:t:`brode_utci_2012` usage guidalines. This ensures UTCI calculation
- for low winds. Default value False.
+ Which statistic to apply.
+ If "instant", the instantaneous cosine of the solar zenith angle is calculated.
+ If "sunlit", the cosine of the solar zenith angle is calculated during the sunlit period of each interval.
+ This is necessary if `mrt` is not `None`.
+ mask_invalid : bool
+ If True (default), UTCI values are NaN where any of the inputs are outside their validity ranges:
+ - -50°C < tas < 50°C.
+ - -30°C < tas - mrt < 30°C.
+ - 0.5 m/s < sfcWind < 17.0 m/s.
+ wind_cap_min : bool
+ If True, wind velocities are capped to a minimum of 0.5 m/s following :cite:t:`brode_utci_2012` usage guidelines.
+ This ensures UTCI calculation for low winds. Default value False.
Returns
-------
@@ -1884,9 +1910,7 @@ def universal_thermal_climate_index(
This code was inspired by the `pythermalcomfort` and `thermofeel` packages.
- Notes
- -----
- See: http://www.utci.org/utcineu/utcineu.php
+ For more information: https://www.utci.org/
References
----------
@@ -1934,7 +1958,8 @@ def _fdir_ratio(
csza: xr.DataArray,
rsds: xr.DataArray,
) -> xr.DataArray:
- r"""Return ratio of direct solar radiation.
+ r"""
+ Return ratio of direct solar radiation.
The ratio of direct solar radiation is the fraction of the total horizontal solar irradiance
due to the direct beam of the sun.
@@ -1942,16 +1967,16 @@ def _fdir_ratio(
Parameters
----------
dates : xr.DataArray
- Series of dates and time of day
+ Series of dates and time of day.
csza : xr.DataArray
- Cosine of the solar zenith angle during the sunlit period of each interval or at an instant
+ Cosine of the solar zenith angle during the sunlit period of each interval or at an instant.
rsds : xr.DataArray
- Surface Downwelling Shortwave Radiation
+ Surface Downwelling Shortwave Radiation.
Returns
-------
xarray.DataArray, [dimensionless]
- Ratio of direct solar radiation
+ Ratio of direct solar radiation.
Notes
-----
@@ -1983,33 +2008,33 @@ def mean_radiant_temperature(
rlus: xr.DataArray,
stat: str = "sunlit",
) -> xr.DataArray:
- r"""Mean radiant temperature.
+ r"""
+ Mean radiant temperature.
The mean radiant temperature is the incidence of radiation on the body from all directions.
Parameters
----------
rsds : xr.DataArray
- Surface Downwelling Shortwave Radiation
+ Surface Downwelling Shortwave Radiation.
rsus : xr.DataArray
- Surface Upwelling Shortwave Radiation
+ Surface Upwelling Shortwave Radiation.
rlds : xr.DataArray
- Surface Downwelling Longwave Radiation
+ Surface Downwelling Longwave Radiation.
rlus : xr.DataArray
- Surface Upwelling Longwave Radiation
+ Surface Upwelling Longwave Radiation.
stat : {'instant', 'sunlit'}
- Which statistic to apply. If "instant", the instantaneous cosine
- of the solar zenith angle is calculated. If "sunlit", the cosine of the
- solar zenith angle is calculated during the sunlit period of each interval.
+ Which statistic to apply. If "instant", the instantaneous cosine of the solar zenith angle is calculated.
+ If "sunlit", the cosine of the solar zenith angle is calculated during the sunlit period of each interval.
Returns
-------
xarray.DataArray, [K]
- Mean radiant temperature
+ Mean Radiant Temperature.
Warnings
--------
- There are some issues in the calculation of mrt in polar regions.
+ There are some issues in the calculation of `mrt` in extreme polar regions.
Notes
-----
@@ -2089,28 +2114,34 @@ def wind_profile(
h_r: Quantified,
method: str = "power_law",
**kwds,
-):
- r"""Wind speed at a given height estimated from the wind speed at a reference height.
+) -> xr.DataArray:
+ r"""
+ Wind speed at a given height estimated from the wind speed at a reference height.
Estimate the wind speed based on a power law profile relating wind speed to height above the surface.
Parameters
----------
wind_speed : xarray.DataArray
- Wind speed at the reference height.
+ Wind Speed at the reference height.
h : Quantified
- Height at which to compute the wind speed.
+ Height at which to compute the Wind Speed.
h_r : Quantified
Reference height.
method : {"power_law"}
Method to use. Currently only "power_law" is implemented.
- kwds : dict
- Additional keyword arguments to pass to the method. For power_law, this is alpha, which takes a default value
+ **kwds : dict
+ Additional keyword arguments to pass to the method.For power_law, this is alpha, which takes a default value
of 1/7, but is highly variable based on topography, surface cover and atmospheric stability.
+ Returns
+ -------
+ xarray.DataArray
+ Wind Speed at the desired height.
+
Notes
-----
- The power law profile is given by
+ The power law profile is given by:
.. math::
@@ -2118,7 +2149,6 @@ def wind_profile(
where :math:`v_r` is the wind speed at the reference height, :math:`h` is the height at which the wind speed is
desired, and :math:`h_r` is the reference height.
-
"""
# Convert units to meters
h = convert_units_to(h, "m")
@@ -2146,7 +2176,8 @@ def wind_power_potential(
rated: Quantified = "13 m/s",
cut_out: Quantified = "25 m/s",
) -> xr.DataArray:
- r"""Wind power potential estimated from an idealized wind power production factor.
+ r"""
+ Wind power potential estimated from an idealized wind power production factor.
The actual power production of a wind farm can be estimated by multiplying its nominal (nameplate) capacity by the
wind power potential, which depends on wind speed at the hub height, the turbine specifications and air density.
@@ -2154,9 +2185,10 @@ def wind_power_potential(
Parameters
----------
wind_speed : xarray.DataArray
- Wind speed at the hub height. Use the `wind_profile` function to estimate from the surface wind speed.
- air_density: xarray.DataArray
- Air density at the hub height. Defaults to 1.225 kg/m³.
+ Wind Speed at the hub height.
+ Use the `wind_profile` function to estimate from the surface wind speed.
+ air_density : xarray.DataArray
+ Air Density at the hub height. Defaults to 1.225 kg/m³.
This is worth changing if applying in cold or mountainous regions with non-standard air density.
cut_in : Quantified
Cut-in wind speed. Default is 3.5 m/s.
@@ -2168,7 +2200,7 @@ def wind_power_potential(
Returns
-------
xr.DataArray
- The power production factor. Multiply by the nominal capacity to get the actual power production.
+ The power production factor. Multiply by the nominal capacity to get the actual power production.
See Also
--------
@@ -2206,7 +2238,6 @@ def wind_power_potential(
References
----------
:cite:cts:`chen_2020,tobin_2018`.
-
"""
# Convert units
cut_in = convert_units_to(cut_in, wind_speed)
diff --git a/xclim/indices/_hydrology.py b/xclim/indices/_hydrology.py
index 23d320cbc..e01377b1a 100644
--- a/xclim/indices/_hydrology.py
+++ b/xclim/indices/_hydrology.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Hydrological indice definitions."""
+
from __future__ import annotations
import numpy as np
@@ -28,7 +29,8 @@
@declare_units(q="[discharge]")
def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray:
- r"""Base flow index.
+ r"""
+ Base flow index.
Return the base flow index, defined as the minimum 7-day average flow divided by the mean flow.
@@ -53,13 +55,11 @@ def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray:
\frac{\min(\mathrm{CMA}_7(\mathbf{q}))}{\overline{\mathbf{q}}}
-
where :math:`\mathrm{CMA}_7` is the seven days moving average of the daily flow:
.. math::
\mathrm{CMA}_7(q_i) = \frac{\sum_{j=i-3}^{i+3} q_j}{7}
-
"""
m7 = q.rolling(time=7, center=True).mean(skipna=False).resample(time=freq)
mq = q.resample(time=freq)
@@ -72,7 +72,8 @@ def base_flow_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray:
@declare_units(q="[discharge]")
def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray:
- r"""Richards-Baker flashiness index.
+ r"""
+ Richards-Baker flashiness index.
Measures oscillations in flow relative to total flow, quantifying the frequency and rapidity of short term changes
in flow, based on :cite:t:`baker_new_2004`.
@@ -110,13 +111,14 @@ def rb_flashiness_index(q: xr.DataArray, freq: str = "YS") -> xr.DataArray:
@declare_units(snd="[length]")
def snd_max(snd: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
- """Maximum snow depth.
+ """
+ Maximum snow depth.
The maximum daily snow depth.
Parameters
----------
- snw : xarray.DataArray
+ snd : xarray.DataArray
Snow depth (mass per area).
freq : str
Resampling frequency.
@@ -131,7 +133,8 @@ def snd_max(snd: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
@declare_units(snd="[length]")
def snd_max_doy(snd: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
- """Maximum snow depth day of year.
+ """
+ Day of year of maximum snow depth.
Day of year when surface snow reaches its peak value. If snow depth is 0 over entire period, return NaN.
@@ -162,7 +165,8 @@ def snd_max_doy(snd: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
@declare_units(snw="[mass]/[area]")
def snw_max(snw: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
- """Maximum snow amount.
+ """
+ Maximum snow amount.
The maximum daily snow amount.
@@ -170,7 +174,7 @@ def snw_max(snw: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
----------
snw : xarray.DataArray
Snow amount (mass per area).
- freq: str
+ freq : str
Resampling frequency.
Returns
@@ -183,7 +187,8 @@ def snw_max(snw: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
@declare_units(snw="[mass]/[area]")
def snw_max_doy(snw: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
- """Maximum snow amount day of year.
+ """
+ Day of year of maximum snow amount.
Day of year when surface snow amount reaches its peak value. If snow amount is 0 over entire period, return NaN.
@@ -216,7 +221,8 @@ def snw_max_doy(snw: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray:
def snow_melt_we_max(
snw: xr.DataArray, window: int = 3, freq: str = "YS-JUL"
) -> xr.DataArray:
- """Maximum snow melt.
+ """
+ Maximum snow melt.
The maximum snow melt over a given number of days expressed in snow water equivalent.
@@ -250,7 +256,8 @@ def snow_melt_we_max(
def melt_and_precip_max(
snw: xr.DataArray, pr: xr.DataArray, window: int = 3, freq: str = "YS-JUL"
) -> xr.DataArray:
- """Maximum snow melt and precipitation.
+ """
+ Maximum snow melt and precipitation.
The maximum snow melt plus precipitation over a given number of days expressed in snow water equivalent.
@@ -262,7 +269,7 @@ def melt_and_precip_max(
Daily precipitation flux.
window : int
Number of days during which the water input is accumulated.
- freq: str
+ freq : str
Resampling frequency.
Returns
@@ -288,7 +295,7 @@ def melt_and_precip_max(
@declare_units(q="[discharge]")
def flow_index(q: xr.DataArray, p: float = 0.95) -> xr.DataArray:
"""
- Flow index
+ Flow index.
Calculate the pth percentile of daily streamflow normalized by the median flow.
diff --git a/xclim/indices/_multivariate.py b/xclim/indices/_multivariate.py
index 843604af4..8303e37e9 100644
--- a/xclim/indices/_multivariate.py
+++ b/xclim/indices/_multivariate.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Multivariate indice definitions."""
+
from __future__ import annotations
from collections.abc import Callable
@@ -74,7 +75,8 @@ def cold_spell_duration_index(
bootstrap: bool = False, # noqa # noqa
op: str = "<",
) -> xarray.DataArray:
- r"""Cold spell duration index.
+ r"""
+ Cold spell duration index.
Number of days with at least `window` consecutive days when the daily minimum temperature is below the
`tasmin_per` percentiles.
@@ -84,7 +86,7 @@ def cold_spell_duration_index(
tasmin : xarray.DataArray
Minimum daily temperature.
tasmin_per : xarray.DataArray
- nth percentile of daily minimum temperature with `dayofyear` coordinate.
+ The nth percentile of daily minimum temperature with `dayofyear` coordinate.
window : int
Minimum number of days with temperature below threshold to qualify as a cold spell.
freq : str
@@ -97,8 +99,8 @@ def cold_spell_duration_index(
Bootstrapping is only useful when the percentiles are computed on a part of the studied sample.
This period, common to percentiles and the sample must be bootstrapped to avoid inhomogeneities with
the rest of the time series.
- Keep bootstrap to False when there is no common period, it would give wrong results
- plus, bootstrapping is computationally expensive.
+ Keep bootstrap to `False` when there is no common period, as bootstrapping is computationally expensive,
+ and it might provide the wrong results.
op : {"<", "<=", "lt", "le"}
Comparison operation. Default: "<".
@@ -164,14 +166,15 @@ def cold_and_dry_days(
pr_per: xarray.DataArray,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Cold and dry days.
+ r"""
+ Cold and dry days.
Returns the total number of days when "Cold" and "Dry" conditions coincide.
Parameters
----------
tas : xarray.DataArray
- Mean daily temperature values
+ Mean daily temperature values.
pr : xarray.DataArray
Daily precipitation.
tas_per : xarray.DataArray
@@ -181,27 +184,26 @@ def cold_and_dry_days(
freq : str
Resampling frequency.
- Warnings
- --------
- Before computing the percentiles, all the precipitation below 1mm must be filtered out!
- Otherwise, the percentiles will include non-wet days.
-
Returns
-------
xarray.DataArray
The total number of days when cold and dry conditions coincide.
+ Warnings
+ --------
+ Before computing the percentiles, all the precipitation below 1mm must be filtered out!
+ Otherwise, the percentiles will include non-wet days.
+
Notes
-----
Bootstrapping is not available for quartiles because it would make no significant difference to bootstrap
percentiles so far from the extremes.
- Formula to be written (:cite:t:`beniston_trends_2009`)
+ Formula to be written (:cite:t:`beniston_trends_2009`).
References
----------
:cite:cts:`beniston_trends_2009`
-
"""
tas_per = convert_units_to(tas_per, tas)
thresh = resample_doy(tas_per, tas)
@@ -230,14 +232,15 @@ def warm_and_dry_days(
pr_per: xarray.DataArray,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Warm and dry days.
+ r"""
+ Warm and dry days.
Returns the total number of days when "warm" and "Dry" conditions coincide.
Parameters
----------
tas : xarray.DataArray
- Mean daily temperature values
+ Mean daily temperature values.
pr : xarray.DataArray
Daily precipitation.
tas_per : xarray.DataArray
@@ -247,27 +250,26 @@ def warm_and_dry_days(
freq : str
Resampling frequency.
- Warnings
- --------
- Before computing the percentiles, all the precipitation below 1mm must be filtered out!
- Otherwise, the percentiles will include non-wet days.
-
Returns
-------
xarray.DataArray,
The total number of days when warm and dry conditions coincide.
+ Warnings
+ --------
+ Before computing the percentiles, all the precipitation below 1mm must be filtered out!
+ Otherwise, the percentiles will include non-wet days.
+
Notes
-----
Bootstrapping is not available for quartiles because it would make no significant difference to bootstrap
percentiles so far from the extremes.
- Formula to be written (:cite:t:`beniston_trends_2009`)
+ Formula to be written (:cite:t:`beniston_trends_2009`).
References
----------
:cite:cts:`beniston_trends_2009`
-
"""
tas_per = convert_units_to(tas_per, tas)
thresh = resample_doy(tas_per, tas)
@@ -296,14 +298,15 @@ def warm_and_wet_days(
pr_per: xarray.DataArray,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Warm and wet days.
+ r"""
+ Warm and wet days.
Returns the total number of days when "warm" and "wet" conditions coincide.
Parameters
----------
tas : xarray.DataArray
- Mean daily temperature values
+ Mean daily temperature values.
pr : xarray.DataArray
Daily precipitation.
tas_per : xarray.DataArray
@@ -313,22 +316,22 @@ def warm_and_wet_days(
freq : str
Resampling frequency.
- Warnings
- --------
- Before computing the percentiles, all the precipitation below 1mm must be filtered out!
- Otherwise, the percentiles will include non-wet days.
-
Returns
-------
xarray.DataArray
The total number of days when warm and wet conditions coincide.
+ Warnings
+ --------
+ Before computing the percentiles, all the precipitation below 1mm must be filtered out!
+ Otherwise, the percentiles will include non-wet days.
+
Notes
-----
Bootstrapping is not available for quartiles because it would make no significant difference
to bootstrap percentiles so far from the extremes.
- Formula to be written (:cite:t:`beniston_trends_2009`)
+ Formula to be written (:cite:t:`beniston_trends_2009`).
References
----------
@@ -361,19 +364,15 @@ def cold_and_wet_days(
pr_per: xarray.DataArray,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Cold and wet days.
+ r"""
+ Cold and wet days.
Returns the total number of days when "cold" and "wet" conditions coincide.
- Warnings
- --------
- Before computing the percentiles, all the precipitation below 1mm must be filtered out!
- Otherwise, the percentiles will include non-wet days.
-
Parameters
----------
tas : xarray.DataArray
- Mean daily temperature values
+ Mean daily temperature values.
pr : xarray.DataArray
Daily precipitation.
tas_per : xarray.DataArray
@@ -388,12 +387,17 @@ def cold_and_wet_days(
xarray.DataArray
The total number of days when cold and wet conditions coincide.
+ Warnings
+ --------
+ Before computing the percentiles, all the precipitation below 1mm must be filtered out!
+ Otherwise, the percentiles will include non-wet days.
+
Notes
-----
Bootstrapping is not available for quartiles because it would make no significant
difference to bootstrap percentiles so far from the extremes.
- Formula to be written (:cite:t:`beniston_trends_2009`)
+ Formula to be written (:cite:t:`beniston_trends_2009`).
References
----------
@@ -431,7 +435,8 @@ def multiday_temperature_swing(
freq: str = "YS",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Statistics of consecutive diurnal temperature swing events.
+ r"""
+ Statistics of consecutive diurnal temperature swing events.
A diurnal swing of max and min temperature event is when Tmax > thresh_tasmax and Tmin <= thresh_tasmin. This indice
finds all days that constitute these events and computes statistics over the length and frequency of these events.
@@ -512,7 +517,8 @@ def daily_temperature_range(
freq: str = "YS",
op: str | Callable = "mean",
) -> xarray.DataArray:
- r"""Statistics of daily temperature range.
+ r"""
+ Statistics of daily temperature range.
The mean difference between the daily maximum temperature and the daily minimum temperature.
@@ -555,7 +561,8 @@ def daily_temperature_range(
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.
+ r"""
+ Mean absolute day-to-day variation in daily temperature range.
Mean absolute day-to-day variation in daily temperature range.
@@ -594,7 +601,8 @@ def daily_temperature_range_variability(
def extreme_temperature_range(
tasmin: xarray.DataArray, tasmax: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Extreme intra-period temperature range.
+ r"""
+ Extreme intra-period temperature range.
The maximum of max temperature (TXx) minus the minimum of min temperature (TNn) for the given time period.
@@ -647,7 +655,8 @@ def heat_wave_frequency(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Heat wave frequency.
+ r"""
+ Heat wave frequency.
Number of heat waves over a given period. A heat wave is defined as an event where the minimum and maximum daily
temperature both exceed specific thresholds over a minimum number of days.
@@ -725,7 +734,8 @@ def heat_wave_max_length(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Heat wave max length.
+ r"""
+ Heat wave max length.
Maximum length of heat waves over a given period. A heat wave is defined as an event where the minimum and maximum
daily temperature both exceeds specific thresholds over a minimum number of days.
@@ -805,7 +815,8 @@ def heat_wave_total_length(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Heat wave total length.
+ r"""
+ Heat wave total length.
Total length of heat waves over a given period. A heat wave is defined as an event where the minimum and maximum
daily temperature both exceeds specific thresholds over a minimum number of days.
@@ -825,7 +836,7 @@ def heat_wave_total_length(
Minimum number of days with temperatures above thresholds to qualify as a heatwave.
freq : str
Resampling frequency.
- op: {">", ">=", "gt", "ge"}
+ op : {">", ">=", "gt", "ge"}
Comparison operation. Default: ">".
resample_before_rl : bool
Determines if the resampling should take place before or after the run
@@ -871,7 +882,8 @@ def liquid_precip_ratio(
thresh: Quantified = "0 degC",
freq: str = "QS-DEC",
) -> xarray.DataArray:
- r"""Ratio of rainfall to total precipitation.
+ r"""
+ Ratio of rainfall to total precipitation.
The ratio of total liquid precipitation over the total precipitation. If solid precipitation is not provided,
it is approximated with pr, tas and thresh, using the `snowfall_approximation` function with method 'binary'.
@@ -894,6 +906,10 @@ def liquid_precip_ratio(
xarray.DataArray, [dimensionless]
Ratio of rainfall to total precipitation.
+ See Also
+ --------
+ winter_rain_ratio : The ratio of rainfall to total precipitation during winter.
+
Notes
-----
Let :math:`PR_i` be the mean daily precipitation of day :math:`i`, then for a period :math:`j` starting at
@@ -904,10 +920,6 @@ def liquid_precip_ratio(
PR_{ij} = \sum_{i=a}^{b} PR_i
PRwet_{ij}
-
- See Also
- --------
- winter_rain_ratio
"""
if prsn is None and tas is not None:
prsn = snowfall_approximation(pr, tas=tas, thresh=thresh, method="binary")
@@ -929,7 +941,8 @@ def precip_accumulation(
thresh: Quantified = "0 degC",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Accumulated total (liquid and/or solid) precipitation.
+ 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.
@@ -969,7 +982,7 @@ def precip_accumulation(
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.:
+ precipitation at the seasonal frequency, i.e. DJF, MAM, JJA, SON, DJF, etc.:
>>> from xclim.indices import precip_accumulation
>>> pr_day = xr.open_dataset(path_to_pr_file).pr
@@ -992,7 +1005,8 @@ def precip_average(
thresh: Quantified = "0 degC",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Averaged (liquid and/or solid) precipitation.
+ r"""
+ Averaged (liquid and/or solid) precipitation.
Resample the original daily mean precipitation flux and average over each period.
If a daily temperature is provided, the `phase` keyword can be used to average precipitation of a given phase only.
@@ -1032,7 +1046,7 @@ def precip_average(
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.:
+ precipitation at the seasonal frequency, i.e. DJF, MAM, JJA, SON, DJF, etc.:
>>> from xclim.indices import precip_average
>>> pr_day = xr.open_dataset(path_to_pr_file).pr
@@ -1055,7 +1069,8 @@ def rain_on_frozen_ground_days(
thresh: Quantified = "1 mm/d",
freq: str = "YS",
) -> xarray.DataArray:
- """Number of rain on frozen ground events.
+ """
+ Number of rain on frozen ground events.
Number of days with rain above a threshold after a series of seven days below freezing temperature.
Precipitation is assumed to be rain when the temperature is above 0℃.
@@ -1096,12 +1111,12 @@ def rain_on_frozen_ground_days(
t = convert_units_to(thresh, pr, context="hydro")
frz = convert_units_to("0 C", tas)
- def func(x, axis):
+ def _func(x, axis):
"""Check that temperature conditions are below 0 for seven days and above after."""
frozen = x == np.array([0, 0, 0, 0, 0, 0, 0, 1], bool)
return frozen.all(axis=axis)
- tcond = (tas > frz).rolling(time=8).reduce(func)
+ tcond = (tas > frz).rolling(time=8).reduce(_func)
pcond = pr > t
out = (tcond * pcond * 1).resample(time=freq).sum(dim="time")
@@ -1121,7 +1136,8 @@ def high_precip_low_temp(
tas_thresh: Quantified = "-0.2 degC",
freq: str = "YS",
) -> xarray.DataArray:
- """Number of days with precipitation above threshold and temperature below threshold.
+ """
+ Number of days with precipitation above threshold and temperature below threshold.
Number of days when precipitation is greater or equal to some threshold, and temperatures are colder than some
threshold. This can be used for example to identify days with the potential for freezing rain or icing conditions.
@@ -1144,8 +1160,8 @@ def high_precip_low_temp(
xarray.DataArray, [time]
Count of days with high precipitation and low temperatures.
- Example
- -------
+ Examples
+ --------
To compute the number of days with intense rainfall while minimum temperatures dip below -0.2C:
>>> pr = xr.open_dataset(path_to_pr_file).pr
>>> tasmin = xr.open_dataset(path_to_tasmin_file).tasmin
@@ -1171,7 +1187,8 @@ def days_over_precip_thresh(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Number of wet days with daily precipitation over a given percentile.
+ r"""
+ Number of wet days with daily precipitation over a given percentile.
Number of days over period where the precipitation is above a threshold defining wet days and above a given
percentile for that day.
@@ -1232,7 +1249,8 @@ def fraction_over_precip_thresh(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Fraction of precipitation due to wet days with daily precipitation over a given percentile.
+ r"""
+ Fraction of precipitation due to wet days with daily precipitation over a given percentile.
Percentage of the total precipitation over period occurring in days when the precipitation is above a threshold
defining wet days and above a given percentile for that day.
@@ -1262,7 +1280,6 @@ def fraction_over_precip_thresh(
-------
xarray.DataArray, [dimensionless]
Fraction of precipitation over threshold during wet days.
-
"""
pr_per = convert_units_to(pr_per, pr, context="hydro")
thresh = convert_units_to(thresh, pr, context="hydro")
@@ -1299,7 +1316,8 @@ def tg90p(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Number of days with daily mean temperature over the 90th percentile.
+ r"""
+ Number of days with daily mean temperature over the 90th percentile.
Number of days with daily mean temperature over the 90th percentile.
@@ -1357,7 +1375,8 @@ def tg10p(
bootstrap: bool = False, # noqa
op: str = "<",
) -> xarray.DataArray:
- r"""Number of days with daily mean temperature below the 10th percentile.
+ r"""
+ Number of days with daily mean temperature below the 10th percentile.
Number of days with daily mean temperature below the 10th percentile.
@@ -1415,7 +1434,8 @@ def tn90p(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Number of days with daily minimum temperature over the 90th percentile.
+ r"""
+ Number of days with daily minimum temperature over the 90th percentile.
Number of days with daily minimum temperature over the 90th percentile.
@@ -1473,7 +1493,8 @@ def tn10p(
bootstrap: bool = False, # noqa
op: str = "<",
) -> xarray.DataArray:
- r"""Number of days with daily minimum temperature below the 10th percentile.
+ r"""
+ Number of days with daily minimum temperature below the 10th percentile.
Number of days with daily minimum temperature below the 10th percentile.
@@ -1531,7 +1552,8 @@ def tx90p(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Number of days with daily maximum temperature over the 90th percentile.
+ r"""
+ Number of days with daily maximum temperature over the 90th percentile.
Number of days with daily maximum temperature over the 90th percentile.
@@ -1589,7 +1611,8 @@ def tx10p(
bootstrap: bool = False, # noqa
op: str = "<",
) -> xarray.DataArray:
- r"""Number of days with daily maximum temperature below the 10th percentile.
+ r"""
+ Number of days with daily maximum temperature below the 10th percentile.
Number of days with daily maximum temperature below the 10th percentile.
@@ -1652,7 +1675,8 @@ def tx_tn_days_above(
freq: str = "YS",
op: str = ">",
) -> xarray.DataArray:
- r"""Number of days with both hot maximum and minimum daily temperatures.
+ r"""
+ Number of days with both hot maximum and minimum daily temperatures.
The number of days per period with tasmin above a threshold and tasmax above another threshold.
@@ -1674,7 +1698,7 @@ def tx_tn_days_above(
Returns
-------
xarray.DataArray, [time]
- the number of days with tasmin > thresh_tasmin and tasmax > thresh_tasmax per period.
+ The number of days with tasmin > thresh_tasmin and tasmax > thresh_tasmax per period.
Notes
-----
@@ -1692,7 +1716,6 @@ def tx_tn_days_above(
.. math::
TN_{ij} > TN_{thresh} [℃]
-
"""
thresh_tasmax = convert_units_to(thresh_tasmax, tasmax)
thresh_tasmin = convert_units_to(thresh_tasmin, tasmin)
@@ -1717,7 +1740,8 @@ def warm_spell_duration_index(
bootstrap: bool = False, # noqa
op: str = ">",
) -> xarray.DataArray:
- r"""Warm spell duration index.
+ r"""
+ Warm spell duration index.
Number of days inside spells of a minimum number of consecutive days when the daily maximum temperature is above the
90th percentile. The 90th percentile should be computed for a 5-day moving window, centered on each calendar day in
@@ -1728,7 +1752,7 @@ def warm_spell_duration_index(
tasmax : xarray.DataArray
Maximum daily temperature.
tasmax_per : xarray.DataArray
- percentile(s) of daily maximum temperature.
+ Percentile(s) of daily maximum temperature.
window : int
Minimum number of days with temperature above threshold to qualify as a warm spell.
freq : str
@@ -1792,7 +1816,8 @@ def winter_rain_ratio(
tas: xarray.DataArray | None = None,
freq: str = "QS-DEC",
) -> xarray.DataArray:
- """Ratio of rainfall to total precipitation during winter.
+ """
+ Ratio of rainfall to total precipitation during winter.
The ratio of total liquid precipitation over the total precipitation over the winter months (DJF). If solid
precipitation is not provided, then precipitation is assumed solid if the temperature is below 0°C.
@@ -1830,7 +1855,8 @@ def blowing_snow(
window: int = 3,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- """Blowing snow days.
+ """
+ Blowing snow days.
Number of days when both snowfall over the last days and daily wind speeds are above respective thresholds.
@@ -1839,7 +1865,7 @@ def blowing_snow(
snd : xarray.DataArray
Surface snow depth.
sfcWind : xr.DataArray
- Wind velocity
+ Wind velocity.
snd_thresh : Quantified
Threshold on net snowfall accumulation over the last `window` days.
sfcWind_thresh : Quantified
@@ -1872,7 +1898,8 @@ def blowing_snow(
def water_cycle_intensity(
pr: xarray.DataArray, evspsbl: xarray.DataArray, freq="YS"
) -> xarray.DataArray:
- """Water cycle intensity.
+ """
+ Water cycle intensity.
The sum of precipitation and actual evapotranspiration.
@@ -1882,6 +1909,8 @@ def water_cycle_intensity(
Precipitation flux.
evspsbl : xarray.DataArray
Actual evapotranspiration flux.
+ freq : str
+ Resampling frequency.
Returns
-------
diff --git a/xclim/indices/_simple.py b/xclim/indices/_simple.py
index 431f99043..61a7e4312 100644
--- a/xclim/indices/_simple.py
+++ b/xclim/indices/_simple.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Simple indice definitions."""
+
from __future__ import annotations
import xarray
@@ -42,7 +43,8 @@
@declare_units(tas="[temperature]")
def tg_max(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Highest mean temperature.
+ r"""
+ Highest mean temperature.
The maximum of daily mean temperature.
@@ -72,7 +74,8 @@ def tg_max(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tas="[temperature]")
def tg_mean(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Mean of daily average temperature.
+ r"""
+ Mean of daily average temperature.
Resample the original daily mean temperature series by taking the mean over each period.
@@ -86,7 +89,7 @@ def tg_mean(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
Returns
-------
xarray.DataArray, [same units as tas]
- The mean daily temperature at the given time frequency
+ The mean daily temperature at the given time frequency.
Notes
-----
@@ -111,7 +114,8 @@ def tg_mean(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tas="[temperature]")
def tg_min(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Lowest mean temperature.
+ r"""
+ Lowest mean temperature.
Minimum of daily mean temperature.
@@ -141,7 +145,8 @@ def tg_min(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmin="[temperature]")
def tn_max(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Highest minimum temperature.
+ r"""
+ Highest minimum temperature.
The maximum of daily minimum temperature.
@@ -171,7 +176,8 @@ def tn_max(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmin="[temperature]")
def tn_mean(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Mean minimum temperature.
+ r"""
+ Mean minimum temperature.
Mean of daily minimum temperature.
@@ -201,7 +207,8 @@ def tn_mean(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmin="[temperature]")
def tn_min(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Lowest minimum temperature.
+ r"""
+ Lowest minimum temperature.
Minimum of daily minimum temperature.
@@ -231,7 +238,8 @@ def tn_min(tasmin: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmax="[temperature]")
def tx_max(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Highest max temperature.
+ r"""
+ Highest max temperature.
The maximum value of daily maximum temperature.
@@ -261,7 +269,8 @@ def tx_max(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmax="[temperature]")
def tx_mean(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Mean max temperature.
+ r"""
+ Mean max temperature.
The mean of daily maximum temperature.
@@ -291,7 +300,8 @@ def tx_mean(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
@declare_units(tasmax="[temperature]")
def tx_min(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
- r"""Lowest max temperature.
+ r"""
+ Lowest max temperature.
The minimum of daily maximum temperature.
@@ -323,7 +333,8 @@ def tx_min(tasmax: xarray.DataArray, freq: str = "YS") -> xarray.DataArray:
def frost_days(
tasmin: xarray.DataArray, thresh: Quantified = "0 degC", freq: str = "YS", **indexer
) -> xarray.DataArray:
- r"""Frost days index.
+ r"""
+ Frost days index.
Number of days where daily minimum temperatures are below a threshold temperature.
@@ -335,7 +346,7 @@ def frost_days(
Freezing temperature.
freq : str
Resampling frequency.
- indexer
+ **indexer : {dim: indexer}, optional
Indexing parameters to compute the frost days on a temporal subset of the data.
It accepts the same arguments as :py:func:`xclim.indices.generic.select_time`.
@@ -363,7 +374,8 @@ def frost_days(
def ice_days(
tasmax: xarray.DataArray, thresh: Quantified = "0 degC", freq: str = "YS"
) -> xarray.DataArray:
- r"""Number of ice/freezing days.
+ r"""
+ Number of ice/freezing days.
Number of days when daily maximum temperatures are below a threshold.
@@ -399,7 +411,8 @@ def ice_days(
def max_1day_precipitation_amount(
pr: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Highest 1-day precipitation amount for a period (frequency).
+ r"""
+ Highest 1-day precipitation amount for a period (frequency).
Resample the original daily total precipitation temperature series by taking the max over each period.
@@ -438,7 +451,8 @@ def max_1day_precipitation_amount(
def max_n_day_precipitation_amount(
pr: xarray.DataArray, window: int = 1, freq: str = "YS"
) -> xarray.DataArray:
- r"""Highest precipitation amount cumulated over a n-day moving window.
+ 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.
@@ -475,7 +489,8 @@ def max_n_day_precipitation_amount(
def max_pr_intensity(
pr: xarray.DataArray, window: int = 1, freq: str = "YS"
) -> xarray.DataArray:
- r"""Highest precipitation intensity over a n-hour moving window.
+ r"""
+ Highest precipitation intensity over a n-hour moving window.
Calculate the n-hour rolling average of the original hourly total precipitation series
and determine the maximum value over each period.
@@ -515,7 +530,8 @@ def snow_depth(
snd: xarray.DataArray,
freq: str = "YS",
) -> xarray.DataArray:
- """Mean of daily average snow depth.
+ """
+ Mean of daily average snow depth.
Resample the original daily mean snow depth series by taking the mean over each period.
@@ -529,7 +545,7 @@ def snow_depth(
Returns
-------
xarray.DataArray, [same units as snd]
- The mean daily snow depth at the given time frequency
+ The mean daily snow depth at the given time frequency.
"""
return snd.resample(time=freq).mean(dim="time").assign_attrs(units=snd.units)
@@ -538,7 +554,8 @@ def snow_depth(
def sfcWind_max( # noqa: N802
sfcWind: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Highest daily mean wind speed.
+ r"""
+ Highest daily mean wind speed.
The maximum of daily mean wind speed.
@@ -579,7 +596,8 @@ def sfcWind_max( # noqa: N802
def sfcWind_mean( # noqa: N802
sfcWind: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Mean of daily mean wind speed.
+ r"""
+ Mean of daily mean wind speed.
Resample the original daily mean wind speed series by taking the mean over each period.
@@ -593,7 +611,7 @@ def sfcWind_mean( # noqa: N802
Returns
-------
xarray.DataArray, [same units as sfcWind]
- The mean daily wind speed at the given time frequency
+ The mean daily wind speed at the given time frequency.
Notes
-----
@@ -622,7 +640,8 @@ def sfcWind_mean( # noqa: N802
def sfcWind_min( # noqa: N802
sfcWind: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Lowest daily mean wind speed.
+ r"""
+ Lowest daily mean wind speed.
The minimum of daily mean wind speed.
@@ -663,7 +682,8 @@ def sfcWind_min( # noqa: N802
def sfcWindmax_max( # noqa: N802
sfcWindmax: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Highest maximum wind speed.
+ r"""
+ Highest maximum wind speed.
The maximum of daily maximum wind speed.
@@ -707,7 +727,8 @@ def sfcWindmax_max( # noqa: N802
def sfcWindmax_mean( # noqa: N802
sfcWindmax: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Mean of daily maximum wind speed.
+ r"""
+ Mean of daily maximum wind speed.
Resample the original daily maximum wind speed series by taking the mean over each period.
@@ -721,7 +742,7 @@ def sfcWindmax_mean( # noqa: N802
Returns
-------
xarray.DataArray, [same units as sfcWindmax]
- The mean daily maximum wind speed at the given time frequency
+ The mean daily maximum wind speed at the given time frequency.
Notes
-----
@@ -751,7 +772,8 @@ def sfcWindmax_mean( # noqa: N802
def sfcWindmax_min( # noqa: N802
sfcWindmax: xarray.DataArray, freq: str = "YS"
) -> xarray.DataArray:
- r"""Lowest daily maximum wind speed.
+ r"""
+ Lowest daily maximum wind speed.
The minimum of daily maximum wind speed.
@@ -769,8 +791,8 @@ def sfcWindmax_min( # noqa: N802
Notes
-----
- Let :math:`FX_{ij}` be the maximum wind speed at day :math:`i` of period :math:`j`. Then the minimum
- daily maximum wind speed for period :math:`j` is:
+ Let :math:`FX_{ij}` be the maximum wind speed at day :math:`i` of period :math:`j`.
+ Then the minimum daily maximum wind speed for period :math:`j` is:
.. math::
diff --git a/xclim/indices/_synoptic.py b/xclim/indices/_synoptic.py
index a27beeb80..419f055bf 100644
--- a/xclim/indices/_synoptic.py
+++ b/xclim/indices/_synoptic.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Synoptic indice definitions."""
+
from __future__ import annotations
import cf_xarray # noqa: F401, pylint: disable=unused-import
@@ -23,16 +24,13 @@
def jetstream_metric_woollings(
ua: xarray.DataArray,
) -> tuple[xarray.DataArray, xarray.DataArray]:
- """Strength and latitude of jetstream.
+ """
+ Strength and latitude of jetstream.
Identify latitude and strength of maximum smoothed zonal wind speed in the region from 15 to 75°N and -60 to 0°E,
using the formula outlined in :cite:p:`woollings_variability_2010`. Wind is smoothened using a Lanczos filter
approach.
- Warnings
- --------
- This metric expects eastward wind component (u) to be on a regular grid (i.e. Plate Carree, 1D lat and lon)
-
Parameters
----------
ua : xarray.DataArray
@@ -43,6 +41,10 @@ def jetstream_metric_woollings(
(xarray.DataArray, xarray.DataArray)
Daily time series of latitude of jetstream and daily time series of strength of jetstream.
+ Warnings
+ --------
+ This metric expects eastward wind component (u) to be on a regular grid (i.e. Plate Carree, 1D lat and lon)
+
References
----------
:cite:cts:`woollings_variability_2010`
diff --git a/xclim/indices/_threshold.py b/xclim/indices/_threshold.py
index dba665096..13420027b 100644
--- a/xclim/indices/_threshold.py
+++ b/xclim/indices/_threshold.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Threshold indice definitions."""
+
from __future__ import annotations
import warnings
@@ -113,7 +114,8 @@
def calm_days(
sfcWind: xarray.DataArray, thresh: Quantified = "2 m s-1", freq: str = "MS"
) -> xarray.DataArray:
- r"""Calm days.
+ r"""
+ Calm days.
The number of days with average near-surface wind speed below threshold (default: 2 m/s).
@@ -154,7 +156,8 @@ def cold_spell_days(
op: str = "<",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Cold spell days.
+ r"""
+ Cold spell days.
The number of days that are part of cold spell events, defined as a sequence of consecutive days with mean daily
temperature below a threshold (default: -10°C).
@@ -213,10 +216,10 @@ def cold_spell_frequency(
op: str = "<",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Cold spell frequency.
+ r"""
+ Cold spell frequency.
- The number of cold spell events, defined as a sequence of consecutive {window} days
- with mean daily temperature below a {thresh}.
+ The number of cold spell events, defined as a sequence of consecutive {window} days with mean daily temperature below a {thresh}.
Parameters
----------
@@ -231,13 +234,12 @@ def cold_spell_frequency(
op : {"<", "<=", "lt", "le"}
Comparison operation. Default: "<".
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
+ Determines if the resampling should take place before or after the run.
Returns
-------
xarray.DataArray, [unitless]
The {freq} number of cold periods of minimum {window} days.
-
"""
t = convert_units_to(thresh, tas)
over = compare(tas, op, t, constrain=("<", "<="))
@@ -262,7 +264,8 @@ def cold_spell_max_length(
op: str = "<",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Longest cold spell.
+ r"""
+ Longest cold spell.
Longest spell of low temperatures over a given period.
Longest series of at least {window} consecutive days with temperature at or below {thresh}.
@@ -311,7 +314,8 @@ def cold_spell_total_length(
op: str = "<",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Total length of cold spells.
+ r"""
+ Total length of cold spells.
Total length of spells of low temperatures over a given period.
Total length of series of at least {window} consecutive days with temperature at or below {thresh}.
@@ -357,7 +361,8 @@ def snd_season_end(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover end date (depth).
+ r"""
+ Snow cover end date (depth).
First day after the start of the continuous snow depth cover when snow depth is below a threshold
for at least `window` consecutive days.
@@ -395,7 +400,8 @@ def snw_season_end(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover end date (amount).
+ r"""
+ Snow cover end date (amount).
First day after the start of the continuous snow water cover
when snow water is below a threshold for at least `N` consecutive days.
@@ -433,7 +439,8 @@ def snd_season_start(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover start date (depth).
+ r"""
+ Snow cover start date (depth).
Day of year when snow depth is above or equal to a threshold
for at least `N` consecutive days.
@@ -471,10 +478,10 @@ def snw_season_start(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover start date (amount).
+ r"""
+ Snow cover start date (amount).
- Day of year when snow water is above or equal to a threshold
- for at least `N` consecutive days.
+ Day of year when snow water is above or equal to a threshold for at least `N` consecutive days.
Parameters
----------
@@ -495,7 +502,6 @@ def snw_season_start(
References
----------
:cite:cts:`chaumont_elaboration_2017`
-
"""
valid = at_least_n_valid(snw.where(snw > 0), n=1, freq=freq)
out = season(snw, thresh, window=window, op=">=", stat="start", freq=freq)
@@ -510,7 +516,8 @@ def snd_season_length(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover duration (depth).
+ r"""
+ Snow cover duration (depth).
The season starts when snow depth is above a threshold for at least `N` consecutive days
and stops when it drops below the same threshold for the same number of days.
@@ -548,7 +555,8 @@ def snw_season_length(
window: int = 14,
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Snow cover duration (amount).
+ r"""
+ Snow cover duration (amount).
The season starts when the snow amount is above a threshold for at least `N` consecutive days
and stops when it drops below the same threshold for the same number of days.
@@ -583,14 +591,11 @@ def snw_season_length(
def snd_storm_days(
snd: xarray.DataArray, thresh: Quantified = "25 cm", freq: str = "YS-JUL"
) -> xarray.DataArray:
- """Days with snowfall over threshold.
+ """
+ Days with snowfall over threshold.
Number of days with snowfall depth accumulation greater or equal to threshold (default: 25 cm).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
snd : xarray.DataArray
@@ -605,6 +610,10 @@ def snd_storm_days(
xarray.DataArray
Number of days per period identified as winter storms.
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
+
Notes
-----
Snowfall accumulation is estimated by the change in snow depth.
@@ -624,14 +633,11 @@ def snd_storm_days(
def snw_storm_days(
snw: xarray.DataArray, thresh: Quantified = "10 kg m-2", freq: str = "YS-JUL"
) -> xarray.DataArray:
- """Days with snowfall over threshold.
+ """
+ Days with snowfall over threshold.
Number of days with snowfall amount accumulation greater or equal to threshold (default: 10 kg m-2).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
snw : xarray.DataArray
@@ -646,6 +652,10 @@ def snw_storm_days(
xarray.DataArray
Number of days per period identified as winter storms.
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
+
Notes
-----
Snowfall accumulation is estimated by the change in snow amount.
@@ -668,7 +678,8 @@ def daily_pr_intensity(
freq: str = "YS",
op: str = ">=",
) -> xarray.DataArray:
- r"""Average daily precipitation intensity.
+ r"""
+ Average daily precipitation intensity.
Return the average precipitation over wet days.
Wet days are those with precipitation over a given threshold (default: 1 mm/day).
@@ -745,7 +756,8 @@ def dry_days(
freq: str = "YS",
op: str = "<",
) -> xarray.DataArray:
- r"""Dry days.
+ r"""
+ Dry days.
The number of days with daily precipitation below threshold.
@@ -787,7 +799,8 @@ def maximum_consecutive_wet_days(
freq: str = "YS",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Consecutive wet days.
+ r"""
+ Consecutive wet days.
Returns the maximum number of consecutive days with precipitation above a given threshold (default: 1 mm/day).
@@ -838,7 +851,8 @@ def maximum_consecutive_wet_days(
def cooling_degree_days(
tas: xarray.DataArray, thresh: Quantified = "18 degC", freq: str = "YS"
) -> xarray.DataArray:
- r"""Cooling degree days.
+ r"""
+ Cooling degree days.
Returns the sum of degree days above the temperature threshold at which spaces are cooled (default: 18℃).
@@ -875,7 +889,8 @@ def cooling_degree_days(
def growing_degree_days(
tas: xarray.DataArray, thresh: Quantified = "4.0 degC", freq: str = "YS"
) -> xarray.DataArray:
- r"""Growing degree-days over threshold temperature value.
+ r"""
+ Growing degree-days over threshold temperature value.
The sum of growing degree-days over a given mean daily temperature threshold (default: 4℃).
@@ -915,17 +930,14 @@ def growing_season_start(
freq: str = "YS",
op: Literal[">", ">=", "gt", "ge"] = ">=",
) -> xarray.DataArray:
- r"""Start of the growing season.
+ r"""
+ Start of the growing season.
The growing season starts with the first sequence of a minimum length of consecutive days above the threshold
and ends with the first sequence of the same minimum length of consecutive days under the threshold. Sequences
- of consecutive days under the threshold shorter then `window` are allowed within the season.
+ of consecutive days under the threshold shorter than `window` are allowed within the season.
A middle date can be given, a start can't happen later and an end can't happen earlier.
- Warnings
- --------
- The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
-
Parameters
----------
tas : xarray.DataArray
@@ -946,6 +958,10 @@ def growing_season_start(
-------
xarray.DataArray, [dimensionless]
Start of the growing season.
+
+ Warnings
+ --------
+ The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
"""
return season(
tas,
@@ -968,17 +984,14 @@ def growing_season_end(
freq: str = "YS",
op: Literal[">", ">=", "lt", "le"] = ">",
) -> xarray.DataArray:
- r"""End of the growing season.
+ r"""
+ End of the growing season.
The growing season starts with the first sequence of a minimum length of consecutive days above the threshold
and ends with the first sequence of the same minimum length of consecutive days under the threshold. Sequences
- of consecutive days under the threshold shorter then `window` are allowed within the season.
+ of consecutive days under the threshold shorter than `window` are allowed within the season.
A middle date can be given, a start can't happen later and an end can't happen earlier.
- Warnings
- --------
- The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
-
Parameters
----------
tas : xarray.DataArray
@@ -1001,6 +1014,10 @@ def growing_season_end(
xarray.DataArray, [dimensionless]
End of the growing season.
+ Warnings
+ --------
+ The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
+
Notes
-----
Let :math:`x_i` be the daily mean temperature at day of the year :math:`i` for values of :math:`i` going from 1
@@ -1034,19 +1051,16 @@ def growing_season_length(
freq: str = "YS",
op: str = ">=",
) -> xarray.DataArray:
- r"""Growing season length.
+ r"""
+ Growing season length.
The growing season starts with the first sequence of a minimum length of consecutive days above the threshold
- and ends with the first sequence of the same minimum length of consecutive days under the threshold. Sequences
- of consecutive days under the threshold shorter then `window` are allowed within the season.
+ and ends with the first sequence of the same minimum length of consecutive days under the threshold.
+ Sequences of consecutive days under the threshold shorter than `window` are allowed within the season.
A middle date can be given, a start can't happen later and an end can't happen earlier.
If the season starts but never ends, the length is computed up to the end of the resampling period.
If no season start is found, but the data is valid, a length of 0 is returned.
- Warnings
- --------
- The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
-
Parameters
----------
tas : xarray.DataArray
@@ -1057,7 +1071,7 @@ def growing_season_length(
Minimum number of days with temperature above threshold to mark the beginning and end of growing season.
mid_date : str, optional
Date of the year before which the season must start and after which it can end. Should have the format '%m-%d'.
- ``None`` removes that constraint.
+ Setting `None` removes that constraint.
freq : str
Resampling frequency.
op : {">", ">=", "gt", "ge"}
@@ -1068,6 +1082,10 @@ def growing_season_length(
xarray.DataArray, [time]
Growing season length.
+ Warnings
+ --------
+ The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
+
Notes
-----
Let :math:`TG_{ij}` be the mean temperature at day :math:`i` of period :math:`j`. Then counted is
@@ -1083,6 +1101,10 @@ def growing_season_length(
TG_{ij} < 5 ℃
+ References
+ ----------
+ :cite:cts:`project_team_eca&d_algorithm_2013`
+
Examples
--------
>>> from xclim.indices import growing_season_length
@@ -1095,11 +1117,6 @@ def growing_season_length(
If working in the Southern Hemisphere, one can use:
>>> gsl_sh = growing_season_length(tas, mid_date="01-01", freq="YS-JUL")
-
- References
- ----------
- :cite:cts:`project_team_eca&d_algorithm_2013`
-
"""
return season(
tas,
@@ -1122,17 +1139,14 @@ def frost_season_length(
freq: str = "YS-JUL",
op: str = "<",
) -> xarray.DataArray:
- r"""Frost season length.
+ r"""
+ Frost season length.
The number of days between the first occurrence of at least `N` (default: 5) consecutive days with minimum daily
temperature under a threshold (default: 0℃) and the first occurrence of at least `N` consecutive days with
minimum daily temperature above the same threshold.
A mid-date can be given to limit the earliest day the end of season can take.
- Warnings
- --------
- The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
-
Parameters
----------
tasmin : xarray.DataArray
@@ -1154,6 +1168,10 @@ def frost_season_length(
xarray.DataArray, [time]
Frost season length.
+ Warnings
+ --------
+ The default `freq` and `mid_date` parameters are valid for the northern hemisphere.
+
Notes
-----
Let :math:`TN_{ij}` be the minimum temperature at day :math:`i` of period :math:`j`. Then counted is
@@ -1203,7 +1221,8 @@ def frost_free_season_start(
op: str = ">=",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Start of the frost free season.
+ r"""
+ Start of the frost free season.
The frost free season starts when a sequence of `window` consecutive days are above the threshold
and ends when a sequence of consecutive days of the same length are under the threshold. Sequences
@@ -1264,7 +1283,8 @@ def frost_free_season_end(
op: str = ">=",
freq: str = "YS",
) -> xarray.DataArray:
- r"""End of the frost free season.
+ r"""
+ End of the frost free season.
The frost free season starts when a sequence of `window` consecutive days are above the threshold
and ends when a sequence of consecutive days of the same length are under the threshold. Sequences
@@ -1331,7 +1351,8 @@ def frost_free_season_length(
op: str = ">=",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Length of the frost free season.
+ r"""
+ Length of the frost free season.
The frost free season starts when a sequence of `window` consecutive days are above the threshold
and ends when a sequence of consecutive days of the same length are under the threshold. Sequences
@@ -1411,7 +1432,8 @@ def frost_free_spell_max_length(
op: str = ">=",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Longest frost free spell.
+ r"""
+ Longest frost free spell.
Longest spell of warm temperatures over a given period.
Longest series of at least {window} consecutive days with temperature at or above the threshold.
@@ -1460,14 +1482,11 @@ def last_spring_frost(
window: int = 1,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Last day of temperatures inferior to a threshold temperature.
+ r"""
+ Last day of temperatures inferior to a threshold temperature.
Returns last day of period where minimum temperature is inferior to a threshold over a given number of days
- (default: 1) and limited to a final calendar date (default: July 1).
-
- Warnings
- --------
- The default `freq` and `before_date` parameters are valid for the northern hemisphere.
+ (default: 1) and limited to a final calendar date (default: July 1st).
Parameters
----------
@@ -1489,6 +1508,10 @@ def last_spring_frost(
xarray.DataArray, [dimensionless]
Day of the year when temperature is inferior to a threshold over a given number of days for the first time.
If there is no such day, returns np.nan.
+
+ Warnings
+ --------
+ The default `freq` and `before_date` parameters are valid for the northern hemisphere.
"""
thresh = convert_units_to(thresh, tasmin)
cond = compare(tasmin, op, thresh, constrain=("<", "<="))
@@ -1518,14 +1541,11 @@ def first_day_temperature_below(
window: int = 1,
freq: str = "YS",
) -> xarray.DataArray:
- r"""First day of temperatures inferior to a given temperature threshold.
+ r"""
+ First day of temperatures inferior to a given temperature threshold.
Returns first day of period where temperature is inferior to a threshold over a given number of days (default: 1),
- limited to a starting calendar date (default: July 1).
-
- Warnings
- --------
- The default `freq` and `after_date` parameters are valid for the northern hemisphere.
+ limited to a starting calendar date (default: July 1st).
Parameters
----------
@@ -1547,6 +1567,10 @@ def first_day_temperature_below(
xarray.DataArray, [dimensionless]
Day of the year when temperature is inferior to a threshold over a given number of days for the first time.
If there is no such day, returns np.nan.
+
+ Warnings
+ --------
+ The default `freq` and `after_date` parameters are valid for the northern hemisphere.
"""
# noqa
@@ -1571,14 +1595,11 @@ def first_day_temperature_above(
window: int = 1,
freq: str = "YS",
) -> xarray.DataArray:
- r"""First day of temperatures superior to a given temperature threshold.
+ r"""
+ First day of temperatures superior to a given temperature threshold.
Returns first day of period where temperature is superior to a threshold over a given number of days (default: 1),
- limited to a starting calendar date (default: January 1).
-
- Warnings
- --------
- The default `freq` and `after_date` parameters are valid for the northern hemisphere.
+ limited to a starting calendar date (default: January 1st).
Parameters
----------
@@ -1601,6 +1622,10 @@ def first_day_temperature_above(
Day of the year when temperature is superior to a threshold over a given number of days for the first time.
If there is no such day, returns np.nan.
+ Warnings
+ --------
+ The default `freq` and `after_date` parameters are valid for the northern hemisphere.
+
Notes
-----
Let :math:`x_i` be the daily mean|max|min temperature at day of the year :math:`i` for values of :math:`i` going
@@ -1631,20 +1656,17 @@ def first_snowfall(
thresh: Quantified = "1 mm/day",
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""First day with snowfall rate above a threshold.
+ r"""
+ First day with snowfall rate above a threshold.
Returns the first day of a period where snowfall exceeds a threshold (default: 1 mm/day).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
prsn : xarray.DataArray
Snowfall flux.
thresh : Quantified
- Threshold snowfall flux or liquid water equivalent snowfall rate. (default: 1 mm/day)
+ Threshold snowfall flux or liquid water equivalent snowfall rate. (default: 1 mm/day).
freq : str
Resampling frequency.
@@ -1654,9 +1676,9 @@ def first_snowfall(
Last day of the year where snowfall is superior to a threshold.
If there is no such day, returns np.nan.
- References
- ----------
- :cite:cts:`cbcl_climate_2020`.
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
Notes
-----
@@ -1665,6 +1687,10 @@ def first_snowfall(
If threshold and prsn differ by a density (i.e. [length/time] vs. [mass/area/time]), a liquid water equivalent
snowfall rate is assumed and the threshold is converted using a 1000 kg m-3 density.
+
+ References
+ ----------
+ :cite:cts:`cbcl_climate_2020`.
"""
thresh = convert_units_to(thresh, prsn, context="hydro")
cond = prsn >= thresh
@@ -1686,14 +1712,11 @@ def last_snowfall(
thresh: Quantified = "1 mm/day",
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Last day with snowfall above a threshold.
+ r"""
+ Last day with snowfall above a threshold.
Returns the last day of a period where snowfall exceeds a threshold (default: 1 mm/day)
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
prsn : xarray.DataArray
@@ -1709,10 +1732,9 @@ def last_snowfall(
Last day of the year where snowfall is superior to a threshold.
If there is no such day, returns np.nan.
-
- References
- ----------
- :cite:cts:`cbcl_climate_2020`.
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
Notes
-----
@@ -1721,6 +1743,10 @@ def last_snowfall(
If threshold and prsn differ by a density (i.e. [length/time] vs. [mass/area/time]), a liquid water equivalent
snowfall rate is assumed and the threshold is converted using a 1000 kg m-3 density.
+
+ References
+ ----------
+ :cite:cts:`cbcl_climate_2020`.
"""
thresh = convert_units_to(thresh, prsn, context="hydro")
cond = prsn >= thresh
@@ -1747,18 +1773,15 @@ def days_with_snow(
high: Quantified = "1E6 kg m-2 s-1",
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Days with snow.
+ r"""
+ Days with snow.
Return the number of days where snowfall is within low and high thresholds.
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
prsn : xarray.DataArray
- Snowfall flux
+ Snowfall flux.
low : Quantified
Minimum threshold snowfall flux or liquid water equivalent snowfall rate.
high : Quantified
@@ -1771,14 +1794,18 @@ def days_with_snow(
xarray.DataArray, [days]
Number of days where snowfall is between low and high thresholds.
- References
- ----------
- :cite:cts:`matthews_planning_2017`
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
Notes
-----
If threshold and prsn differ by a density (i.e. [length/time] vs. [mass/area/time]), a liquid water equivalent
snowfall rate is assumed and the threshold is converted using a 1000 kg m-3 density.
+
+ References
+ ----------
+ :cite:cts:`matthews_planning_2017`.
"""
low = convert_units_to(low, prsn, context="hydro")
high = convert_units_to(high, prsn, context="hydro")
@@ -1792,14 +1819,11 @@ def snowfall_frequency(
thresh: Quantified = "1 mm/day",
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Percentage of snow days.
+ r"""
+ Percentage of snow days.
Return the percentage of days where snowfall exceeds a threshold (default: 1 mm/day).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
prsn : xarray.DataArray
@@ -1814,9 +1838,9 @@ def snowfall_frequency(
xarray.DataArray, [%]
Percentage of days where snowfall exceeds a threshold.
- References
- ----------
- :cite:cts:`frei_snowfall_2018`
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
Notes
-----
@@ -1825,6 +1849,10 @@ def snowfall_frequency(
If threshold and prsn differ by a density (i.e. [length/time] vs. [mass/area/time]), a liquid water equivalent
snowfall rate is assumed and the threshold is converted using a 1000 kg m-3 density.
+
+ References
+ ----------
+ :cite:cts:`frei_snowfall_2018`.
"""
# High threshold here just needs to be a big value. It is converted to same units as
# so that a warning message won't be triggered just because of this value
@@ -1847,14 +1875,11 @@ def snowfall_intensity(
thresh: Quantified = "1 mm/day",
freq: str = "YS-JUL",
) -> xarray.DataArray:
- r"""Mean daily snowfall rate during snow days.
+ r"""
+ Mean daily snowfall rate during snow days.
Return mean daily snowfall rate during days where snowfall exceeds a threshold (default: 1 mm/day).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
prsn : xarray.DataArray
@@ -1869,9 +1894,9 @@ def snowfall_intensity(
xarray.DataArray,
Mean daily liquid water equivalent snowfall rate during days where snowfall exceeds a threshold.
- References
- ----------
- :cite:cts:`frei_snowfall_2018`
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
Notes
-----
@@ -1880,6 +1905,10 @@ def snowfall_intensity(
If threshold and prsn differ by a density (i.e. [length/time] vs. [mass/area/time]), a liquid water equivalent
snowfall rate is assumed and the threshold is converted using a 1000 kg m-3 density.
+
+ References
+ ----------
+ :cite:cts:`frei_snowfall_2018`.
"""
thresh = convert_units_to(thresh, "mm/day", context="hydro")
lwe_prsn = convert_units_to(prsn, "mm/day", context="hydro")
@@ -1900,7 +1929,8 @@ def heat_wave_index(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- """Heat wave index.
+ """
+ Heat wave index.
Number of days that are part of a heatwave, defined as five or more consecutive days over a threshold of 25℃.
@@ -1946,7 +1976,8 @@ def hot_spell_max_magnitude(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- """Hot spell maximum magnitude.
+ """
+ Hot spell maximum magnitude.
Magnitude of the most intensive heat wave event as sum of differences between tasmax
and the given threshold for Heat Wave days, defined as three or more consecutive days
@@ -1968,14 +1999,14 @@ def hot_spell_max_magnitude(
Determines if the resampling should take place before or after the run
length encoding (or a similar algorithm) is applied to runs.
- References
- ----------
- :cite:cts:`russo_magnitude_2014,zhang_high_2022`
-
Returns
-------
DataArray, [time]
Hot spell maximum magnitude.
+
+ References
+ ----------
+ :cite:cts:`russo_magnitude_2014,zhang_high_2022`.
"""
thresh = convert_units_to(thresh, tasmax)
over_values = (tasmax - thresh).clip(0)
@@ -1996,7 +2027,8 @@ def heating_degree_days(
thresh: Quantified = "17.0 degC",
freq: str = "YS",
) -> xarray.DataArray:
- r"""Heating degree days.
+ r"""
+ Heating degree days.
Sum of degree days below the temperature threshold (default: 17℃) at which spaces are heated.
@@ -2040,7 +2072,8 @@ def hot_spell_max_length(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Longest hot spell.
+ r"""
+ Longest hot spell.
Longest spell of high temperatures over a given period.
Longest series of at least {window} consecutive days with temperature at or above {thresh}.
@@ -2102,7 +2135,8 @@ def hot_spell_total_length(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Total length of hot spells.
+ r"""
+ Total length of hot spells.
Total length of spells of high temperatures over a given period.
Total length of series of at least {window} consecutive days with temperature at or above {thresh}.
@@ -2160,7 +2194,8 @@ def hot_spell_frequency(
op: str = ">",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- """Hot spell frequency.
+ """
+ Hot spell frequency.
The number of hot spell events, defined as a sequence of consecutive {window} days
with mean daily temperature above a {thresh}.
@@ -2178,7 +2213,7 @@ def hot_spell_frequency(
op : {">", ">=", "gt", "ge"}
Comparison operation. Default: ">".
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
+ Determines if the resampling should take place before or after the run.
Returns
-------
@@ -2220,7 +2255,8 @@ def snd_days_above(
freq: str = "YS-JUL",
op: str = ">=",
) -> xarray.DataArray:
- """The number of days with snow depth above a threshold.
+ """
+ The number of days with snow depth above a threshold.
Number of days where surface snow depth is greater or equal to given threshold (default: 2 cm).
@@ -2253,7 +2289,8 @@ def snw_days_above(
freq: str = "YS-JUL",
op: str = ">=",
) -> xarray.DataArray:
- """The number of days with snow amount above a threshold.
+ """
+ The number of days with snow amount above a threshold.
Number of days where surface snow amount is greater or equal to given threshold.
@@ -2272,7 +2309,6 @@ def snw_days_above(
-------
xarray.DataArray, [time]
Number of days where snow amount is greater than or equal to {thresh}.
-
"""
valid = at_least_n_valid(snw, n=1, freq=freq)
thresh = convert_units_to(thresh, snw)
@@ -2287,7 +2323,8 @@ def tn_days_above(
freq: str = "YS",
op: str = ">",
):
- """The number of days with tasmin above a threshold (number of tropical nights).
+ """
+ The number of days with tasmin above a threshold (number of tropical nights).
Number of days where minimum daily temperature exceeds a threshold (default: 20℃).
@@ -2328,7 +2365,8 @@ def tn_days_below(
freq: str = "YS",
op: str = "<",
) -> xarray.DataArray:
- """Number of days with tasmin below a threshold.
+ """
+ Number of days with tasmin below a threshold.
Number of days where minimum daily temperature is below a threshold (default: -10℃).
@@ -2369,7 +2407,8 @@ def tg_days_above(
freq: str = "YS",
op: str = ">",
):
- """The number of days with tas above a threshold.
+ """
+ The number of days with tas above a threshold.
Number of days where mean daily temperature exceeds a threshold (default: 10℃).
@@ -2410,7 +2449,8 @@ def tg_days_below(
freq: str = "YS",
op: str = "<",
):
- """The number of days with tas below a threshold.
+ """
+ The number of days with tas below a threshold.
Number of days where mean daily temperature is below a threshold (default: 10℃).
@@ -2451,7 +2491,8 @@ def tx_days_above(
freq: str = "YS",
op: str = ">",
) -> xarray.DataArray:
- """The number of days with tasmax above a threshold (number of summer days).
+ """
+ The number of days with tasmax above a threshold (number of summer days).
Number of days where maximum daily temperature exceeds a threshold (default: 25℃).
@@ -2492,7 +2533,8 @@ def tx_days_below(
freq: str = "YS",
op: str = "<",
):
- """The number of days with tmax below a threshold.
+ """
+ The number of days with tmax below a threshold.
Number of days where maximum daily temperature is below a threshold (default: 25℃).
@@ -2533,7 +2575,8 @@ def warm_day_frequency(
freq: str = "YS",
op: str = ">",
) -> xarray.DataArray:
- """Frequency of extreme warm days.
+ """
+ Frequency of extreme warm days.
Return the number of days with maximum daily temperature exceeding threshold (default: 30℃) per period.
@@ -2574,7 +2617,8 @@ def warm_night_frequency(
freq: str = "YS",
op: str = ">",
) -> xarray.DataArray:
- """Frequency of extreme warm nights.
+ """
+ Frequency of extreme warm nights.
Return the number of days with minimum daily temperature exceeding threshold (default: 22℃) per period.
@@ -2606,7 +2650,8 @@ def wetdays(
freq: str = "YS",
op: str = ">=",
) -> xarray.DataArray:
- """Wet days.
+ """
+ Wet days.
Return the total number of days during period with precipitation over threshold (default: 1.0 mm/day).
@@ -2648,7 +2693,8 @@ def wetdays_prop(
freq: str = "YS",
op: str = ">=",
) -> xarray.DataArray:
- """Proportion of wet days.
+ """
+ Proportion of wet days.
Return the proportion of days during period with precipitation over threshold (default: 1.0 mm/day).
@@ -2691,15 +2737,12 @@ def maximum_consecutive_frost_days(
freq: str = "YS-JUL",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Maximum number of consecutive frost days (Tn < 0℃).
+ r"""
+ Maximum number of consecutive frost days (Tn < 0℃).
The maximum number of consecutive days within the period where the minimum daily temperature
is under a given threshold (default: 0°C).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
tasmin : xarray.DataArray
@@ -2717,6 +2760,10 @@ def maximum_consecutive_frost_days(
xarray.DataArray, [time]
The maximum number of consecutive frost days (tasmin < threshold per period).
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
+
Notes
-----
Let :math:`\mathbf{t}=t_0, t_1, \ldots, t_n` be a minimum daily temperature series and :math:`thresh` the threshold
@@ -2749,7 +2796,8 @@ def maximum_consecutive_dry_days(
freq: str = "YS",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Maximum number of consecutive dry days.
+ r"""
+ Maximum number of consecutive dry days.
Return the maximum number of consecutive days within the period where precipitation
is below a certain threshold (default: 1 mm/day).
@@ -2804,15 +2852,12 @@ def maximum_consecutive_frost_free_days(
freq: str = "YS",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Maximum number of consecutive frost free days (Tn >= 0℃).
+ r"""
+ Maximum number of consecutive frost free days (Tn >= 0℃).
Return the maximum number of consecutive days within the period where the minimum daily temperature is
above or equal to a certain threshold (default: 0℃).
- Warnings
- --------
- The default `freq` is valid for the northern hemisphere.
-
Parameters
----------
tasmin : xarray.DataArray
@@ -2830,6 +2875,10 @@ def maximum_consecutive_frost_free_days(
xarray.DataArray, [time]
The maximum number of consecutive frost free days (tasmin >= threshold per period).
+ Warnings
+ --------
+ The default `freq` is valid for the northern hemisphere.
+
Notes
-----
Let :math:`\mathbf{t}=t_0, t_1, \ldots, t_n` be a daily minimum temperature series and :math:`thresh` the threshold
@@ -2862,7 +2911,8 @@ def maximum_consecutive_tx_days(
freq: str = "YS",
resample_before_rl: bool = True,
) -> xarray.DataArray:
- r"""Maximum number of consecutive days with tasmax above a threshold (summer days).
+ r"""
+ Maximum number of consecutive days with tasmax above a threshold (summer days).
Return the maximum number of consecutive days within the period where the maximum daily temperature is
above a certain threshold (default: 25℃).
@@ -2913,7 +2963,8 @@ def maximum_consecutive_tx_days(
def sea_ice_area(
siconc: xarray.DataArray, areacello: xarray.DataArray, thresh: Quantified = "15 pct"
) -> xarray.DataArray:
- """Total sea ice area.
+ """
+ Total sea ice area.
Sea ice area measures the total sea ice covered area where sea ice concentration is above a threshold,
usually set to 15%.
@@ -2951,7 +3002,8 @@ def sea_ice_area(
def sea_ice_extent(
siconc: xarray.DataArray, areacello: xarray.DataArray, thresh: Quantified = "15 pct"
) -> xarray.DataArray:
- """Total sea ice extent.
+ """
+ Total sea ice extent.
Sea ice extent measures the *ice-covered* area, where a region is considered ice-covered if its sea ice
concentration is above a threshold, usually set to 15%.
@@ -2988,7 +3040,8 @@ def sea_ice_extent(
def windy_days(
sfcWind: xarray.DataArray, thresh: Quantified = "10.8 m s-1", freq: str = "MS"
) -> xarray.DataArray:
- r"""Windy days.
+ r"""
+ Windy days.
The number of days with average near-surface wind speed above threshold (default: 10.8 m/s).
@@ -3028,7 +3081,8 @@ def rprctot(
freq: str = "YS",
op: str = ">=",
) -> xarray.DataArray:
- """Proportion of accumulated precipitation arising from convective processes.
+ """
+ Proportion of accumulated precipitation arising from convective processes.
Return the proportion of total accumulated precipitation due to convection on days with total precipitation
greater or equal to a given threshold (default: 1.0 mm/day) during the given period.
@@ -3074,7 +3128,8 @@ def degree_days_exceedance_date(
never_reached: DayOfYearStr | int | None = None,
freq: str = "YS",
) -> xarray.DataArray:
- r"""Degree-days exceedance date.
+ r"""
+ Degree-days exceedance date.
Day of year when the sum of degree days exceeds a threshold (default: 25 K days).
Degree days are computed above or below a given temperature threshold (default: 0℃).
@@ -3090,10 +3145,10 @@ def degree_days_exceedance_date(
op : {">", "gt", "<", "lt", ">=", "ge", "<=", "le"}
If equivalent to '>', degree days are computed as `tas - thresh` and if
equivalent to '<', they are computed as `thresh - tas`.
- after_date: str, optional
+ after_date : str, optional
Date at which to start the cumulative sum.
In "MM-DD" format, defaults to the start of the sampling period.
- never_reached: int, str, optional
+ never_reached : int, str, optional
What to do when `sum_thresh` is never exceeded.
If an int, the value to assign as a day-of-year.
If a string, must be in "MM-DD" format, the day-of-year of that date is assigned.
@@ -3176,7 +3231,8 @@ def dry_spell_frequency(
op: str = "sum",
**indexer,
) -> xarray.DataArray:
- r"""Return the number of dry periods of n days and more.
+ r"""
+ Return the number of dry periods of n days and more.
Periods during which the accumulated or maximal daily precipitation amount on a window of n days is under threshold.
@@ -3192,15 +3248,14 @@ def dry_spell_frequency(
freq : str
Resampling frequency.
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
- length encoding (or a similar algorithm) is applied to runs.
- op: {"sum", "max", "min", "mean"}
+ Determines if the resampling should take place before or after the run length encoding (or a similar algorithm) is applied to runs.
+ op : {"sum", "max", "min", "mean"}
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.
- \*\*indexer
+ **indexer : {dim: indexer}, optional
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.
@@ -3212,7 +3267,7 @@ def dry_spell_frequency(
See Also
--------
- xclim.indices.generic.spell_length_statistics
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
Examples
--------
@@ -3245,7 +3300,8 @@ def dry_spell_total_length(
resample_before_rl: bool = True,
**indexer,
) -> xarray.DataArray:
- r"""Total length of dry spells.
+ r"""
+ 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.
@@ -3267,9 +3323,8 @@ def dry_spell_total_length(
freq : str
Resampling frequency.
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
- length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ Determines if the resampling should take place before or after the run length encoding (or a similar algorithm) is applied to runs.
+ **indexer : {dim: indexer}, optional
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.
@@ -3281,7 +3336,7 @@ def dry_spell_total_length(
See Also
--------
- xclim.indices.generic.spell_length_statistics
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
Notes
-----
@@ -3316,7 +3371,8 @@ def dry_spell_max_length(
resample_before_rl: bool = True,
**indexer,
) -> xarray.DataArray:
- r"""Longest dry spell.
+ r"""
+ Longest dry spell.
Maximum number of consecutive days in a dry period of minimum length, during which the maximum or
accumulated precipitation within a window of the same length is under a threshold.
@@ -3336,20 +3392,20 @@ def dry_spell_max_length(
resample_before_rl : bool
Determines if the resampling should take place before or after the run
length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ **indexer : {dim: indexer}, optional
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.
- See Also
- --------
- xclim.indices.generic.spell_length_statistics
-
Returns
-------
xarray.DataArray, [days]
The {freq} longest spell in dry periods of minimum {window} days.
+ See Also
+ --------
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
+
Notes
-----
The algorithm assumes days before and after the timeseries are "wet", meaning that the condition for being
@@ -3383,7 +3439,8 @@ def wet_spell_frequency(
op: str = "sum",
**indexer,
) -> xarray.DataArray:
- r"""Return the number of wet periods of n days and more.
+ r"""
+ Return the number of wet periods of n days and more.
Periods during which the accumulated, minimal, or maximal daily precipitation amount on a window of n days is over threshold.
@@ -3393,34 +3450,33 @@ def wet_spell_frequency(
Daily precipitation.
thresh : Quantified
Precipitation amount over which a period is considered dry.
- The value against which the threshold is compared depends on `op` .
+ The value against which the threshold is compared depends on `op`.
window : int
Minimum length of the spells.
freq : str
Resampling frequency.
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
- length encoding (or a similar algorithm) is applied to runs.
+ Determines if the resampling should take place before or after the run length encoding (or a similar algorithm) is applied to runs.
op : {"sum","min", "max", "mean"}
Operation to perform on the window.
Default is "sum", which checks that the sum of accumulated precipitation over the whole window is more than the
threshold.
"min" checks that the maximal daily precipitation amount within the window is more than the threshold.
This is the same as verifying that each individual day is above the threshold.
- \*\*indexer
+ **indexer : {dim: indexer}, optional
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 wet days, but before finding the spells.
- See Also
- --------
- xclim.indices.generic.spell_length_statistics
-
Returns
-------
xarray.DataArray, [unitless]
The {freq} number of wet periods of minimum {window} days.
+ See Also
+ --------
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
+
Examples
--------
>>> from xclim.indices import wet_spell_frequency
@@ -3452,7 +3508,8 @@ def wet_spell_total_length(
resample_before_rl: bool = True,
**indexer,
) -> xarray.DataArray:
- r"""Total length of wet spells.
+ r"""
+ Total length of wet spells.
Total number of days in wet periods of a minimum length, during which the minimum or
accumulated precipitation within a window of the same length is over a threshold.
@@ -3473,22 +3530,21 @@ def wet_spell_total_length(
freq : str
Resampling frequency.
resample_before_rl : bool
- Determines if the resampling should take place before or after the run
- length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ Determines if the resampling should take place before or after the run length encoding (or a similar algorithm) is applied to runs.
+ **indexer : {dim: indexer}, optional
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 wet days, but before finding the spells.
- See Also
- --------
- xclim.indices.generic.spell_length_statistics
-
Returns
-------
xarray.DataArray, [days]
The {freq} total number of days in wet periods of minimum {window} days.
+ See Also
+ --------
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
+
Notes
-----
The algorithm assumes days before and after the timeseries are "dry", meaning that the condition for being
@@ -3522,7 +3578,8 @@ def wet_spell_max_length(
resample_before_rl: bool = True,
**indexer,
) -> xarray.DataArray:
- r"""Longest wet spell.
+ r"""
+ Longest wet spell.
Maximum number of consecutive days in a wet period of minimum length, during which the minimum or
accumulated precipitation within a window of the same length is over a threshold.
@@ -3542,23 +3599,22 @@ def wet_spell_max_length(
In all cases, the whole window is marked a part of a wet spell.
freq : str
Resampling frequency.
- resample_before_rl: bool
- Determines if the resampling should take place before or after the run
- length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ resample_before_rl : bool
+ Determines if the resampling should take place before or after the run length encoding (or a similar algorithm) is applied to runs.
+ **indexer : {dim: indexer}, optional
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 wet days, but before finding the spells.
- See Also
- --------
- xclim.indices.generic.spell_length_statistics
-
Returns
-------
xarray.DataArray, [days]
The {freq} longest spell in wet periods of minimum {window} days.
+ See Also
+ --------
+ xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics.
+
Notes
-----
The algorithm assumes days before and after the timeseries are "dry", meaning that the condition for being
diff --git a/xclim/indices/fire/_cffwis.py b/xclim/indices/fire/_cffwis.py
index 6c3a74b4b..e59dc2cb6 100644
--- a/xclim/indices/fire/_cffwis.py
+++ b/xclim/indices/fire/_cffwis.py
@@ -122,7 +122,7 @@
... ffmc_start=85,
... dmc_dry_factor=2,
... )
-"""
+""" # numpydoc ignore=GL07
# This file is structured in the following way:
# Section 1: individual codes, numba-accelerated and vectorized functions.
@@ -243,7 +243,8 @@ def _day_length_factor(lat: float, mth: int): # pragma: no cover
@vectorize(nopython=True)
def _fine_fuel_moisture_code(t, p, w, h, ffmc0): # pragma: no cover
- """Compute the fine fuel moisture code over one time step.
+ """
+ Compute the fine fuel moisture code over one time step.
Parameters
----------
@@ -329,7 +330,8 @@ def _duff_moisture_code(
lat: float,
dmc0: float,
): # pragma: no cover
- """Compute the Duff moisture code over one time step.
+ """
+ Compute the Duff moisture code over one time step.
Parameters
----------
@@ -395,7 +397,8 @@ def _drought_code( # pragma: no cover
lat: float,
dc0: float,
) -> np.ndarray:
- """Compute the drought code over one time step.
+ """
+ Compute the drought code over one time step.
Parameters
----------
@@ -438,7 +441,8 @@ def _drought_code( # pragma: no cover
def initial_spread_index(ws: np.ndarray, ffmc: np.ndarray) -> np.ndarray:
- """Initialize spread index.
+ """
+ Initialize spread index.
Parameters
----------
@@ -459,7 +463,8 @@ def initial_spread_index(ws: np.ndarray, ffmc: np.ndarray) -> np.ndarray:
def build_up_index(dmc, dc):
- """Build-up index.
+ """
+ Build-up index.
Parameters
----------
@@ -482,20 +487,21 @@ def build_up_index(dmc, dc):
# TODO: Does this need to be renamed?
-def fire_weather_index(isi, bui):
- """Fire weather index.
+def fire_weather_index(isi: np.ndarray, bui: np.ndarray) -> np.ndarray:
+ """
+ Fire Weather Index.
Parameters
----------
- isi : array
- Initial spread index
- bui : array
- Build up index.
+ isi : array-like
+ Initial spread index.
+ bui : array-like
+ Build Up Index.
Returns
-------
- array
- Build up index.
+ array-like
+ The Fire Weather Index.
"""
fwi = np.where(
bui <= 80.0,
@@ -507,30 +513,34 @@ def fire_weather_index(isi, bui):
def daily_severity_rating(fwi: np.ndarray) -> np.ndarray:
- """Daily severity rating.
+ """
+ Daily Severity Rating.
Parameters
----------
- fwi : array_like
- Fire weather index
+ fwi : array-like
+ Fire Weather Index.
Returns
-------
- array_like
- Daily severity rating.
+ array-like
+ The Daily Severity Rating.
"""
return 0.0272 * fwi**1.77
@vectorize(nopython=True)
-def _overwintering_drought_code(DCf, wpr, a, b, minDC): # pragma: no cover
- """Compute the season-starting drought code based on the previous season's last drought code and the total winter precipitation.
+def _overwintering_drought_code(
+ DCf: np.ndarray, wpr: np.ndarray, a: float, b: float, minDC: int
+) -> np.ndarray | np.nan: # pragma: no cover
+ """
+ Compute the season-starting drought code based on the previous season's last drought code and the total winter precipitation.
Parameters
----------
- DCf : ndarray
+ DCf : array-like
The previous season's last drought code
- wpr : ndarray
+ wpr : array-like
The accumulated precipitation since the end of the fire season.
a : float
The carryover fraction from the previous season.
@@ -538,6 +548,11 @@ def _overwintering_drought_code(DCf, wpr, a, b, minDC): # pragma: no cover
The wetting efficiency fraction.
minDC : int
The overwintered DC cannot be below this value, usually the normal "dc_start" value.
+
+ Returns
+ -------
+ array-like or np.nan
+ The Overwintered Drought Code.
"""
if np.isnan(DCf) or np.isnan(wpr):
return np.nan
@@ -562,16 +577,17 @@ def _fire_season(
snow_condition_days: int = default_params["snow_condition_days"],
snow_thresh: float = default_params["snow_thresh"][0],
) -> np.ndarray:
- """Compute the active fire season mask.
+ """
+ Compute the active fire season mask.
Parameters
----------
- tas : ndarray
+ tas : array-like
Temperature [degC], the time axis on the last position.
- snd : ndarray, optional
+ snd : array-like, optional
Snow depth [m], time axis on the last position, used with method == 'LA08'.
method : {"WF93", "LA08", "GFWED"}
- Which method to use.
+ Which method to use. Defaults to "WF93".
temp_start_thresh : float
Starting temperature threshold.
temp_end_thresh : float
@@ -892,7 +908,7 @@ def _fire_weather_calc( # noqa: C901
# TODO: Does this need to be renamed?
-def fire_weather_ufunc( # noqa: C901
+def fire_weather_ufunc( # noqa: C901 # numpydoc ignore=PR01,PR02
*,
tas: xr.DataArray,
pr: xr.DataArray,
@@ -913,7 +929,8 @@ def fire_weather_ufunc( # noqa: C901
initial_start_up: bool = True,
**params,
) -> dict[str, xr.DataArray]:
- """Fire Weather Indexes computation using xarray's apply_ufunc.
+ """
+ Fire Weather Indexes computation using xarray's apply_ufunc.
No unit handling. Meant to be used by power users only. Please prefer using the :py:indicator:`DC` and
:py:indicator:`CFFWIS` indicators or the :py:func:`drought_code` and :py:func:`cffwis_indices` indices defined
@@ -925,17 +942,17 @@ def fire_weather_ufunc( # noqa: C901
Parameters
----------
tas : xr.DataArray
- Noon surface temperature in °C
+ Noon surface temperature in °C.
pr : xr.DataArray
- Rainfall over previous 24h, at noon in mm/day
+ Rainfall over previous 24h, at noon in mm/day.
hurs : xr.DataArray, optional
- Noon surface relative humidity in %, not needed for DC
+ Noon surface relative humidity in %, not needed for DC.
sfcWind : xr.DataArray, optional
- Noon surface wind speed in km/h, not needed for DC, DMC or BUI
+ Noon surface wind speed in km/h, not needed for DC, DMC or BUI.
snd : xr.DataArray, optional
- Noon snow depth in m, only needed if `season_method` is "LA08"
+ Noon snow depth in m, only needed if `season_method` is "LA08".
lat : xr.DataArray, optional
- Latitude in °N, not needed for FFMC or ISI
+ Latitude in °N, not needed for FFMC or ISI.
dc0 : xr.DataArray, optional
Previous DC map, see Notes. Defaults to NaN.
dmc0 : xr.DataArray, optional
@@ -1185,7 +1202,8 @@ def overwintering_drought_code(
],
min_dc: xr.DataArray | float = default_params["dc_start"],
) -> xr.DataArray:
- """Compute season-starting drought code based on previous season's last drought code and total winter precipitation.
+ """
+ Compute season-starting drought code based on previous season's last drought code and total winter precipitation.
This method replicates the "wDC" method of the "cffdrs R package :cite:p:`cantin_canadian_2014`, with an added
control on the "minimum" DC.
@@ -1197,15 +1215,15 @@ def overwintering_drought_code(
winter_pr : xr.DataArray
The accumulated precipitation since the end of the fire season.
carry_over_fraction : xr.DataArray or float
- Carry-over fraction of last fall’s moisture
+ Carry-over fraction of last fall’s moisture.
wetting_efficiency_fraction : xr.DataArray or float
- Effectiveness of winter precipitation in recharging moisture reserves in spring
+ Effectiveness of winter precipitation in recharging moisture reserves in spring.
min_dc : xr.DataArray or float
Minimum drought code starting value.
Returns
-------
- wDC : xr.DataArray
+ xr.DataArray
Overwintered drought code.
Notes
@@ -1303,10 +1321,16 @@ def cffwis_indices(
) -> tuple[
xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray
]:
- """Canadian Fire Weather Index System indices.
+ r"""
+ Canadian Fire Weather Index System indices.
- Computes the 6 fire weather indexes as defined by the Canadian Forest Service: the Drought Code, the Duff-Moisture
- Code, the Fine Fuel Moisture Code, the Initial Spread Index, the Build Up Index and the Fire Weather Index.
+ Computes the six (6) fire weather indexes, as defined by the Canadian Forest Service:
+ - The Drought Code
+ - The Duff-Moisture Code
+ - The Fine Fuel Moisture Code
+ - The Initial Spread Index
+ - The Build Up Index
+ - The Fire Weather Index.
Parameters
----------
@@ -1319,7 +1343,7 @@ def cffwis_indices(
hurs : xr.DataArray
Noon relative humidity.
lat : xr.DataArray
- Latitude coordinate
+ Latitude coordinate.
snd : xr.DataArray
Noon snow depth, only used if `season_method='LA08'` is passed.
ffmc0 : xr.DataArray
@@ -1342,17 +1366,23 @@ def cffwis_indices(
If True (default), gridpoints where the fire season is active on the first timestep go through a start_up phase
for that time step. Otherwise, previous codes must be given as a continuing fire season is assumed for those
points.
- params
+ **params : dict
Any other keyword parameters as defined in :py:func:`fire_weather_ufunc` and in :py:data:`default_params`.
Returns
-------
DC: xr.DataArray, [dimensionless]
+ The Drought Code.
DMC: xr.DataArray, [dimensionless]
+ The Duff Moisture Code.
FFMC: xr.DataArray, [dimensionless]
+ The Fine Fuel Moisture Code.
ISI: xr.DataArray, [dimensionless]
+ The Initial Spread Index.
BUI: xr.DataArray, [dimensionless]
+ The Build Up Index.
FWI: xr.DataArray, [dimensionless]
+ The Fire Weather Index.
Notes
-----
@@ -1415,7 +1445,8 @@ def drought_code(
initial_start_up: bool = True,
**params,
) -> xr.DataArray:
- r"""Drought code (FWI component).
+ r"""
+ Drought code (FWI component).
The drought code is part of the Canadian Forest Fire Weather Index System.
It is a numeric rating of the average moisture content of organic layers.
@@ -1427,7 +1458,7 @@ def drought_code(
pr : xr.DataArray
Rain fall in open over previous 24 hours, at noon.
lat : xr.DataArray
- Latitude coordinate
+ Latitude coordinate.
snd : xr.DataArray
Noon snow depth.
dc0 : xr.DataArray
@@ -1447,13 +1478,13 @@ def drought_code(
If True (default), grid points where the fire season is active on the first timestep go through a start_up phase
for that time step. Otherwise, previous codes must be given as a continuing fire season is assumed for those
points.
- params
+ **params : dict
Any other keyword parameters as defined in `xclim.indices.fire.fire_weather_ufunc` and in :py:data:`default_params`.
Returns
-------
xr.DataArray, [dimensionless]
- Drought code
+ Drought code.
Notes
-----
@@ -1510,7 +1541,8 @@ def duff_moisture_code(
initial_start_up: bool = True,
**params,
) -> xr.DataArray:
- r"""Duff moisture code (FWI component).
+ r"""
+ Duff moisture code (FWI component).
The duff moisture code is part of the Canadian Forest Fire Weather Index System.
It is a numeric rating of the average moisture content of loosely compacted organic layers of moderate depth.
@@ -1524,7 +1556,7 @@ def duff_moisture_code(
hurs : xr.DataArray
Noon relative humidity.
lat : xr.DataArray
- Latitude coordinate
+ Latitude coordinate.
snd : xr.DataArray
Noon snow depth.
dmc0 : xr.DataArray
@@ -1542,19 +1574,19 @@ def duff_moisture_code(
If True (default), grid points where the fire season is active on the first timestep go through a start_up phase
for that time step. Otherwise, previous codes must be given as a continuing fire season is assumed for those
points.
- params
+ **params : dict
Any other keyword parameters as defined in `xclim.indices.fire.fire_weather_ufunc` and in :py:data:`default_params`.
Returns
-------
xr.DataArray, [dimensionless]
- Duff moisture code
+ The Duff Moisture Code.
Notes
-----
See :cite:cts:`code-natural_resources_canada_data_nodate`, the :py:mod:`xclim.indices.fire` module documentation,
and the docstring of :py:func:`fire_weather_ufunc` for more information. This algorithm follows the official R code
- released by the CFS, which contains revisions from the original 1982 Fortran code.
+ released by the Canadian Forestry Service, which contains revisions from the original 1982 Fortran code.
References
----------
@@ -1601,7 +1633,8 @@ def fire_season(
snow_condition_days: int = 3,
snow_thresh: Quantified = "0.01 m",
) -> xr.DataArray:
- """Fire season mask.
+ """
+ Fire season mask.
Binary mask of the active fire season, defined by conditions on consecutive daily temperatures and, optionally, snow depths.
@@ -1632,7 +1665,7 @@ def fire_season(
Returns
-------
xr.DataArray
- Fire season mask
+ Fire season mask.
References
----------
diff --git a/xclim/indices/fire/_ffdi.py b/xclim/indices/fire/_ffdi.py
index a10d340b4..5c06ee0b2 100644
--- a/xclim/indices/fire/_ffdi.py
+++ b/xclim/indices/fire/_ffdi.py
@@ -3,15 +3,14 @@
McArthur Forest Fire Danger (Mark 5) System
===========================================
-This submodule defines indices related to the McArthur Forest Fire Danger Index Mark 5. Currently
-implemented are the :py:func:`xclim.indices.fire.keetch_byram_drought_index`,
+This submodule defines indices related to the McArthur Forest Fire Danger Index Mark 5.
+Currently implemented are the :py:func:`xclim.indices.fire.keetch_byram_drought_index`,
:py:func:`xclim.indices.fire.griffiths_drought_factor` and
:py:func:`xclim.indices.fire.mcarthur_forest_fire_danger_index` indices, which are used by the eponym indicators.
The implementation of these indices follows :cite:t:`ffdi-finkele_2006` and :cite:t:`ffdi-noble_1980`,
-with any differences described in the documentation for each index. Users are encouraged to read this module's
-documentation and consult :cite:t:`ffdi-finkele_2006` for a full description of the methods used to calculate each
+with any differences described in the documentation for each index. Users are encouraged to read the documentation of
+this module and consult :cite:t:`ffdi-finkele_2006` for a full description of the methods used to calculate each
index.
-
"""
# This file is structured in the following way:
# Section 1: individual codes, numba-accelerated and vectorized functions.
@@ -194,7 +193,8 @@ def keetch_byram_drought_index(
pr_annual: xr.DataArray,
kbdi0: xr.DataArray | None = None,
) -> xr.DataArray:
- """Keetch-Byram drought index (KBDI) for soil moisture deficit.
+ """
+ Keetch-Byram drought index (KBDI) for soil moisture deficit.
The KBDI indicates the amount of water necessary to bring the soil moisture content back to
field capacity. It is often used in the calculation of the McArthur Forest Fire Danger
@@ -208,7 +208,7 @@ def keetch_byram_drought_index(
Total rainfall over previous 24 hours [mm/day].
tasmax : xr.DataArray
Maximum temperature near the surface over previous 24 hours [degC].
- pr_annual: xr.DataArray
+ pr_annual : xr.DataArray
Mean (over years) annual accumulated rainfall [mm/year].
kbdi0 : xr.DataArray, optional
Previous KBDI values used to initialise the KBDI calculation [mm/day]. Defaults to 0.
@@ -276,7 +276,8 @@ def griffiths_drought_factor(
smd: xr.DataArray,
limiting_func: str = "xlim",
) -> xr.DataArray:
- """Griffiths drought factor based on the soil moisture deficit.
+ """
+ Griffiths drought factor based on the soil moisture deficit.
The drought factor is a numeric indicator of the forest fire fuel availability in the
deep litter bed. It is often used in the calculation of the McArthur Forest Fire Danger
@@ -296,7 +297,7 @@ def griffiths_drought_factor(
Returns
-------
- df : xr.DataArray
+ xr.DataArray
The limited Griffiths drought factor.
Notes
@@ -361,7 +362,8 @@ def mcarthur_forest_fire_danger_index(
hurs: xr.DataArray,
sfcWind: xr.DataArray,
):
- """McArthur forest fire danger index (FFDI) Mark 5.
+ """
+ McArthur forest fire danger index (FFDI) Mark 5.
The FFDI is a numeric indicator of the potential danger of a forest fire.
diff --git a/xclim/indices/generic.py b/xclim/indices/generic.py
index 98515be73..510c6eb45 100644
--- a/xclim/indices/generic.py
+++ b/xclim/indices/generic.py
@@ -70,7 +70,8 @@
def select_resample_op(
da: xr.DataArray, op: str | Callable, freq: str = "YS", out_units=None, **indexer
) -> xr.DataArray:
- """Apply operation over each period that is part of the index selection.
+ r"""
+ Apply operation over each period that is part of the index selection.
Parameters
----------
@@ -82,7 +83,7 @@ def select_resample_op(
Resampling frequency defining the periods as defined in :ref:`timeseries.resampling`.
out_units : str, optional
Output units to assign. Only necessary if `op` is function not supported by :py:func:`xclim.core.units.to_agg_units`.
- indexer : {dim: indexer, }, optional
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter values,
month=1 to select January, or month=[6,7,8] to select summer months. If not indexer is given, all values are
considered.
@@ -124,7 +125,8 @@ def select_rolling_resample_op(
out_units=None,
**indexer,
) -> xr.DataArray:
- """Apply operation over each period that is part of the index selection, using a rolling window before the operation.
+ r"""
+ Apply operation over each period that is part of the index selection, using a rolling window before the operation.
Parameters
----------
@@ -142,7 +144,7 @@ def select_rolling_resample_op(
Resampling frequency defining the periods as defined in :ref:`timeseries.resampling`. Applied after the rolling window.
out_units : str, optional
Output units to assign. Only necessary if `op` is function not supported by :py:func:`xclim.core.units.to_agg_units`.
- indexer : {dim: indexer, }, optional
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter values,
month=1 to select January, or month=[6,7,8] to select summer months. If not indexer is given, all values are
considered.
@@ -161,14 +163,38 @@ def select_rolling_resample_op(
def doymax(da: xr.DataArray) -> xr.DataArray:
- """Return the day of year of the maximum value."""
+ """
+ Return the day of year of the maximum value.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ The DataArray to process.
+
+ Returns
+ -------
+ xr.DataArray
+ The day of year of the maximum value.
+ """
i = da.argmax(dim="time")
out = da.time.dt.dayofyear.isel(time=i, drop=True)
return to_agg_units(out, da, "doymax")
def doymin(da: xr.DataArray) -> xr.DataArray:
- """Return the day of year of the minimum value."""
+ """
+ Return the day of year of the minimum value.
+
+ Parameters
+ ----------
+ da : xr.DataArray
+ The DataArray to process.
+
+ Returns
+ -------
+ xr.DataArray
+ The day of year of the minimum value.
+ """
i = da.argmin(dim="time")
out = da.time.dt.dayofyear.isel(time=i, drop=True)
return to_agg_units(out, da, "doymin")
@@ -178,7 +204,19 @@ def doymin(da: xr.DataArray) -> xr.DataArray:
def default_freq(**indexer) -> str:
- """Return the default frequency."""
+ r"""
+ Return the default frequency.
+
+ Parameters
+ ----------
+ **indexer : {dim: indexer, }
+ The indexer to use to compute the frequency.
+
+ Returns
+ -------
+ str
+ The default frequency.
+ """
freq = "YS-JAN"
if indexer:
group, value = indexer.popitem()
@@ -197,7 +235,8 @@ def default_freq(**indexer) -> str:
def get_op(op: str, constrain: Sequence[str] | None = None) -> Callable:
- """Get python's comparing function according to its name of representation and validate allowed usage.
+ """
+ Get python's comparing function according to its name of representation and validate allowed usage.
Accepted op string are keys and values of xclim.indices.generic.binary_ops.
@@ -207,6 +246,11 @@ def get_op(op: str, constrain: Sequence[str] | None = None) -> Callable:
Operator.
constrain : sequence of str, optional
A tuple of allowed operators.
+
+ Returns
+ -------
+ Callable
+ The operator function.
"""
if op == "gteq":
warnings.warn(f"`{op}` is being renamed `ge` for compatibility.")
@@ -242,7 +286,8 @@ def compare(
right: float | int | np.ndarray | xr.DataArray,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Compare a dataArray to a threshold using given operator.
+ """
+ Compare a DataArray to a threshold using given operator.
Parameters
----------
@@ -270,7 +315,8 @@ def threshold_count(
freq: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Count number of days where value is above or below threshold.
+ """
+ Count number of days where value is above or below threshold.
Parameters
----------
@@ -303,7 +349,8 @@ def domain_count(
high: float | int | xr.DataArray,
freq: str,
) -> xr.DataArray:
- """Count number of days where value is within low and high thresholds.
+ """
+ Count number of days where value is within low and high thresholds.
A value is counted if it is larger than `low`, and smaller or equal to `high`, i.e. in `]low, high]`.
@@ -333,7 +380,8 @@ def get_daily_events(
op: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Return a 0/1 mask when a condition is True or False.
+ """
+ Return a 0/1 mask when a condition is True or False.
Parameters
----------
@@ -346,6 +394,11 @@ def get_daily_events(
constrain : sequence of str, optional
Optionally allowed conditions.
+ Returns
+ -------
+ xr.DataArray
+ The mask array of daily events.
+
Notes
-----
The function returns:
@@ -353,10 +406,6 @@ def get_daily_events(
- ``1`` where operator(da, da_value) is ``True``
- ``0`` where operator(da, da_value) is ``False``
- ``nan`` where da is ``nan``
-
- Returns
- -------
- xr.DataArray
"""
events = compare(da, op, threshold, constrain) * 1
events = events.where(~(np.isnan(da)))
@@ -374,32 +423,33 @@ def spell_mask(
weights: Sequence[float] = None,
var_reducer: str = "all",
) -> xr.DataArray:
- """Compute the boolean mask of data points that are part of a spell as defined by a rolling statistic.
+ """
+ Compute the boolean mask of data points that are part of a spell as defined by a rolling statistic.
A day is part of a spell (True in the mask) if it is contained in any period that fulfills the condition.
Parameters
----------
- data: DataArray or sequence of DataArray
+ data : DataArray or sequence of DataArray
The input data. Can be a list, in which case the condition is checked on all variables.
See var_reducer for the latter case.
- window: int
+ window : int
The length of the rolling window in which to compute statistics.
- win_reducer: {'min', 'max', 'sum', 'mean'}
+ win_reducer : {'min', 'max', 'sum', 'mean'}
The statistics to compute on the rolling window.
- op: {">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne"}
+ op : {">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne"}
The comparison operator to use when finding spells.
- thresh: float or sequence of floats
- The threshold to compare the rolling statistics against, as ``window_stats op threshold``.
+ thresh : float or sequence of floats
+ The threshold to compare the rolling statistics against, as ``{window_stats} {op} {threshold}``.
If data is a list, this must be a list of the same length with a threshold for each variable.
This function does not handle units and can't accept Quantified objects.
- min_gap: int
- The shortest possible gap between two spells. Spells closer than this are merged by assigning
- the gap steps to the merged spell.
- weights: sequence of floats
+ min_gap : int
+ The shortest possible gap between two spells.
+ Spells closer than this are merged by assigning the gap steps to the merged spell.
+ weights : sequence of floats
A list of weights of the same length as the window.
- Only supported if `win_reducer` is "mean".
- var_reducer: {'all', 'any'}
+ Only supported if `win_reducer` is `"mean"`.
+ var_reducer : {'all', 'any'}
If the data is a list, the condition must either be fulfilled on *all*
or *any* variables for the period to be considered a spell.
@@ -538,8 +588,9 @@ def spell_length_statistics(
min_gap: int = 1,
resample_before_rl: bool = True,
**indexer,
-):
- r"""Statistics on spells lengths.
+) -> xr.DataArray | Sequence[xr.DataArray]:
+ r"""
+ Generate statistic on spells lengths.
A spell is when a statistic (`win_reducer`) over a minimum number (`window`) of consecutive timesteps respects a condition (`op` `thresh`).
This returns a statistic over the spells count or lengths.
@@ -567,11 +618,21 @@ def spell_length_statistics(
resample_before_rl : bool
Determines if the resampling should take place before or after the run
length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
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 days part of a spell, but before taking the spell statistics.
+ Returns
+ -------
+ xr.DataArray or sequence of xr.DataArray
+ The length of the longest of such spells.
+
+ See Also
+ --------
+ spell_mask : The lower level functions that finds spells.
+ bivariate_spell_length_statistics : The bivariate version of this function.
+
Examples
--------
>>> spell_length_statistics(
@@ -600,11 +661,6 @@ def spell_length_statistics(
Here, a day is part of a spell if it is in any five (5) day period where the total accumulated precipitation reaches
or exceeds 20 mm. We then return the length of the longest of such spells.
-
- See Also
- --------
- spell_mask : The lower level functions that finds spells.
- bivariate_spell_length_statistics : The bivariate version of this function.
"""
thresh = convert_units_to(threshold, data, context="infer")
return _spell_length_statistics(
@@ -635,8 +691,9 @@ def bivariate_spell_length_statistics(
min_gap: int = 1,
resample_before_rl: bool = True,
**indexer,
-):
- r"""Statistics on spells lengths based on two variables.
+) -> xr.DataArray | Sequence[xr.DataArray]:
+ r"""
+ Generate statistic on spells lengths based on two variables.
A spell is when a statistic (`win_reducer`) over a minimum number (`window`) of consecutive timesteps respects a condition (`op` `thresh`).
This returns a statistic over the spells count or lengths. In this bivariate version, conditions on both variables must be fulfilled.
@@ -668,14 +725,19 @@ def bivariate_spell_length_statistics(
resample_before_rl : bool
Determines if the resampling should take place before or after the run
length encoding (or a similar algorithm) is applied to runs.
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
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 days part of a spell, but before taking the spell statistics.
+ Returns
+ -------
+ xr.DataArray or sequence of xr.DataArray
+ The length of the longest of such spells.
+
See Also
--------
- spell_length_statistics: The univariate version.
+ spell_length_statistics : The univariate version.
spell_mask : The lower level functions that finds spells.
"""
thresh1 = convert_units_to(threshold1, data1, context="infer")
@@ -705,11 +767,12 @@ def season(
mid_date: DayOfYearStr | None = None,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- r"""Season.
+ r"""
+ Season.
A season starts when a variable respects some condition for a consecutive run of `N` days. It stops
when the condition is inverted for `N` days. Runs where the condition is not met for fewer than `N` days
- are thus allowed. Additionally a middle date can serve as a maximal start date and minimum end date.
+ are thus allowed. Additionally, a middle date can serve as a maximal start date and minimum end date.
Parameters
----------
@@ -736,6 +799,12 @@ def season(
Depends on 'stat'. If 'start' or 'end', this is the day of year of the season's start or end.
If 'length', this is the length of the season.
+ See Also
+ --------
+ xclim.indices.run_length.season_start : The function that finds the start of the season.
+ xclim.indices.run_length.season_length : The function that finds the length of the season.
+ xclim.indices.run_length.season_end : The function that finds the end of the season.
+
Examples
--------
>>> season(tas, thresh="0 °C", window=5, op=">", stat="start", freq="YS")
@@ -756,20 +825,15 @@ def season(
Returns the length of the "dry" season. The season starts with 7 consecutive days with precipitation under or equal to
2 mm/d and ends with as many days above 2 mm/d. If no start is found before the first of august, the season is invalid.
If a start is found but no end, the end is set to the last day of the period (December 31st if the dataset is complete).
-
- See Also
- --------
- xclim.indices.run_length.season_start
- xclim.indices.run_length.season_length
- xclim.indices.run_length.season_end
"""
thresh = convert_units_to(thresh, data, context="infer")
cond = compare(data, op, thresh, constrain=constrain)
- FUNC = {"start": rl.season_start, "end": rl.season_end, "length": rl.season_length}
+ func = {"start": rl.season_start, "end": rl.season_end, "length": rl.season_length}
map_kwargs = {"window": window, "mid_date": mid_date}
+
if stat in ["start", "end"]:
map_kwargs["coord"] = "dayofyear"
- out = resample_map(cond, "time", freq, FUNC[stat], map_kwargs=map_kwargs)
+ out = resample_map(cond, "time", freq, func[stat], map_kwargs=map_kwargs)
if stat == "length":
return to_agg_units(out, data, "count")
# else, a date
@@ -790,7 +854,8 @@ def count_level_crossings(
op_low: str = "<",
op_high: str = ">=",
) -> xr.DataArray:
- """Calculate the number of times low_data is below threshold while high_data is above threshold.
+ """
+ Calculate the number of times low_data is below threshold while high_data is above threshold.
First, the threshold is transformed to the same standard_name and units as the input data,
then the thresholding is performed, and finally, the number of occurrences is counted.
@@ -813,6 +878,7 @@ def count_level_crossings(
Returns
-------
xr.DataArray
+ The DataArray of level crossing events.
"""
# Convert units to low_data
high_data = convert_units_to(high_data, low_data)
@@ -833,11 +899,12 @@ def count_occurrences(
op: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Calculate the number of times some condition is met.
+ """
+ Calculate the number of times some condition is met.
- First, the threshold is transformed to the same standard_name and units as the input data.
+ First, the threshold is transformed to the same standard_name and units as the input data:
Then the thresholding is performed as condition(data, threshold),
- i.e. if condition is `<`, then this counts the number of times `data < threshold`.
+ i.e. if condition is `<`, then this counts the number of times `data < threshold`:
Finally, count the number of occurrences when condition is met.
Parameters
@@ -856,6 +923,7 @@ def count_occurrences(
Returns
-------
xr.DataArray
+ The DataArray of counted occurrences.
"""
threshold = convert_units_to(threshold, data)
@@ -868,7 +936,8 @@ def count_occurrences(
def diurnal_temperature_range(
low_data: xr.DataArray, high_data: xr.DataArray, reducer: str, freq: str
) -> xr.DataArray:
- """Calculate the diurnal temperature range and reduce according to a statistic.
+ """
+ Calculate the diurnal temperature range and reduce according to a statistic.
Parameters
----------
@@ -878,12 +947,13 @@ def diurnal_temperature_range(
The highest daily temperature (tasmax).
reducer : {'max', 'min', 'mean', 'sum'}
Reducer.
- freq: str
+ freq : str
Resampling frequency defining the periods as defined in :ref:`timeseries.resampling`.
Returns
-------
xr.DataArray
+ The DataArray of the diurnal temperature range.
"""
high_data = convert_units_to(high_data, low_data)
@@ -903,7 +973,8 @@ def first_occurrence(
op: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Calculate the first time some condition is met.
+ """
+ Calculate the first time some condition is met.
First, the threshold is transformed to the same standard_name and units as the input data.
Then the thresholding is performed as condition(data, threshold), i.e. if condition is <, data < threshold.
@@ -925,6 +996,7 @@ def first_occurrence(
Returns
-------
xr.DataArray
+ The DataArray of times of first occurrences.
"""
threshold = convert_units_to(threshold, data)
@@ -949,7 +1021,8 @@ def last_occurrence(
op: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Calculate the last time some condition is met.
+ """
+ Calculate the last time some condition is met.
First, the threshold is transformed to the same standard_name and units as the input data.
Then the thresholding is performed as condition(data, threshold), i.e. if condition is <, data < threshold.
@@ -971,6 +1044,7 @@ def last_occurrence(
Returns
-------
xr.DataArray
+ The DataArray of times of last occurrences.
"""
threshold = convert_units_to(threshold, data)
@@ -991,7 +1065,8 @@ def last_occurrence(
def spell_length(
data: xr.DataArray, threshold: Quantified, reducer: str, freq: str, op: str
) -> xr.DataArray:
- """Calculate statistics on lengths of spells.
+ """
+ Calculate statistics on lengths of spells.
First, the threshold is transformed to the same standard_name and units as the input data.
Then the thresholding is performed as condition(data, threshold), i.e. if condition is <, data < threshold.
@@ -1013,6 +1088,7 @@ def spell_length(
Returns
-------
xr.DataArray
+ The DataArray of spell lengths.
"""
threshold = convert_units_to(
threshold,
@@ -1033,7 +1109,8 @@ def spell_length(
def statistics(data: xr.DataArray, reducer: str, freq: str) -> xr.DataArray:
- """Calculate a simple statistic of the data.
+ """
+ Calculate a simple statistic of the data.
Parameters
----------
@@ -1047,6 +1124,7 @@ def statistics(data: xr.DataArray, reducer: str, freq: str) -> xr.DataArray:
Returns
-------
xr.DataArray
+ The DataArray for the given statistic.
"""
out = getattr(data.resample(time=freq), reducer)()
out.attrs["units"] = data.attrs["units"]
@@ -1062,7 +1140,8 @@ def thresholded_statistics(
freq: str,
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- """Calculate a simple statistic of the data for which some condition is met.
+ """
+ Calculate a simple statistic of the data for which some condition is met.
First, the threshold is transformed to the same standard_name and units as the input data.
Then the thresholding is performed as condition(data, threshold), i.e. if condition is <, data < threshold.
@@ -1086,6 +1165,7 @@ def thresholded_statistics(
Returns
-------
xr.DataArray
+ The DataArray for the given thresholded statistic.
"""
threshold = convert_units_to(threshold, data)
@@ -1100,7 +1180,8 @@ def thresholded_statistics(
def temperature_sum(
data: xr.DataArray, op: str, threshold: Quantified, freq: str
) -> xr.DataArray:
- """Calculate the temperature sum above/below a threshold.
+ """
+ Calculate the temperature sum above/below a threshold.
First, the threshold is transformed to the same standard_name and units as the input data.
Then the thresholding is performed as condition(data, threshold), i.e. if condition is <, data < threshold.
@@ -1121,6 +1202,7 @@ def temperature_sum(
Returns
-------
xr.DataArray
+ The DataArray for the sum of temperatures above or below a threshold.
"""
threshold = convert_units_to(threshold, data)
@@ -1136,7 +1218,8 @@ def temperature_sum(
def interday_diurnal_temperature_range(
low_data: xr.DataArray, high_data: xr.DataArray, freq: str
) -> xr.DataArray:
- """Calculate the average absolute day-to-day difference in diurnal temperature range.
+ """
+ Calculate the average absolute day-to-day difference in diurnal temperature range.
Parameters
----------
@@ -1150,6 +1233,7 @@ def interday_diurnal_temperature_range(
Returns
-------
xr.DataArray
+ The DataArray for the average absolute day-to-day difference in diurnal temperature range.
"""
high_data = convert_units_to(high_data, low_data)
@@ -1164,7 +1248,8 @@ def interday_diurnal_temperature_range(
def extreme_temperature_range(
low_data: xr.DataArray, high_data: xr.DataArray, freq: str
) -> xr.DataArray:
- """Calculate the extreme temperature range as the maximum of daily maximum temperature minus the minimum of daily minimum temperature.
+ """
+ Calculate the extreme temperature range as the maximum of daily maximum temperature minus the minimum of daily minimum temperature.
Parameters
----------
@@ -1178,6 +1263,7 @@ def extreme_temperature_range(
Returns
-------
xr.DataArray
+ The DataArray for the extreme temperature range.
"""
high_data = convert_units_to(high_data, low_data)
@@ -1195,7 +1281,8 @@ def aggregate_between_dates(
op: str = "sum",
freq: str | None = None,
) -> xr.DataArray:
- """Aggregate the data over a period between start and end dates and apply the operator on the aggregated data.
+ """
+ Aggregate the data over a period between start and end dates and apply the operator on the aggregated data.
Parameters
----------
@@ -1290,7 +1377,8 @@ def _get_days(_bound, _group, _base_time):
def cumulative_difference(
data: xr.DataArray, threshold: Quantified, op: str, freq: str | None = None
) -> xr.DataArray:
- """Calculate the cumulative difference below/above a given value threshold.
+ """
+ Calculate the cumulative difference below/above a given value threshold.
Parameters
----------
@@ -1307,6 +1395,7 @@ def cumulative_difference(
Returns
-------
xr.DataArray
+ The DataArray for the cumulative difference between values and a given threshold.
"""
threshold = convert_units_to(threshold, data)
@@ -1336,14 +1425,15 @@ def first_day_threshold_reached(
freq: str = "YS",
constrain: Sequence[str] | None = None,
) -> xr.DataArray:
- r"""First day of values exceeding threshold.
+ r"""
+ First day of values exceeding threshold.
Returns first day of period where values reach or exceed a threshold over a given number of days,
limited to a starting calendar date.
Parameters
----------
- data xr.DataArray
+ data : xr.DataArray
Dataset being evaluated.
threshold : str
Threshold on which to base evaluation.
@@ -1385,21 +1475,22 @@ def _get_zone_bins(
zone_max: Quantity,
zone_step: Quantity,
):
- """Bin boundary values as defined by zone parameters.
+ """
+ Bin boundary values as defined by zone parameters.
Parameters
----------
zone_min : Quantity
- Left boundary of the first zone
+ Left boundary of the first zone.
zone_max : Quantity
- Right boundary of the last zone
+ Right boundary of the last zone.
zone_step: Quantity
- Size of zones
+ Size of zones.
Returns
-------
xr.DataArray, [units of `zone_step`]
- Array of values corresponding to each zone: [zone_min, zone_min+step, ..., zone_max]
+ Array of values corresponding to each zone: [zone_min, zone_min+step, ..., zone_max].
"""
units = pint2cfunits(str2pint(zone_step))
mn, mx, step = (
@@ -1423,7 +1514,8 @@ def get_zones(
exclude_boundary_zones: bool = True,
close_last_zone_right_boundary: bool = True,
) -> xr.DataArray:
- r"""Divide data into zones and attribute a zone coordinate to each input value.
+ r"""
+ Divide data into zones and attribute a zone coordinate to each input value.
Divide values into zones corresponding to bins of width zone_step beginning at zone_min and ending at zone_max.
Bins are inclusive on the left values and exclusive on the right values.
@@ -1431,24 +1523,24 @@ def get_zones(
Parameters
----------
da : xr.DataArray
- Input data
- zone_min : Quantity | None
- Left boundary of the first zone
- zone_max : Quantity | None
- Right boundary of the last zone
- zone_step: Quantity | None
- Size of zones
- bins : xr.DataArray | list[Quantity] | None
- Zones to be used, either as a DataArray with appropriate units or a list of Quantity
- exclude_boundary_zones : Bool
+ Input data.
+ zone_min : Quantity, optional
+ Left boundary of the first zone.
+ zone_max : Quantity, optional
+ Right boundary of the last zone.
+ zone_step : Quantity, optional
+ Size of zones.
+ bins : xr.DataArray or list of Quantity, optional
+ Zones to be used, either as a DataArray with appropriate units or a list of Quantity.
+ exclude_boundary_zones : bool
Determines whether a zone value is attributed for values in ]`-np.inf`, `zone_min`[ and [`zone_max`, `np.inf`\ [.
- close_last_zone_right_boundary : Bool
+ close_last_zone_right_boundary : bool
Determines if the right boundary of the last zone is closed.
Returns
-------
xr.DataArray, [dimensionless]
- Zone index for each value in `da`. Zones are returned as an integer range, starting from `0`
+ Zone index for each value in `da`. Zones are returned as an integer range, starting from `0`.
"""
# Check compatibility of arguments
zone_params = np.array([zone_min, zone_max, zone_step])
@@ -1489,7 +1581,8 @@ def _get_zone(_da):
def detrend(
ds: xr.DataArray | xr.Dataset, dim="time", deg=1
) -> xr.DataArray | xr.Dataset:
- """Detrend data along a given dimension computing a polynomial trend of a given order.
+ """
+ Detrend data along a given dimension computing a polynomial trend of a given order.
Parameters
----------
@@ -1526,7 +1619,8 @@ def thresholded_events(
window_stop: int = 1,
freq: str | None = None,
) -> xr.Dataset:
- r"""Thresholded events.
+ r"""
+ Thresholded events.
Finds all events along the time dimension. An event starts if the start condition is fulfilled for a given number of consecutive time steps.
It ends when the end condition is fulfilled for a given number of consecutive time steps.
@@ -1544,25 +1638,26 @@ def thresholded_events(
Threshold defining the event.
op : {">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne"}
Logical operator defining the event, e.g. arr > thresh.
- window: int
+ window : int
Number of time steps where the event condition must be true to start an event.
thresh_stop : Quantified, optional
Threshold defining the end of an event. Defaults to `thresh`.
op_stop : {">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne"}, optional
Logical operator for the end of an event. Defaults to the opposite of `op`.
- window_stop: int, optional
- Number of time steps where the end condition must be true to end an event. Defaults to 1.
- freq: str, optional
+ window_stop : int, optional
+ Number of time steps where the end condition must be true to end an event. Defaults to `1`.
+ freq : str, optional
A frequency to divide the data into periods. If absent, the output has not time dimension.
If given, the events are searched within in each resample period independently.
Returns
-------
xr.Dataset, same shape as the data except the time dimension is replaced by an "event" dimension.
- event_length: The number of time steps in each event
- event_effective_length: The number of time steps of even event where the start condition is true.
- event_sum: The sum within each event, only considering the steps where start condition is true.
- event_start: The datetime of the start of the run.
+ The dataset contains the following variables:
+ event_length: The number of time steps in each event
+ event_effective_length: The number of time steps of even event where the start condition is true.
+ event_sum: The sum within each event, only considering the steps where start condition is true.
+ event_start: The datetime of the start of the run.
"""
thresh = convert_units_to(thresh, data)
diff --git a/xclim/indices/helpers.py b/xclim/indices/helpers.py
index 25986cefd..abe8e86de 100644
--- a/xclim/indices/helpers.py
+++ b/xclim/indices/helpers.py
@@ -72,12 +72,23 @@ def distance_from_sun(dates: xr.DataArray) -> xr.DataArray:
return xr.DataArray(sun_earth, coords=dates.coords, dims=dates.dims)
-def day_angle(time: xr.DataArray):
- """Day of year as an angle.
+def day_angle(time: xr.DataArray) -> xr.DataArray:
+ """
+ Day of year as an angle.
- Assuming the earth makes a full circle in a year, this is the angle covered from
+ Assuming the Earth makes a full circle in a year, this is the angle covered from
the beginning of the year up to that timestep. Also called the "julian day fraction".
See :py:func:`~xclim.core.calendar.datetime_to_decimal_year`.
+
+ Parameters
+ ----------
+ time : xr.DataArray
+ Time coordinate.
+
+ Returns
+ -------
+ xr.DataArray, [rad]
+ Day angle.
"""
if XR2409:
decimal_year = time.dt.decimal_year
@@ -87,20 +98,21 @@ def day_angle(time: xr.DataArray):
def solar_declination(time: xr.DataArray, method="spencer") -> xr.DataArray:
- """Solar declination.
+ """
+ Solar declination.
The angle between the sun rays and the earth's equator, in radians, as approximated
by :cite:t:`spencer_fourier_1971` or assuming the orbit is a circle.
Parameters
----------
- time: xr.DataArray
- Time coordinate.
+ time : xr.DataArray
+ Time coordinate.
method : {'spencer', 'simple'}
- Which approximation to use. The default ("spencer") uses the first 7 terms of the
- Fourier series representing the observed declination, while "simple" assumes
- the orbit is a circle with a fixed obliquity and that the solstice/equinox happen
- at fixed angles on the orbit (the exact calendar date changes for leap years).
+ Which approximation to use. The default ("spencer") uses the first seven (7) terms of the
+ Fourier series representing the observed declination, while "simple" assumes the orbit is
+ a circle with a fixed obliquity and that the solstice/equinox happen at fixed angles on
+ the orbit (the exact calendar date changes for leap years).
Returns
-------
@@ -135,15 +147,16 @@ def solar_declination(time: xr.DataArray, method="spencer") -> xr.DataArray:
def time_correction_for_solar_angle(time: xr.DataArray) -> xr.DataArray:
- """Time correction for solar angle.
+ """
+ Time correction for solar angle.
Every 1° of angular rotation on earth is equal to 4 minutes of time.
The time correction is needed to adjust local watch time to solar time.
Parameters
----------
- time: xr.DataArray
- Time coordinate.
+ time : xr.DataArray
+ Time coordinate.
Returns
-------
@@ -169,19 +182,20 @@ def time_correction_for_solar_angle(time: xr.DataArray) -> xr.DataArray:
def eccentricity_correction_factor(
time: xr.DataArray, method: str = "spencer"
) -> xr.DataArray:
- """Eccentricity correction factor of the Earth's orbit.
+ """
+ Eccentricity correction factor of the Earth's orbit.
The squared ratio of the mean distance Earth-Sun to the distance at a specific moment.
As approximated by :cite:t:`spencer_fourier_1971`.
Parameters
----------
- time: xr.DataArray
- Time coordinate
+ time : xr.DataArray
+ Time coordinate.
method : {'spencer', 'simple'}
Which approximation to use.
- The default ("spencer") uses the first five terms of the fourier series of the eccentricity.
- The "simple" method approximates with only the first two.
+ The default ("spencer") uses the first five (5) terms of the fourier series of the eccentricity.
+ The "simple" method approximates with only the first two (2).
Returns
-------
@@ -221,7 +235,8 @@ def cosine_of_solar_zenith_angle(
sunlit: bool = False,
chunks: dict[str, int] | None = None,
) -> xr.DataArray:
- """Cosine of the solar zenith angle.
+ """
+ Cosine of the solar zenith angle.
The solar zenith angle is the angle between a vertical line (perpendicular to the ground) and the sun rays.
This function computes a statistic of its cosine : its instantaneous value, the integral from sunrise to sunset or the average over
@@ -230,7 +245,7 @@ def cosine_of_solar_zenith_angle(
Parameters
----------
- time: xr.DataArray
+ time : xr.DataArray
The UTC time. If not daily and `stat` is "integral" or "average", the timestamp is taken as the start of interval.
If daily, the interval is assumed to be centered on Noon.
If fewer than three timesteps are given, a daily frequency is assumed.
@@ -248,7 +263,7 @@ def cosine_of_solar_zenith_angle(
If "average", this returns the average of the cosine of the zenith angle
If "integral", this returns the integral of the cosine of the zenith angle
If "instant", this returns the instantaneous cosine of the zenith angle
- sunlit: bool
+ sunlit : bool
If True, only the sunlit part of the interval is considered in the integral or average.
Does nothing if stat is "instant".
chunks : dictionary
@@ -334,7 +349,7 @@ def cosine_of_solar_zenith_angle(
def _sunlit_integral_of_cosine_of_solar_zenith_angle(
declination, lat, h_sunset, h_start, h_end, average
):
- """Integral of the cosine of the the solar zenith angle over the sunlit part of the interval."""
+ """Integral of the cosine of the solar zenith angle over the sunlit part of the interval."""
# Code inspired by PyWBGT
h_sunrise = -h_sunset
# Polar day
@@ -389,7 +404,8 @@ def extraterrestrial_solar_radiation(
method: str = "spencer",
chunks: Mapping[Any, tuple] | None = None,
) -> xr.DataArray:
- """Extraterrestrial solar radiation.
+ """
+ Extraterrestrial solar radiation.
This is the daily energy received on a surface parallel to the ground at the mean distance of the earth to the sun.
It neglects the effect of the atmosphere. Computation is based on :cite:t:`kalogirou_chapter_2014` and the default
@@ -404,15 +420,16 @@ def extraterrestrial_solar_radiation(
solar_constant : str
The solar constant, the energy received on earth from the sun per surface per time.
method : {'spencer', 'simple'}
- Which method to use when computing the solar declination and the eccentricity
- correction factor. See :py:func:`solar_declination` and :py:func:`eccentricity_correction_factor`.
+ Which method to use when computing the solar declination and the eccentricity correction factor.
+ See :py:func:`solar_declination` and :py:func:`eccentricity_correction_factor`.
chunks : dict
When `times` and `lat` originate from coordinates of a large chunked dataset, passing the dataset's chunks here
will ensure the computation is chunked as well.
Returns
-------
- Extraterrestrial solar radiation, [J m-2 d-1]
+ xr.DataArray, [J m-2 d-1]
+ Extraterrestrial solar radiation.
References
----------
@@ -437,16 +454,18 @@ def day_lengths(
lat: xr.DataArray,
method: str = "spencer",
) -> xr.DataArray:
- r"""Day-lengths according to latitude and day of year.
+ r"""
+ Calculate day-length according to latitude and day of year.
See :py:func:`solar_declination` for the approximation used to compute the solar declination angle.
Based on :cite:t:`kalogirou_chapter_2014`.
Parameters
----------
- dates: xr.DataArray
- Daily datetime data. This function makes no sense with data of other frequency.
- lat: xarray.DataArray
+ dates : xr.DataArray
+ Daily datetime data.
+ This function makes no sense with data of other frequency.
+ lat : xarray.DataArray
Latitude coordinate.
method : {'spencer', 'simple'}
Which approximation to use when computing the solar declination angle.
@@ -479,23 +498,24 @@ def wind_speed_height_conversion(
h_target: str,
method: str = "log",
) -> xr.DataArray:
- r"""Wind speed at two meters.
+ r"""
+ Wind speed at two meters.
Parameters
----------
ua : xarray.DataArray
- Wind speed at height h
+ Wind speed at height `h`.
h_source : str
- Height of the input wind speed `ua` (e.g. `h == "10 m"` for a wind speed at `10 meters`)
+ Height of the input wind speed `ua` (e.g. `h == "10 m"` for a wind speed at `10 meters`).
h_target : str
- Height of the output wind speed
+ Height of the output wind speed.
method : {"log"}
- Method used to convert wind speed from one height to another
+ Method used to convert wind speed from one height to another.
Returns
-------
xarray.DataArray
- Wind speed at height `h_target`
+ Wind speed at height `h_target`.
References
----------
@@ -515,7 +535,8 @@ def wind_speed_height_conversion(
def _gather_lat(da: xr.DataArray) -> xr.DataArray:
- """Gather latitude coordinate using cf-xarray.
+ """
+ Gather latitude coordinate using cf-xarray.
Parameters
----------
@@ -540,7 +561,8 @@ def _gather_lat(da: xr.DataArray) -> xr.DataArray:
def _gather_lon(da: xr.DataArray) -> xr.DataArray:
- """Gather longitude coordinate using cf-xarray.
+ """
+ Gather longitude coordinate using cf-xarray.
Parameters
----------
@@ -573,7 +595,8 @@ def resample_map(
resample_kwargs: dict | None = None,
map_kwargs: dict | None = None,
) -> xr.DataArray | xr.Dataset:
- r"""Wraps xarray's resample(...).map() with a :py:func:`xarray.map_blocks`, ensuring the chunking is appropriate using flox.
+ r"""
+ Wrap xarray's resample(...).map() with a :py:func:`xarray.map_blocks`, ensuring the chunking is appropriate using flox.
Parameters
----------
@@ -597,7 +620,8 @@ def resample_map(
Returns
-------
- Resampled object.
+ xr.DataArray or xr.Dataset
+ Resampled object.
"""
resample_kwargs = resample_kwargs or {}
map_kwargs = map_kwargs or {}
@@ -649,13 +673,14 @@ def _compute_daytime_temperature(
tasmax: xr.DataArray,
daylength: xr.DataArray,
) -> xr.DataArray:
- """Compute daytime temperature based on a sinusoidal profile.
+ """
+ Compute daytime temperature based on a sinusoidal profile.
Minimum temperature is reached at sunrise and maximum temperature 2h before sunset.
Parameters
----------
- hours_after_sunrise : xarray.DataArray
+ hour_after_sunrise : xarray.DataArray
Hours after the last sunrise.
tasmin : xarray.DataArray
Daily minimum temperature.
@@ -680,7 +705,8 @@ def _compute_nighttime_temperature(
tas_sunset: xr.DataArray,
daylength: xr.DataArray,
) -> xr.DataArray:
- """Compute nighttime temperature based on a logarithmic profile.
+ """
+ Compute nighttime temperature based on a logarithmic profile.
Temperature at sunset is computed from previous daytime temperature,
minimum temperature is reached at sunrise.
@@ -707,10 +733,10 @@ def _compute_nighttime_temperature(
def _add_one_day(time: xr.DataArray) -> xr.DataArray:
- """Add one day to a time coordinate.
+ """
+ Add one day to a time coordinate.
- Depending on the calendar/dtype of the time array we need to use numpy's or
- datetime's (for cftimes) timedelta.
+ Depending on the calendar/dtype of the time array we need to use numpy's or datetime's (for cftimes) timedelta.
Parameters
----------
@@ -728,7 +754,8 @@ def _add_one_day(time: xr.DataArray) -> xr.DataArray:
def make_hourly_temperature(tasmin: xr.DataArray, tasmax: xr.DataArray) -> xr.DataArray:
- """Compute hourly temperatures from tasmin and tasmax.
+ """
+ Compute hourly temperatures from tasmin and tasmax.
Based on the Linvill et al. "Calculating Chilling Hours and Chill Units from Daily
Maximum and Minimum Temperature Observations", HortScience, 1990
diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py
index 5d5e71d97..fc75d7577 100644
--- a/xclim/indices/run_length.py
+++ b/xclim/indices/run_length.py
@@ -7,8 +7,9 @@
from __future__ import annotations
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
from datetime import datetime
+from typing import Any
from warnings import warn
import numpy as np
@@ -36,7 +37,8 @@ def use_ufunc(
freq: str | None = None,
index: str = "first",
) -> bool:
- """Return whether the ufunc version of run length algorithms should be used with this DataArray or not.
+ """
+ Return whether the ufunc version of run length algorithms should be used with this DataArray or not.
If ufunc_1dim is 'from_context', the parameter is read from xclim's global (or context) options.
If it is 'auto', this returns False for dask-backed array and for arrays with more than :py:const:`npts_opt`
@@ -51,7 +53,7 @@ def use_ufunc(
dim : str
The dimension along which to find runs.
freq : str
- Resampling frequency.
+ Resampling frequency.
index : {'first', 'last'}
If 'first' (default), the run length is indexed with the first element in the run.
If 'last', with the last element in the run.
@@ -75,42 +77,43 @@ def use_ufunc(
# If resampling after run length is set up for the computation, the 1d method is not implemented
# Unless ufunc_1dim is specifically set to False (in which case we flag an error above),
# we simply forbid this possibility.
- return (index == "first") and (ufunc_1dim) and (freq is None)
+ return (index == "first") and ufunc_1dim and (freq is None)
def resample_and_rl(
da: xr.DataArray,
resample_before_rl: bool,
- compute,
- *args,
+ compute: Callable,
+ *args: Any,
freq: str,
dim: str = "time",
**kwargs,
) -> xr.DataArray:
- """Wrap run length algorithms to control if resampling occurs before or after the algorithms.
+ r"""
+ Wrap run length algorithms to control if resampling occurs before or after the algorithms.
Parameters
----------
- da: xr.DataArray
- N-dimensional array (boolean).
+ da : xr.DataArray
+ N-dimensional array (boolean).
resample_before_rl : bool
- Determines whether if input arrays of runs `da` should be separated in period before
- or after the run length algorithms are applied.
- compute
- Run length function to apply
- args
- Positional arguments needed in `compute`.
- dim: str
- The dimension along which to find runs.
+ Determines whether if input arrays of runs `da` should be separated in period before
+ or after the run length algorithms are applied.
+ compute : Callable
+ Run length function to apply.
+ *args : Any
+ Positional arguments needed in `compute`.
freq : str
- Resampling frequency.
- kwargs
- Keyword arguments needed in `compute`.
+ Resampling frequency.
+ dim : str
+ The dimension along which to find runs.
+ **kwargs : dict
+ Keyword arguments needed in `compute`.
Returns
-------
xr.DataArray
- Output of compute resampled according to frequency {freq}.
+ Output of compute resampled according to frequency {freq}.
"""
if resample_before_rl:
out = resample_map(
@@ -131,7 +134,8 @@ def _cumsum_reset(
index: str = "last",
reset_on_zero: bool = True,
) -> xr.DataArray:
- """Compute the cumulative sum for each series of numbers separated by zero.
+ """
+ Compute the cumulative sum for each series of numbers separated by zero.
Parameters
----------
@@ -176,7 +180,8 @@ def rle(
dim: str = "time",
index: str = "first",
) -> xr.DataArray:
- """Run length
+ """
+ Run length.
Despite its name, this is not an actual run-length encoder : it returns an array of the same shape
as the input with 0 where the input was <= 0, nan where the input was > 0, except on the first (or last) element
@@ -199,6 +204,7 @@ def rle(
Returns
-------
xr.DataArray
+ The run length array.
"""
if da.dtype == bool:
da = da.astype(int)
@@ -232,7 +238,8 @@ def rle_statistics(
ufunc_1dim: str | bool = "from_context",
index: str = "first",
) -> xr.DataArray:
- """Return the length of consecutive run of True values, according to a reducing operator.
+ """
+ Return the length of consecutive run of True values, according to a reducing operator.
Parameters
----------
@@ -243,9 +250,9 @@ def rle_statistics(
window : int
Minimal length of consecutive runs to be included in the statistics.
dim : str
- Dimension along which to calculate consecutive run; Default: 'time'.
+ Dimension along which to calculate consecutive run; Default: 'time'.
freq : str
- Resampling frequency.
+ Resampling frequency.
ufunc_1dim : Union[str, bool]
Use the 1d 'ufunc' version of this function : default (auto) will attempt to select optimal
usage based on number of data points. Using 1D_ufunc=True is typically more efficient
@@ -286,16 +293,17 @@ def longest_run(
ufunc_1dim: str | bool = "from_context",
index: str = "first",
) -> xr.DataArray:
- """Return the length of the longest consecutive run of True values.
+ """
+ Return the length of the longest consecutive run of True values.
Parameters
----------
da : xr.DataArray
N-dimensional array (boolean).
dim : str
- Dimension along which to calculate consecutive run; Default: 'time'.
+ Dimension along which to calculate consecutive run; Default: 'time'.
freq : str
- Resampling frequency.
+ Resampling frequency.
ufunc_1dim : Union[str, bool]
Use the 1d 'ufunc' version of this function : default (auto) will attempt to select optimal
usage based on number of data points. Using 1D_ufunc=True is typically more efficient
@@ -329,7 +337,8 @@ def windowed_run_events(
ufunc_1dim: str | bool = "from_context",
index: str = "first",
) -> xr.DataArray:
- """Return the number of runs of a minimum length.
+ """
+ Return the number of runs of a minimum length.
Parameters
----------
@@ -339,9 +348,9 @@ def windowed_run_events(
Minimum run length.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run (default: 'time').
+ Dimension along which to calculate consecutive run (default: 'time').
freq : str
- Resampling frequency.
+ Resampling frequency.
ufunc_1dim : Union[str, bool]
Use the 1d 'ufunc' version of this function : default (auto) will attempt to select optimal
usage based on number of data points. Using 1D_ufunc=True is typically more efficient
@@ -384,7 +393,8 @@ def windowed_run_count(
ufunc_1dim: str | bool = "from_context",
index: str = "first",
) -> xr.DataArray:
- """Return the number of consecutive true values in array for runs at least as long as given duration.
+ """
+ Return the number of consecutive true values in array for runs at least as long as given duration.
Parameters
----------
@@ -394,9 +404,9 @@ def windowed_run_count(
Minimum run length.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run (default: 'time').
+ Dimension along which to calculate consecutive run (default: 'time').
freq : str
- Resampling frequency.
+ Resampling frequency.
ufunc_1dim : Union[str, bool]
Use the 1d 'ufunc' version of this function : default (auto) will attempt to select optimal
usage based on number of data points. Using 1D_ufunc=True is typically more efficient
@@ -436,7 +446,8 @@ def windowed_max_run_sum(
freq: str | None = None,
index: str = "first",
) -> xr.DataArray:
- """Return the maximum sum of consecutive float values for runs at least as long as the given window length.
+ """
+ Return the maximum sum of consecutive float values for runs at least as long as the given window length.
Parameters
----------
@@ -446,9 +457,9 @@ def windowed_max_run_sum(
Minimum run length.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run (default: 'time').
+ Dimension along which to calculate consecutive run (default: 'time').
freq : str
- Resampling frequency.
+ Resampling frequency.
index : {'first', 'last'}
If 'first', the run length is indexed with the first element in the run.
If 'last', with the last element in the run.
@@ -482,7 +493,8 @@ def _boundary_run(
ufunc_1dim: str | bool,
position: str,
) -> xr.DataArray:
- """Return the index of the first item of the first or last run of at least a given length.
+ """
+ Return the index of the first item of the first or last run of at least a given length.
Parameters
----------
@@ -492,9 +504,9 @@ def _boundary_run(
Minimum duration of consecutive run to accumulate values.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run.
+ Dimension along which to calculate consecutive run.
freq : str
- Resampling frequency.
+ Resampling frequency.
coord : Optional[str]
If not False, the function returns values along `dim` instead of indexes.
If `dim` has a datetime dtype, `coord` can also be a str of the name of the
@@ -514,6 +526,7 @@ def _boundary_run(
Returns np.nan if there are no valid runs.
"""
+ # FIXME: The logic here should not use outside scope variables, but rather pass them as arguments.
def coord_transform(out, da):
"""Transforms indexes to coordinates if needed, and drops obsolete dim."""
if coord:
@@ -526,6 +539,7 @@ def coord_transform(out, da):
out = out.drop_vars(dim)
return out
+ # FIXME: The logic here should not use outside scope variables, but rather pass them as arguments.
# general method to get indices (or coords) of first run
def find_boundary_run(runs, position):
if position == "last":
@@ -582,7 +596,8 @@ def first_run(
coord: str | bool | None = False,
ufunc_1dim: str | bool = "from_context",
) -> xr.DataArray:
- """Return the index of the first item of the first run of at least a given length.
+ """
+ Return the index of the first item of the first run of at least a given length.
Parameters
----------
@@ -592,9 +607,9 @@ def first_run(
Minimum duration of consecutive run to accumulate values.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run (default: 'time').
+ Dimension along which to calculate consecutive run (default: 'time').
freq : str
- Resampling frequency.
+ Resampling frequency.
coord : Optional[str]
If not False, the function returns values along `dim` instead of indexes.
If `dim` has a datetime dtype, `coord` can also be a str of the name of the
@@ -631,7 +646,8 @@ def last_run(
coord: str | bool | None = False,
ufunc_1dim: str | bool = "from_context",
) -> xr.DataArray:
- """Return the index of the last item of the last run of at least a given length.
+ """
+ Return the index of the last item of the last run of at least a given length.
Parameters
----------
@@ -641,9 +657,9 @@ def last_run(
Minimum duration of consecutive run to accumulate values.
When equal to 1, an optimized version of the algorithm is used.
dim : str
- Dimension along which to calculate consecutive run (default: 'time').
+ Dimension along which to calculate consecutive run (default: 'time').
freq : str
- Resampling frequency.
+ Resampling frequency.
coord : Optional[str]
If not False, the function returns values along `dim` instead of indexes.
If `dim` has a datetime dtype, `coord` can also be a str of the name of the
@@ -675,7 +691,8 @@ def last_run(
# TODO: Add window arg
# TODO: Inverse window arg to tolerate holes?
def run_bounds(mask: xr.DataArray, dim: str = "time", coord: bool | str = True):
- """Return the start and end dates of boolean runs along a dimension.
+ """
+ Return the start and end dates of boolean runs along a dimension.
Parameters
----------
@@ -740,16 +757,17 @@ def _get_indices(arr, *, N):
def keep_longest_run(
da: xr.DataArray, dim: str = "time", freq: str | None = None
) -> xr.DataArray:
- """Keep the longest run along a dimension.
+ """
+ Keep the longest run along a dimension.
Parameters
----------
da : xr.DataArray
Boolean array.
dim : str
- Dimension along which to check for the longest run.
+ Dimension along which to check for the longest run.
freq : str
- Resampling frequency.
+ Resampling frequency.
Returns
-------
@@ -759,20 +777,20 @@ def keep_longest_run(
# Get run lengths
rls = rle(da, dim)
- def get_out(rls):
- out = xr.where(
+ def _get_out(_rls): # numpydoc ignore=GL08
+ _out = xr.where(
# Construct an integer array and find the max
- rls[dim].copy(data=np.arange(rls[dim].size)) == rls.argmax(dim),
- rls + 1, # Add one to the First longest run
- rls,
+ _rls[dim].copy(data=np.arange(_rls[dim].size)) == _rls.argmax(dim),
+ _rls + 1, # Add one to the First longest run
+ _rls,
)
- out = out.ffill(dim) == out.max(dim)
- return out
+ _out = _out.ffill(dim) == _out.max(dim)
+ return _out
if freq is not None:
- out = resample_map(rls, dim, freq, get_out)
+ out = resample_map(rls, dim, freq, _get_out)
else:
- out = get_out(rls)
+ out = _get_out(rls)
return da.copy(data=out.transpose(*da.dims).data)
@@ -784,18 +802,19 @@ def runs_with_holes(
window_stop: int,
dim: str = "time",
) -> xr.DataArray:
- """Extract events, i.e. runs whose starting and stopping points are defined through run length conditions.
+ """
+ Extract events, i.e. runs whose starting and stopping points are defined through run length conditions.
Parameters
----------
da_start : xr.DataArray
- Input array where run sequences are searched to define the start points in the main runs
- window_start: int,
- Number of True (1) values needed to start a run in `da_start`
+ Input array where run sequences are searched to define the start points in the main runs.
+ window_start : int
+ Number of True (1) values needed to start a run in `da_start`.
da_stop : xr.DataArray
- Input array where run sequences are searched to define the stop points in the main runs
- window_stop: int,
- Number of True (1) values needed to start a run in `da_stop`
+ Input array where run sequences are searched to define the stop points in the main runs.
+ window_stop : int
+ Number of True (1) values needed to start a run in `da_stop`.
dim : str
Dimension name.
@@ -829,7 +848,8 @@ def season_start(
dim: str = "time",
coord: str | bool | None = False,
) -> xr.DataArray:
- """Start of a season.
+ """
+ Start of a season.
See :py:func:`season`.
@@ -855,9 +875,9 @@ def season_start(
See Also
--------
- season
- season_end
- season_length
+ season : Calculate the bounds of a season along a dimension.
+ season_end : End of a season.
+ season_length : Length of a season.
"""
return first_run_before_date(da, window=window, date=mid_date, dim=dim, coord=coord)
@@ -870,13 +890,13 @@ def season_end(
coord: str | bool | None = False,
_beg: xr.DataArray | None = None,
) -> xr.DataArray:
- """End of a season.
+ """
+ End of a season.
See :py:func:`season`. Similar to :py:func:`first_run_after_date` but here a season
must have a start for an end to be valid. Also, if no end is found but a start was found
the end is set to the last element of the series.
-
Parameters
----------
da : xr.DataArray
@@ -891,6 +911,8 @@ def season_end(
If not False, the function returns values along `dim` instead of indexes.
If `dim` has a datetime dtype, `coord` can also be a str of the name of the
DateTimeAccessor object to use (ex: 'dayofyear').
+ _beg : xr.DataArray, optional
+ If given, the start of the season. This is used to avoid recomputing the start.
Returns
-------
@@ -900,9 +922,9 @@ def season_end(
See Also
--------
- season
- season_start
- season_length
+ season : Calculate the bounds of a season along a dimension.
+ season_start : Start of a season.
+ season_length : Length of a season.
"""
# Fast path for `season` and `season_length`
if _beg is not None:
@@ -936,7 +958,8 @@ def season(
stat: str | None = None,
coord: str | bool | None = False,
) -> xr.Dataset | xr.DataArray:
- """Calculate the bounds of a season along a dimension.
+ """
+ Calculate the bounds of a season along a dimension.
A "season" is a run of True values that may include breaks under a given length (`window`).
The start is computed as the first run of `window` True values, and the end as the first subsequent run
@@ -953,6 +976,9 @@ def season(
The date (in MM-DD format) that a run must include to be considered valid.
dim : str
Dimension along which to calculate consecutive run (default: 'time').
+ stat : str, optional
+ Not currently implemented.
+ If not None, return a statistic of the season. The statistic is calculated on the season's values.
coord : Optional[str]
If not False, the function returns values along `dim` instead of indexes.
If `dim` has a datetime dtype, `coord` can also be a str of the name of the
@@ -961,18 +987,24 @@ def season(
Returns
-------
xr.Dataset
- Dataset variables:
+ The Dataset variables:
start : start of the season (index or units depending on ``coord``)
end : end of the season (index or units depending on ``coord``)
length : length of the season (in number of elements along ``dim``)
+ See Also
+ --------
+ season_start : Start of a season.
+ season_end : End of a season.
+ season_length : Length of a season.
+
Notes
-----
The run can include holes of False or NaN values, so long as they do not exceed the window size.
If a date is given, the season start and end are forced to be on each side of this date. This means that
even if the "real" season has been over for a long time, this is the date used in the length calculation.
- Example : Length of the "warm season", where T > 25°C, with date = 1st August. Let's say the temperature is over
+ e.g. Length of the "warm season", where T > 25°C, with date = 1st August. Let's say the temperature is over
25 for all June, but July and august have very cold temperatures. Instead of returning 30 days (June), the function
will return 61 days (July + June).
@@ -980,12 +1012,6 @@ def season(
season end was found before the end of the data. In that case the end is set to last element and
the length is set to the data size minus the start index. Thus, for the specific case, :math:`length = end - start + 1`,
because the end falls on the last element of the season instead of the subsequent one.
-
- See Also
- --------
- season_start
- season_end
- season_length
"""
beg = season_start(da, window=window, dim=dim, mid_date=mid_date, coord=False)
# Use fast path in season_end : no recomputing of start, no masking of end where beg.isnull() and don't set end if none found
@@ -1044,7 +1070,8 @@ def season_length(
mid_date: DayOfYearStr | None = None,
dim: str = "time",
) -> xr.DataArray:
- """Length of a season.
+ """
+ Length of a season.
Parameters
----------
@@ -1064,9 +1091,9 @@ def season_length(
See Also
--------
- season
- season_start
- season_end
+ season : Calculate the bounds of a season along a dimension.
+ season_start : Start of a season.
+ season_end : End of a season.
"""
seas = season(da, window, mid_date, dim, coord=False)
return seas.length
@@ -1079,7 +1106,8 @@ def run_end_after_date(
dim: str = "time",
coord: bool | str | None = "dayofyear",
) -> xr.DataArray:
- """Return the index of the first item after the end of a run after a given date.
+ """
+ Return the index of the first item after the end of a run after a given date.
The run must begin before the date.
@@ -1134,7 +1162,8 @@ def first_run_after_date(
dim: str = "time",
coord: bool | str | None = "dayofyear",
) -> xr.DataArray:
- """Return the index of the first item of the first run after a given date.
+ """
+ Return the index of the first item of the first run after a given date.
Parameters
----------
@@ -1176,7 +1205,8 @@ def last_run_before_date(
dim: str = "time",
coord: bool | str | None = "dayofyear",
) -> xr.DataArray:
- """Return the index of the last item of the last run before a given date.
+ """
+ Return the index of the last item of the last run before a given date.
Parameters
----------
@@ -1215,7 +1245,8 @@ def first_run_before_date(
dim: str = "time",
coord: bool | str | None = "dayofyear",
) -> xr.DataArray:
- """Return the index of the first item of the first run before a given date.
+ """
+ Return the index of the first item of the first run before a given date.
Parameters
----------
@@ -1268,7 +1299,8 @@ def _rle_1d(ia):
def rle_1d(
arr: int | float | bool | Sequence[int | float | bool],
) -> tuple[np.array, np.array, np.array]:
- """Return the length, starting position and value of consecutive identical values.
+ """
+ Return the length, starting position and value of consecutive identical values.
In opposition to py:func:`rle`, this is an actuel run length encoder.
@@ -1304,11 +1336,12 @@ def rle_1d(
def first_run_1d(arr: Sequence[int | float], window: int) -> int | np.nan:
- """Return the index of the first item of a run of at least a given length.
+ """
+ Return the index of the first item of a run of at least a given length.
Parameters
----------
- arr : Sequence[Union[int, float]]
+ arr : sequence of int or float
Input array.
window : int
Minimum duration of consecutive run to accumulate values.
@@ -1328,16 +1361,17 @@ def first_run_1d(arr: Sequence[int | float], window: int) -> int | np.nan:
def statistics_run_1d(arr: Sequence[bool], reducer: str, window: int) -> int:
- """Return statistics on lengths of run of identical values.
+ """
+ Return statistics on lengths of run of identical values.
Parameters
----------
- arr : Sequence[bool]
- Input array (bool)
+ arr : sequence of bool
+ Input array (bool).
reducer : {"mean", "sum", "min", "max", "std", "count"}
Reducing function name.
window : int
- Minimal length of runs to be included in the statistics
+ Minimal length of runs to be included in the statistics.
Returns
-------
@@ -1354,7 +1388,8 @@ def statistics_run_1d(arr: Sequence[bool], reducer: str, window: int) -> int:
def windowed_run_count_1d(arr: Sequence[bool], window: int) -> int:
- """Return the number of consecutive true values in array for runs at least as long as given duration.
+ """
+ Return the number of consecutive true values in array for runs at least as long as given duration.
Parameters
----------
@@ -1373,7 +1408,8 @@ def windowed_run_count_1d(arr: Sequence[bool], window: int) -> int:
def windowed_run_events_1d(arr: Sequence[bool], window: int) -> xr.DataArray:
- """Return the number of runs of a minimum length.
+ """
+ Return the number of runs of a minimum length.
Parameters
----------
@@ -1394,7 +1430,8 @@ def windowed_run_events_1d(arr: Sequence[bool], window: int) -> xr.DataArray:
def windowed_run_count_ufunc(
x: xr.DataArray | Sequence[bool], window: int, dim: str
) -> xr.DataArray:
- """Dask-parallel version of windowed_run_count_1d, ie: the number of consecutive true values in array for runs at least as long as given duration.
+ """
+ Dask-parallel version of windowed_run_count_1d, i.e. the number of consecutive true values in array for runs at least as long as given duration.
Parameters
----------
@@ -1425,7 +1462,8 @@ def windowed_run_count_ufunc(
def windowed_run_events_ufunc(
x: xr.DataArray | Sequence[bool], window: int, dim: str
) -> xr.DataArray:
- """Dask-parallel version of windowed_run_events_1d, ie: the number of runs at least as long as given duration.
+ """
+ Dask-parallel version of windowed_run_events_1d, i.e. the number of runs at least as long as given duration.
Parameters
----------
@@ -1459,13 +1497,14 @@ def statistics_run_ufunc(
window: int,
dim: str = "time",
) -> xr.DataArray:
- """Dask-parallel version of statistics_run_1d, ie: the {reducer} number of consecutive true values in array.
+ """
+ Dask-parallel version of statistics_run_1d, i.e. the {reducer} number of consecutive true values in array.
Parameters
----------
- x : Sequence[bool]
- Input array (bool)
- reducer: {'min', 'max', 'mean', 'sum', 'std'}
+ x : sequence of bool
+ Input array (bool).
+ reducer : {'min', 'max', 'mean', 'sum', 'std'}
Reducing function name.
window : int
Minimal length of runs.
@@ -1494,7 +1533,8 @@ def first_run_ufunc(
window: int,
dim: str,
) -> xr.DataArray:
- """Dask-parallel version of first_run_1d, ie: the first entry in array of consecutive true values.
+ """
+ Dask-parallel version of first_run_1d, i.e. the first entry in array of consecutive true values.
Parameters
----------
@@ -1527,14 +1567,15 @@ def first_run_ufunc(
def lazy_indexing(
da: xr.DataArray, index: xr.DataArray, dim: str | None = None
) -> xr.DataArray:
- """Get values of `da` at indices `index` in a NaN-aware and lazy manner.
+ """
+ Get values of `da` at indices `index` in a NaN-aware and lazy manner.
Parameters
----------
da : xr.DataArray
Input array. If not 1D, `dim` must be given and must not appear in index.
index : xr.DataArray
- N-d integer indices, if da is not 1D, all dimensions of index must be in da
+ N-d integer indices, if DataArray is not 1D, all dimensions of index must be in DataArray.
dim : str, optional
Dimension along which to index, unused if `da` is 1D, should not be present in `index`.
@@ -1614,7 +1655,8 @@ def index_of_date(
max_idxs: int | None = None,
default: int = 0,
) -> np.ndarray:
- """Get the index of a date in a time array.
+ """
+ Get the index of a date in a time array.
Parameters
----------
@@ -1628,15 +1670,15 @@ def index_of_date(
default : int
Index to return if date is None.
- Raises
- ------
- ValueError
- If there are most instances of `date` in `time` than `max_idxs`.
-
Returns
-------
numpy.ndarray
1D array of integers, indexes of `date` in `time`.
+
+ Raises
+ ------
+ ValueError
+ If there are most instances of `date` in `time` than `max_idxs`.
"""
if date is None:
return np.array([default])
@@ -1663,7 +1705,8 @@ def suspicious_run_1d(
op: str = ">",
thresh: float | None = None,
) -> np.ndarray:
- """Return True where the array contains a run of identical values.
+ """
+ Return `True` where the array contains a run of identical values.
Parameters
----------
@@ -1679,7 +1722,7 @@ def suspicious_run_1d(
Returns
-------
numpy.ndarray
- Whether or not the data points are part of a run of identical values.
+ Whether the data points are part of a run of identical values or not.
"""
v, rl, pos = rle_1d(arr)
sus_runs = rl >= window
@@ -1712,7 +1755,8 @@ def suspicious_run(
op: str = ">",
thresh: float | None = None,
) -> xr.DataArray:
- """Return True where the array contains has runs of identical values, vectorized version.
+ """
+ Return `True` where the array contains has runs of identical values, vectorized version.
In opposition to other run length functions, here the output has the same shape as the input.
@@ -1732,6 +1776,7 @@ def suspicious_run(
Returns
-------
xarray.DataArray
+ A boolean array of the same shape as the input, indicating where runs of identical values are found.
"""
return xr.apply_ufunc(
suspicious_run_1d,
@@ -1747,7 +1792,8 @@ def suspicious_run(
def _find_events(da_start, da_stop, data, window_start, window_stop):
- """Actual finding of events for each period.
+ """
+ Actual finding of events for each period.
Get basic blocks to work with, our runs with holes and the lengths of those runs.
Series of ones indicating where we have continuous runs with pauses
@@ -1839,7 +1885,8 @@ def find_events(
data: xr.DataArray | None = None,
freq: str | None = None,
):
- """Find events (runs).
+ """
+ Find events (runs).
An event starts with a run of ``window`` consecutive True values in the condition
and stops with ``window_stop`` consecutive True values in the stop condition.
@@ -1849,11 +1896,11 @@ def find_events(
Parameters
----------
- condition : DataArray of boolean values
+ condition : DataArray of bool
The boolean mask, true where the start condition of the event is fulfilled.
window : int
The number of consecutive True values for an event to start.
- condition_stop : DataArray of boolean values, optional
+ condition_stop : DataArray of bool, optional
The stopping boolean mask, true where the end condition of the event is fulfilled.
Defaults to the opposite of ``condition``.
window_stop : int
@@ -1868,10 +1915,11 @@ def find_events(
Returns
-------
xr.Dataset, same shape as the data it has a new "event" dimension (and the time dimension is resample or removed, according to ``freq``).
- event_length: The number of time steps in each event
- event_effective_length: The number of time steps of even event where the start condition is true.
- event_start: The datetime of the start of the run.
- event_sum: The sum within each event, only considering the steps where start condition is true. Only present if ``data`` is given.
+ The Dataset has the following variables:
+ event_length: The number of time steps in each event
+ event_effective_length: The number of time steps of even event where the start condition is true.
+ event_start: The datetime of the start of the run.
+ event_sum: The sum within each event, only considering the steps where start condition is true. Only present if ``data`` is given.
"""
if condition_stop is None:
condition_stop = ~condition
diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py
index 474973c5f..76a018baa 100644
--- a/xclim/indices/stats.py
+++ b/xclim/indices/stats.py
@@ -83,7 +83,8 @@ def fit(
dim: str = "time",
**fitkwargs: Any,
) -> xr.DataArray:
- r"""Fit an array to a univariate distribution along the time dimension.
+ r"""
+ Fit an array to a univariate distribution along the time dimension.
Parameters
----------
@@ -98,7 +99,7 @@ def fit(
The PWM method is usually more robust to outliers.
dim : str
The dimension upon which to perform the indexing (default: "time").
- \*\*fitkwargs
+ **fitkwargs : dict
Other arguments passed directly to :py:func:`_fitstart` and to the distribution's `fit`.
Returns
@@ -181,7 +182,8 @@ def parametric_quantile(
q: float | Sequence[float],
dist: str | rv_continuous | None = None,
) -> xr.DataArray:
- """Return the value corresponding to the given distribution parameters and quantile.
+ """
+ Return the value corresponding to the given distribution parameters and quantile.
Parameters
----------
@@ -254,7 +256,8 @@ def parametric_cdf(
v: float | Sequence[float],
dist: str | rv_continuous | None = None,
) -> xr.DataArray:
- """Return the cumulative distribution function corresponding to the given distribution parameters and value.
+ """
+ Return the cumulative distribution function corresponding to the given distribution parameters and value.
Parameters
----------
@@ -277,7 +280,7 @@ def parametric_cdf(
dist = get_dist(dist or p.attrs["scipy_dist"])
# Create a lambda function to facilitate passing arguments to dask. There is probably a better way to do this.
- def func(x):
+ def func(x): # numpydoc ignore=GL08
return dist.cdf(v, *x)
data = xr.apply_ufunc(
@@ -318,7 +321,8 @@ def fa(
mode: str = "max",
method: str = "ML",
) -> xr.DataArray:
- """Return the value corresponding to the given return period.
+ """
+ Return the value corresponding to the given return period.
Parameters
----------
@@ -380,7 +384,8 @@ def frequency_analysis(
method: str = "ML",
**indexer: int | float | str,
) -> xr.DataArray:
- r"""Return the value corresponding to a return period.
+ r"""
+ Return the value corresponding to a return period.
Parameters
----------
@@ -403,7 +408,7 @@ def frequency_analysis(
Fitting method, either maximum likelihood (ML or MLE), method of moments (MOM) or approximate method (APP).
Also accepts probability weighted moments (PWM), also called L-Moments, if `dist` is an instance from the lmoments3 library.
The PWM method is usually more robust to outliers.
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
Time attribute and values over which to subset the array. For example, use season='DJF' to select winter values,
month=1 to select January, or month=[6,7,8] to select summer months.
If indexer is not provided, all values are considered.
@@ -435,7 +440,7 @@ def frequency_analysis(
return fa(sel, t, dist=dist, mode=mode, method=method)
-def get_dist(dist: str | rv_continuous):
+def get_dist(dist: str | rv_continuous) -> rv_continuous:
"""
Return a distribution object from `scipy.stats`.
@@ -444,6 +449,11 @@ def get_dist(dist: str | rv_continuous):
dist : str or rv_continuous distribution object
Name of the univariate distribution, e.g. `beta`, `expon`, `genextreme`, `gamma`, `gumbel_r`, `lognorm`, `norm`.
Or an instance of the distribution.
+
+ Returns
+ -------
+ rv_continuous
+ A distribution object from `scipy.stats`.
"""
if isinstance(dist, rv_continuous):
return dist
@@ -456,7 +466,8 @@ def get_dist(dist: str | rv_continuous):
def _fit_start(x, dist: str, **fitkwargs: Any) -> tuple[tuple, dict]:
- r"""Return initial values for distribution parameters.
+ r"""
+ Return initial values for distribution parameters.
Providing the ML fit method initial values can help the optimizer find the global optimum.
@@ -468,7 +479,7 @@ def _fit_start(x, dist: str, **fitkwargs: Any) -> tuple[tuple, dict]:
Name of the univariate distribution, e.g. `beta`, `expon`, `genextreme`, `gamma`, `gumbel_r`, `lognorm`, `norm`.
(see :py:mod:scipy.stats).
Only `genextreme` and `weibull_exp` distributions are supported.
- \*\*fitkwargs
+ **fitkwargs : dict
Kwargs passed to fit.
Returns
@@ -477,7 +488,7 @@ def _fit_start(x, dist: str, **fitkwargs: Any) -> tuple[tuple, dict]:
References
----------
- :cite:cts:`coles_introduction_2001,cohen_parameter_2019,thom_1958,cooke_1979,muralidhar_1992`
+ :cite:cts:`coles_introduction_2001,cohen_parameter_2019,thom_1958,cooke_1979,muralidhar_1992`.
"""
x = np.asarray(x)
m = x.mean()
@@ -562,20 +573,21 @@ def _fit_start(x, dist: str, **fitkwargs: Any) -> tuple[tuple, dict]:
def _dist_method_1D( # noqa: N802
*args, dist: str | rv_continuous, function: str, **kwargs: Any
) -> xr.DataArray:
- r"""Statistical function for given argument on given distribution initialized with params.
+ r"""
+ Statistical function for given argument on given distribution initialized with params.
See :py:ref:`scipy:scipy.stats.rv_continuous` for all available functions and their arguments.
Every method where `"*args"` are the distribution parameters can be wrapped.
Parameters
----------
- \*args
+ *args
The arguments for the requested scipy function.
dist : str or rv_continuous distribution object
The scipy name of the distribution.
function : str
The name of the function to call.
- \*\*kwargs
+ **kwargs : dict
Other parameters to pass to the function call.
Returns
@@ -596,8 +608,8 @@ def dist_method(
r"""
Vectorized statistical function for given argument on given distribution initialized with params.
- Methods where `"*args"` are the distribution parameters can be wrapped, except those that reduce dimensions (
- e.g. `nnlf`) or create new dimensions (eg: 'rvs' with size != 1, 'stats' with more than one moment, 'interval',
+ Methods where `"*args"` are the distribution parameters can be wrapped, except those that reduce dimensions
+ (e.g. `nnlf`) or create new dimensions (e.g. 'rvs' with size != 1, 'stats' with more than one moment, 'interval',
'support').
Parameters
@@ -610,7 +622,7 @@ def dist_method(
The first argument for the requested function if different from `fit_params`.
dist : str or rv_continuous distribution object, optional
The distribution name or instance. Defaults to the `scipy_dist` attribute or `fit_params`.
- \*\*kwargs
+ **kwargs : dict
Other parameters to pass to the function call.
Returns
@@ -620,7 +632,7 @@ def dist_method(
See Also
--------
- scipy:scipy.stats.rv_continuous : for all available functions and their arguments.
+ scipy.stats.rv_continuous : For all available functions and their arguments.
"""
# Typically the data to be transformed
arg = [arg] if arg is not None else []
@@ -651,6 +663,8 @@ def preprocess_standardized_index(
r"""
Perform resample and roll operations involved in computing a standardized index.
+ Parameters
+ ----------
da : xarray.DataArray
Input array.
freq : {'D', 'MS'}, optional
@@ -659,7 +673,7 @@ def preprocess_standardized_index(
window : int
Averaging window length relative to the resampling frequency. For example, if `freq="MS"`,
i.e. a monthly resampling, the window is an integer number of months.
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
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`.
@@ -721,7 +735,8 @@ def standardized_index_fit_params(
fitkwargs: dict | None = None,
**indexer,
) -> xr.DataArray:
- r"""Standardized Index fitting parameters.
+ r"""
+ Standardized Index fitting parameters.
A standardized index measures the deviation of a variable averaged over a rolling temporal window and
fitted with a given distribution `dist` with respect to a calibration dataset.
@@ -747,7 +762,7 @@ def standardized_index_fit_params(
If True, the zeroes of `da` are treated separately when fitting a probability density function.
fitkwargs : dict, optional
Kwargs passed to ``xclim.indices.stats.fit`` used to impose values of certains parameters (`floc`, `fscale`).
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
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`.
@@ -830,7 +845,8 @@ def standardized_index(
params: Quantified | None = None,
**indexer,
) -> xr.DataArray:
- r"""Standardized Index (SI).
+ r"""
+ Standardized Index (SI).
This computes standardized indices which measure the deviation of variables in the dataset compared
to a reference distribution. The reference is a statistical distribution computed with fitting parameters `params`
@@ -866,7 +882,7 @@ def standardized_index(
Fit parameters.
The `params` can be computed using ``xclim.indices.stats.standardized_index_fit_params`` in advance.
The output can be given here as input, and it overrides other options.
- \*\*indexer
+ **indexer : {dim: indexer, }, optional
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`.
@@ -883,7 +899,7 @@ def standardized_index(
References
----------
- :cite:cts:`mckee_relationship_1993`
+ :cite:cts:`mckee_relationship_1993`.
"""
# use input arguments from ``params`` if it is given
if params is not None:
@@ -926,17 +942,21 @@ def standardized_index(
if paramsd != template.sizes:
params = params.broadcast_like(template)
- def reindex_time(da, da_ref, group):
+ def reindex_time(
+ _da: xr.DataArray, _da_ref: xr.DataArray, _group: str
+ ): # numpydoc ignore=GL08
if group == "time.dayofyear":
- da = resample_doy(da, da_ref)
+ _da = resample_doy(_da, _da_ref)
elif group == "time.month":
- da = da.rename(month="time").reindex(time=da_ref.time.dt.month)
- da["time"] = da_ref.time
+ _da = _da.rename(month="time").reindex(time=_da_ref.time.dt.month)
+ _da["time"] = _da_ref.time
elif group == "time.week":
- da = da.rename(week="time").reindex(time=da_ref.time.dt.isocalendar().week)
- da["time"] = da_ref.time
+ _da = _da.rename(week="time").reindex(
+ time=_da_ref.time.dt.isocalendar().week
+ )
+ _da["time"] = _da_ref.time
# I don't think rechunking is necessary here, need to check
- return da if not uses_dask(da) else da.chunk({"time": -1})
+ return _da if not uses_dask(_da) else _da.chunk({"time": -1})
# this should be restricted to some distributions / in some context
zero_inflated = "prob_of_zero" in params.coords
diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py
index 9d784a260..1d32c268e 100644
--- a/xclim/sdba/_adjustment.py
+++ b/xclim/sdba/_adjustment.py
@@ -696,7 +696,7 @@ def npdf_transform(ds: xr.Dataset, **kwargs) -> xr.Dataset:
hist : simulated timeseries on the reference period
sim : Simulated timeseries on the projected period.
rot_matrices : Random rotation matrices.
- \*\*kwargs
+ **kwargs : dict
pts_dim : multivariate dimension name
base : Adjustment class
base_kws : Kwargs for initialising the adjustment object
diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py
index 2b2df7736..ef342be4a 100644
--- a/xclim/sdba/adjustment.py
+++ b/xclim/sdba/adjustment.py
@@ -267,7 +267,7 @@ def train(cls, ref: DataArray, hist: DataArray, **kwargs) -> TrainAdjust:
Training target, usually a reference time series drawn from observations.
hist : DataArray
Training data, usually a model output whose biases are to be adjusted.
- \*\*kwargs
+ **kwargs : dict
Algorithm-specific keyword arguments, see class doc.
"""
kwargs = parse_group(cls._train, kwargs)
@@ -299,7 +299,7 @@ def train(cls, ref: DataArray, hist: DataArray, **kwargs) -> TrainAdjust:
obj.set_dataset(ds)
return obj
- def adjust(self, sim: DataArray, *args, **kwargs):
+ def adjust(self, sim: DataArray, *args: xr.DataArray, **kwargs):
r"""Return bias-adjusted data.
Refer to the class documentation for the algorithm details.
@@ -308,9 +308,9 @@ def adjust(self, sim: DataArray, *args, **kwargs):
----------
sim : DataArray
Time series to be bias-adjusted, usually a model output.
- args : xr.DataArray
+ *args : xr.DataArray
Other DataArrays needed for the adjustment (usually none).
- \*\*kwargs
+ **kwargs : dict
Algorithm-specific keyword arguments, see class doc.
"""
skip_checks = kwargs.pop("skip_input_checks", False)
@@ -388,7 +388,7 @@ def adjust(
Training data, usually a model output whose biases are to be adjusted.
sim : DataArray
Time series to be bias-adjusted, usually a model output.
- \*\*kwargs
+ **kwargs : dict
Algorithm-specific keyword arguments, see class doc.
Returns
@@ -1768,13 +1768,11 @@ class MBCn(TrainAdjust):
The random matrices are generated following a method laid out by :cite:t:`sdba-mezzadri_how_2007`.
+ Only "time" and "time.dayofyear" (with a suitable window) are implemented as possible values for `group`.
+
References
----------
:cite:cts:`sdba-cannon_multivariate_2018,sdba-cannon_mbc_2020,sdba-pitie_n-dimensional_2005,sdba-mezzadri_how_2007,sdba-szekely_testing_2004`
-
- Notes
- -----
- Only "time" and "time.dayofyear" (with a suitable window) are implemented as possible values for `group`.
"""
_allow_diff_calendars = False
diff --git a/xclim/sdba/base.py b/xclim/sdba/base.py
index 03ec7d091..c060a054b 100644
--- a/xclim/sdba/base.py
+++ b/xclim/sdba/base.py
@@ -346,7 +346,7 @@ def apply(
(if False, default) (including the window and dimensions given through `add_dims`).
The dimensions used are also written in the "group_compute_dims" attribute.
If all the input arrays are missing one of the 'add_dims', it is silently omitted.
- \*\*kwargs
+ **kwargs : dict
Other keyword arguments to pass to the function.
Returns
@@ -524,7 +524,7 @@ def map_blocks( # noqa: C901
----------
reduces : sequence of strings
Name of the dimensions that are removed by the function.
- \*\*out_vars
+ **out_vars
Mapping from variable names in the output to their *new* dimensions.
The placeholders ``Grouper.PROP``, ``Grouper.DIM`` and ``Grouper.ADD_DIMS`` can be used to signify
``group.prop``,``group.dim`` and ``group.add_dims`` respectively.
@@ -747,7 +747,7 @@ def map_groups(
if main_only is False, and [Grouper.DIM] if main_only is True. See :py:func:`map_blocks`.
main_only : bool
Same as for :py:meth:`Grouper.apply`.
- \*\*out_vars
+ **out_vars
Mapping from variable names in the output to their *new* dimensions.
The placeholders ``Grouper.PROP``, ``Grouper.DIM`` and ``Grouper.ADD_DIMS`` can be used to signify
``group.prop``,``group.dim`` and ``group.add_dims``, respectively.
diff --git a/xclim/sdba/processing.py b/xclim/sdba/processing.py
index af29e17eb..d102143ca 100644
--- a/xclim/sdba/processing.py
+++ b/xclim/sdba/processing.py
@@ -37,6 +37,7 @@
"stack_variables",
"standardize",
"to_additive_space",
+ "uniform_noise_like",
"unstack_variables",
"unstandardize",
]
diff --git a/xclim/sdba/properties.py b/xclim/sdba/properties.py
index ec4eae75f..86eec98f0 100644
--- a/xclim/sdba/properties.py
+++ b/xclim/sdba/properties.py
@@ -1255,7 +1255,7 @@ def _return_value(
Return the value corresponding to a return period. On average, the return value will be exceeded
(or not exceed for op='min') every return period (e.g. 20 years). The return value is computed by first extracting
the variable annual maxima/minima, fitting a statistical distribution to the maxima/minima,
- then estimating the percentile associated with the return period (eg. 95th percentile (1/20) for 20 years)
+ then estimating the percentile associated with the return period (e.g. 95th percentile (1/20) for 20 years)
Parameters
----------
diff --git a/xclim/testing/conftest.py b/xclim/testing/conftest.py
index eb0fa520f..15ae34a5c 100644
--- a/xclim/testing/conftest.py
+++ b/xclim/testing/conftest.py
@@ -1,4 +1,5 @@
-# noqa: D100
+"""Specialized setup for running xclim doctests."""
+
# This file is the setup for the doctest suite.
# This must be run using the following commands:
# python -c "from xclim.testing.utils import run_doctests; run_doctests()"
@@ -27,14 +28,28 @@
@pytest.fixture(autouse=True, scope="session")
-def threadsafe_data_dir(tmp_path_factory):
- """Return a threadsafe temporary directory for storing testing data."""
+def threadsafe_data_dir(tmp_path_factory): # numpydoc ignore=PR01
+ """
+ Return a threadsafe temporary directory for storing testing data.
+
+ Yields
+ ------
+ Path
+ The path to the temporary directory.
+ """
yield Path(tmp_path_factory.getbasetemp().joinpath("data"))
@pytest.fixture(scope="session")
-def nimbus(threadsafe_data_dir, worker_id):
- """Return a nimbus object for the test data."""
+def nimbus(threadsafe_data_dir, worker_id): # numpydoc ignore=PR01
+ """
+ Return a nimbus object for the test data.
+
+ Returns
+ -------
+ nimbus
+ An preconfigured pooch object.
+ """
return _nimbus(
repo=TESTDATA_REPO_URL,
branch=TESTDATA_BRANCH,
@@ -45,8 +60,15 @@ def nimbus(threadsafe_data_dir, worker_id):
@pytest.fixture(scope="session")
-def open_dataset(nimbus):
- """Return a function that opens a dataset from the test data."""
+def open_dataset(nimbus): # numpydoc ignore=PR01
+ """
+ Return a function that opens a dataset from the test data.
+
+ Returns
+ -------
+ function
+ A function that opens a dataset from the test data.
+ """
def _open_session_scoped_file(file: str | os.PathLike, **xr_kwargs):
xr_kwargs.setdefault("cache", True)
@@ -63,7 +85,7 @@ def _open_session_scoped_file(file: str | os.PathLike, **xr_kwargs):
@pytest.fixture(scope="session", autouse=True)
-def is_matplotlib_installed(xdoctest_namespace) -> None:
+def is_matplotlib_installed(xdoctest_namespace) -> None: # numpydoc ignore=PR01
"""Skip tests that require matplotlib if it is not installed."""
def _is_matplotlib_installed():
@@ -77,7 +99,9 @@ def _is_matplotlib_installed():
@pytest.fixture(scope="session", autouse=True)
-def doctest_setup(xdoctest_namespace, nimbus, worker_id, open_dataset) -> None:
+def doctest_setup(
+ xdoctest_namespace, nimbus, worker_id, open_dataset
+) -> None: # numpydoc ignore=PR01
"""Gather testing data on doctest run."""
testing_setup_warnings()
gather_testing_data(worker_cache_dir=nimbus.path, worker_id=worker_id)
@@ -85,7 +109,7 @@ def doctest_setup(xdoctest_namespace, nimbus, worker_id, open_dataset) -> None:
generate_atmos(branch=TESTDATA_BRANCH, cache_dir=nimbus.path)
)
- class AttrDict(dict):
+ class AttrDict(dict): # numpydoc ignore=PR01
"""A dictionary that allows access to its keys as attributes."""
def __init__(self, *args, **kwargs):
diff --git a/xclim/testing/diagnostics.py b/xclim/testing/diagnostics.py
index 5506d53d8..0b28c9a47 100644
--- a/xclim/testing/diagnostics.py
+++ b/xclim/testing/diagnostics.py
@@ -31,8 +31,29 @@
__all__ = ["adapt_freq_graph", "cannon_2015_figure_2", "synth_rainfall"]
-def synth_rainfall(shape, scale=1, wet_freq=0.25, size=1):
- r"""Return gamma distributed rainfall values for wet days.
+def synth_rainfall(
+ shape: float, scale: float = 1.0, wet_freq: float = 0.25, size: int = 1
+) -> np.ndarray:
+ r"""
+ Return gamma distributed rainfall values for wet days.
+
+ The returned values are zero for dry days.
+
+ Parameters
+ ----------
+ shape : float
+ The shape parameter of the gamma distribution.
+ scale : float
+ The scale parameter of the gamma distribution.
+ wet_freq : float
+ The frequency of wet days.
+ size : int
+ The number of values to generate.
+
+ Returns
+ -------
+ np.ndarray
+ The generated values.
Notes
-----
@@ -49,8 +70,17 @@ def synth_rainfall(shape, scale=1, wet_freq=0.25, size=1):
return np.where(is_wet, wet_intensity, 0)
-def cannon_2015_figure_2():
- """Create a graphic similar to figure 2 of Cannon et al. 2015."""
+def cannon_2015_figure_2() -> plt.Figure:
+ """
+ Create a graphic similar to figure 2 of Cannon et al. 2015.
+
+ The figure shows the distributions of the reference, historical and simulated data, as well as the future
+
+ Returns
+ -------
+ plt.Figure
+ The generated figure.
+ """
# noqa: D103
if plt is None:
raise ModuleNotFoundError("Matplotlib not found.")
@@ -130,7 +160,14 @@ def cannon_2015_figure_2():
def adapt_freq_graph():
- """Create a graphic with the additive adjustment factors estimated after applying the adapt_freq method."""
+ """
+ Create a graphic with the additive adjustment factors estimated after applying the adapt_freq method.
+
+ Returns
+ -------
+ plt.Figure
+ The generated figure.
+ """
if plt is None:
raise ModuleNotFoundError("Matplotlib not found.")
diff --git a/xclim/testing/helpers.py b/xclim/testing/helpers.py
index f715f1586..11fb0ac2e 100644
--- a/xclim/testing/helpers.py
+++ b/xclim/testing/helpers.py
@@ -38,7 +38,21 @@ def generate_atmos(
branch: str | os.PathLike[str] | Path,
cache_dir: str | os.PathLike[str] | Path,
) -> dict[str, xr.DataArray]:
- """Create the `atmosds` synthetic testing dataset."""
+ """
+ Create the `atmosds` synthetic testing dataset.
+
+ Parameters
+ ----------
+ branch : str or os.PathLike[str] or Path
+ The branch to use for the testing dataset.
+ cache_dir : str or os.PathLike[str] or Path
+ The directory to store the testing dataset.
+
+ Returns
+ -------
+ dict[str, xr.DataArray]
+ A dictionary of xarray DataArrays.
+ """
with xtu.open_dataset(
"ERA5/daily_surface_cancities_1990-1993.nc",
branch=branch,
@@ -74,7 +88,14 @@ def generate_atmos(
def add_ensemble_dataset_objects() -> dict[str, str]:
- """Create a dictionary of xclim ensemble-related datasets to be patched into the xdoctest namespace."""
+ """
+ Create a dictionary of xclim ensemble-related datasets to be patched into the xdoctest namespace.
+
+ Returns
+ -------
+ dict[str, str]
+ A dictionary of xclim ensemble-related datasets.
+ """
namespace = {
"nc_files_simple": [
"EnsembleStats/BCCAQv2+ANUSPLIN300_ACCESS1-0_historical+rcp45_r1i1p1_1950-2100_tg_mean_YS.nc",
@@ -91,7 +112,14 @@ def add_ensemble_dataset_objects() -> dict[str, str]:
def add_example_file_paths() -> dict[str, str | list[xr.DataArray]]:
- """Create a dictionary of doctest-relevant datasets to be patched into the xdoctest namespace."""
+ """
+ Create a dictionary of doctest-relevant datasets to be patched into the xdoctest namespace.
+
+ Returns
+ -------
+ dict of str or dict of list of xr.DataArray
+ A dictionary of doctest-relevant datasets.
+ """
namespace = {
"path_to_ensemble_file": "EnsembleReduce/TestEnsReduceCriteria.nc",
"path_to_pr_file": "NRCANdaily/nrcan_canada_daily_pr_1990.nc",
@@ -137,7 +165,14 @@ def add_example_file_paths() -> dict[str, str | list[xr.DataArray]]:
def add_doctest_filepaths() -> dict[str, Any]:
- """Overload some libraries directly into the xdoctest namespace."""
+ """
+ Overload some libraries directly into the xdoctest namespace.
+
+ Returns
+ -------
+ dict[str, Any]
+ A dictionary of xdoctest namespace objects.
+ """
namespace: dict = {}
namespace["np"] = np
namespace["xclim"] = xclim
@@ -157,7 +192,31 @@ def test_timeseries(
as_dataset: bool = False,
cftime: bool = False,
) -> xr.DataArray | xr.Dataset:
- """Create a generic timeseries object based on pre-defined dictionaries of existing variables."""
+ """
+ Create a generic timeseries object based on pre-defined dictionaries of existing variables.
+
+ Parameters
+ ----------
+ values : np.ndarray
+ The values of the DataArray.
+ variable : str
+ The name of the DataArray.
+ start : str
+ The start date of the time dimension. Default is "2000-07-01".
+ units : str or None
+ The units of the DataArray. Default is None.
+ freq : str
+ The frequency of the time dimension. Default is daily/"D".
+ as_dataset : bool
+ Whether to return a Dataset or a DataArray. Default is False.
+ cftime : bool
+ Whether to use cftime or not. Default is False.
+
+ Returns
+ -------
+ xr.DataArray or xr.Dataset
+ A DataArray or Dataset with time, lon and lat dimensions.
+ """
if cftime:
coords = xr.cftime_range(start, periods=len(values), freq=freq)
else:
@@ -185,7 +244,19 @@ def test_timeseries(
def _raise_on_compute(dsk: dict):
- """Raise an AssertionError mentioning the number triggered tasks."""
+ """
+ Raise an AssertionError mentioning the number triggered tasks.
+
+ Parameters
+ ----------
+ dsk : dict
+ The dask graph.
+
+ Raises
+ ------
+ AssertionError
+ If the dask computation is triggered.
+ """
raise AssertionError(
f"Not lazy. Computation was triggered with a graph of {len(dsk)} tasks."
)
diff --git a/xclim/testing/sdba_utils.py b/xclim/testing/sdba_utils.py
index 88353af5b..6018f64a6 100644
--- a/xclim/testing/sdba_utils.py
+++ b/xclim/testing/sdba_utils.py
@@ -14,11 +14,27 @@
from xclim.sdba.utils import equally_spaced_nodes
-__all__ = ["cannon_2015_dist", "cannon_2015_rvs", "nancov", "series"]
-
-
-def series(values, name, start="2000-01-01"):
- """Create a DataArray with time, lon and lat dimensions."""
+__all__ = ["cannon_2015_dist", "cannon_2015_rvs", "series"]
+
+
+def series(values: np.ndarray, name: str, start: str = "2000-01-01"):
+ """
+ Create a DataArray with time, lon and lat dimensions.
+
+ Parameters
+ ----------
+ values : np.ndarray
+ The values of the DataArray.
+ name : str
+ The name of the DataArray.
+ start : str
+ The start date of the time dimension.
+
+ Returns
+ -------
+ xr.DataArray
+ A DataArray with time, lon and lat dimensions.
+ """
coords = collections.OrderedDict()
for dim, n in zip(("time", "lon", "lat"), values.shape, strict=False):
if dim == "time":
@@ -52,7 +68,15 @@ def series(values, name, start="2000-01-01"):
)
-def cannon_2015_dist(): # noqa: D103
+def cannon_2015_dist() -> (gamma, gamma, gamma): # noqa: D103
+ """
+ Generate the distributions used in Cannon et al. 2015.
+
+ Returns
+ -------
+ tuple[gamma, gamma, gamma]
+ The reference, historical and simulated distributions.
+ """
# ref ~ gamma(k=4, theta=7.5) mu: 30, sigma: 15
ref = gamma(4, scale=7.5)
@@ -65,7 +89,22 @@ def cannon_2015_dist(): # noqa: D103
return ref, hist, sim
-def cannon_2015_rvs(n, random=True): # noqa: D103
+def cannon_2015_rvs(n: int, random: bool = True) -> list[xr.DataArray]: # noqa: D103
+ """
+ Generate the Random Variables used in Cannon et al. 2015.
+
+ Parameters
+ ----------
+ n : int
+ The number of random variables to generate.
+ random : bool
+ If True, generate random variables. Otherwise, generate evenly spaced nodes.
+
+ Returns
+ -------
+ list[xr.DataArray]
+ A list of DataArrays with time, lon and lat dimensions.
+ """
# Frozen distributions
fd = cannon_2015_dist()
@@ -75,10 +114,4 @@ def cannon_2015_rvs(n, random=True): # noqa: D103
u = equally_spaced_nodes(n, None)
r = [d.ppf(u) for d in fd]
- return map(lambda x: series(x, "pr"), r)
-
-
-def nancov(X):
- """Numpy's cov but dropping observations with NaNs."""
- X_na = np.isnan(X).any(axis=0)
- return np.cov(X[:, ~X_na])
+ return list(map(lambda x: series(x, "pr"), r))
diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py
index 1969b6519..d878cd6bf 100644
--- a/xclim/testing/utils.py
+++ b/xclim/testing/utils.py
@@ -87,7 +87,8 @@
default_testdata_cache = None
TESTDATA_REPO_URL = str(os.getenv("XCLIM_TESTDATA_REPO_URL", default_testdata_repo_url))
-"""Sets the URL of the testing data repository to use when fetching datasets.
+"""
+Sets the URL of the testing data repository to use when fetching datasets.
Notes
-----
@@ -105,7 +106,8 @@
"""
TESTDATA_BRANCH = str(os.getenv("XCLIM_TESTDATA_BRANCH", default_testdata_version))
-"""Sets the branch of the testing data repository to use when fetching datasets.
+"""
+Sets the branch of the testing data repository to use when fetching datasets.
Notes
-----
@@ -123,7 +125,8 @@
"""
TESTDATA_CACHE_DIR = os.getenv("XCLIM_TESTDATA_CACHE_DIR", default_testdata_cache)
-"""Sets the directory to store the testing datasets.
+"""
+Sets the directory to store the testing datasets.
If not set, the default location will be used (based on ``platformdirs``, see :func:`pooch.os_cache`).
@@ -146,22 +149,23 @@
def list_input_variables(
submodules: Sequence[str] | None = None, realms: Sequence[str] | None = None
) -> dict:
- """List all possible variables names used in xclim's indicators.
+ """
+ List all possible variables names used in xclim's indicators.
Made for development purposes. Parses all indicator parameters with the
:py:attr:`xclim.core.utils.InputKind.VARIABLE` or `OPTIONAL_VARIABLE` kinds.
Parameters
----------
- realms: Sequence of str, optional
- Restrict the output to indicators of a list of realms only. Default None, which parses all indicators.
- submodules: str, optional
- Restrict the output to indicators of a list of submodules only. Default None, which parses all indicators.
+ submodules : str, optional
+ Restrict the output to indicators of a list of submodules only. Default None, which parses all indicators.
+ realms : Sequence of str, optional
+ Restrict the output to indicators of a list of realms only. Default None, which parses all indicators.
Returns
-------
dict
- A mapping from variable name to indicator class.
+ A mapping from variable name to indicator class.
"""
from collections import defaultdict # pylint: disable=import-outside-toplevel
@@ -206,7 +210,8 @@ def publish_release_notes(
file: os.PathLike[str] | StringIO | TextIO | None = None,
changes: str | os.PathLike[str] | None = None,
) -> str | None:
- """Format release notes in Markdown or ReStructuredText.
+ """
+ Format release notes in Markdown or ReStructuredText.
Parameters
----------
@@ -221,6 +226,7 @@ def publish_release_notes(
Returns
-------
str, optional
+ If `file` not provided, the formatted release notes.
Notes
-----
@@ -314,7 +320,8 @@ def show_versions(
file: os.PathLike | StringIO | TextIO | None = None,
deps: list[str] | None = None,
) -> str | None:
- """Print the versions of xclim and its dependencies.
+ """
+ Print the versions of xclim and its dependencies.
Parameters
----------
@@ -326,6 +333,7 @@ def show_versions(
Returns
-------
str or None
+ If `file` not provided, the versions of xclim and its dependencies.
"""
dependencies: list[str]
if deps is None:
@@ -429,7 +437,15 @@ def testing_setup_warnings():
def load_registry(
branch: str = TESTDATA_BRANCH, repo: str = TESTDATA_REPO_URL
) -> dict[str, str]:
- """Load the registry file for the test data.
+ """
+ Load the registry file for the test data.
+
+ Parameters
+ ----------
+ branch : str
+ Branch of the repository to use when fetching testing datasets.
+ repo : str
+ URL of the repository to use when fetching testing datasets.
Returns
-------
@@ -483,7 +499,8 @@ def nimbus( # noqa: PR01
cache_dir: str | Path = TESTDATA_CACHE_DIR,
data_updates: bool = True,
):
- """Pooch registry instance for xclim test data.
+ """
+ Pooch registry instance for xclim test data.
Parameters
----------
@@ -574,6 +591,7 @@ def _downloader(
return _nimbus
+# FIXME: This function is soon to be deprecated.
# idea copied from raven that it borrowed from xclim that borrowed it from xarray that was borrowed from Seaborn
def open_dataset(
name: str | os.PathLike[str],
@@ -583,7 +601,8 @@ def open_dataset(
cache_dir: str | os.PathLike[str] | None = TESTDATA_CACHE_DIR,
**kwargs,
) -> Dataset:
- r"""Open a dataset from the online GitHub-like repository.
+ r"""
+ Open a dataset from the online GitHub-like repository.
If a local copy is found then always use that to avoid network traffic.
@@ -595,16 +614,17 @@ def open_dataset(
URL to OPeNDAP folder where the data is stored. If supplied, supersedes github_url.
branch : str
Branch of the repository to use when fetching datasets.
- repo: str
+ repo : str
URL of the repository to use when fetching testing datasets.
cache_dir : Path
The directory in which to search for and write cached data.
- \*\*kwargs
+ **kwargs : dict
For NetCDF files, keywords passed to :py:func:`xarray.open_dataset`.
Returns
-------
Union[Dataset, Path]
+ The dataset.
Raises
------
@@ -613,7 +633,7 @@ def open_dataset(
See Also
--------
- xarray.open_dataset
+ xarray.open_dataset : Open and read a dataset from a file or file-like object.
"""
if cache_dir is None:
raise ValueError(
@@ -653,7 +673,8 @@ def populate_testing_data(
branch: str = TESTDATA_BRANCH,
local_cache: Path = TESTDATA_CACHE_DIR,
) -> None:
- """Populate the local cache with the testing data.
+ """
+ Populate the local cache with the testing data.
Parameters
----------
@@ -666,10 +687,6 @@ def populate_testing_data(
local_cache : Path
The path to the local cache. Defaults to the location set by the platformdirs library.
The testing data will be downloaded to this local cache.
-
- Returns
- -------
- None
"""
# Create the Pooch instance
n = nimbus(repo=repo, branch=branch, cache_dir=temp_folder or local_cache)
@@ -704,8 +721,26 @@ def gather_testing_data(
worker_cache_dir: str | os.PathLike[str] | Path,
worker_id: str,
_cache_dir: str | os.PathLike[str] | None = TESTDATA_CACHE_DIR,
-):
- """Gather testing data across workers."""
+) -> None:
+ """
+ Gather testing data across workers.
+
+ Parameters
+ ----------
+ worker_cache_dir : str or Path
+ The directory to store the testing data.
+ worker_id : str
+ The worker ID.
+ _cache_dir : str or Path, optional
+ The directory to store the testing data. Default is None.
+
+ Raises
+ ------
+ ValueError
+ If the cache directory is not set.
+ FileNotFoundError
+ If the testing data is not found.
+ """
if _cache_dir is None:
raise ValueError(
"The cache directory must be set. "
@@ -740,7 +775,20 @@ def gather_testing_data(
def audit_url(url: str, context: str | None = None) -> str:
- """Check if the URL is well-formed.
+ """
+ Check if the URL is well-formed.
+
+ Parameters
+ ----------
+ url : str
+ The URL to check.
+ context : str, optional
+ Additional context to include in the error message. Default is None.
+
+ Returns
+ -------
+ str
+ The URL if it is well-formed.
Raises
------