From 6f973aa81eff7aee9082358d83ad90eb009af317 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:12:12 -0500 Subject: [PATCH 01/31] reconfigure codespell, add numpydoc-validation, use Python3.10 conventions --- .pre-commit-config.yaml | 14 ++++++--- Makefile | 31 ++++++++++--------- .../XCLIM_calculate_index-Exemple.ipynb | 13 ++------ docs/references.bib | 2 +- pyproject.toml | 27 +++++++++++++++- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 56181b7ae..a075c90f5 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.8.7 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,12 @@ 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: ^docs/|^tests/ - repo: https://github.com/gitleaks/gitleaks rev: v8.21.2 hooks: diff --git a/Makefile b/Makefile index 898f27d5a..eac2729fa 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 + 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/pyproject.toml b/pyproject.toml index 082108e7a..73170f1c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,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] @@ -248,6 +248,31 @@ 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" + "GL01", # "Docstring text (summary) should start in the line immediately after the opening quotes (not in the same line, or leaving a blank line in between)" + "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 + '^Process ', + '^Assess ', + '^Access ' +] + [tool.pytest.ini_options] minversion = "7.0" addopts = [ From 4f498538e10382ed6bf396cb1b16af96b617a83c Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:13:16 -0500 Subject: [PATCH 02/31] first pass at spelling and docstring corrections --- tests/test_sdba/test_adjustment.py | 7 +++- xclim/analog.py | 2 +- xclim/core/bootstrapping.py | 2 +- xclim/core/calendar.py | 36 +++++++++++-------- xclim/core/dataflags.py | 2 +- xclim/core/formatting.py | 16 +++++---- xclim/core/indicator.py | 4 +-- xclim/core/units.py | 2 +- xclim/core/utils.py | 4 +-- xclim/ensembles/_robustness.py | 6 ++-- xclim/indices/_agro.py | 4 +-- xclim/indices/_conversion.py | 4 +-- xclim/indices/_multivariate.py | 4 +-- xclim/indices/run_length.py | 2 +- xclim/indices/stats.py | 6 ++-- xclim/sdba/_adjustment.py | 2 +- xclim/sdba/adjustment.py | 18 +++++----- xclim/sdba/base.py | 2 +- xclim/sdba/processing.py | 1 + xclim/testing/sdba_utils.py | 8 +---- xclim/testing/utils.py | 57 +++++++++++++++++++++++++----- 21 files changed, 118 insertions(+), 71 deletions(-) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index 706bc5a29..40a32f8a3 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/xclim/analog.py b/xclim/analog.py index 6e16dcb1d..1f02e72b0 100644 --- a/xclim/analog.py +++ b/xclim/analog.py @@ -45,7 +45,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 diff --git a/xclim/core/bootstrapping.py b/xclim/core/bootstrapping.py index 6e4897d6d..e5d522050 100644 --- a/xclim/core/bootstrapping.py +++ b/xclim/core/bootstrapping.py @@ -83,7 +83,7 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray: ---------- compute_index_func : Callable Index function. - \*\*kwargs + \*\*kwargs : dict Arguments to `func`. Returns diff --git a/xclim/core/calendar.py b/xclim/core/calendar.py index 14b47084f..7456dbf72 100644 --- a/xclim/core/calendar.py +++ b/xclim/core/calendar.py @@ -143,22 +143,25 @@ def common_calendar(calendars: Sequence[str], join="outer") -> str: """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 string + 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 -------- @@ -230,6 +233,10 @@ 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 """ if isinstance(source, xr.Dataset): return source.map( @@ -488,7 +495,7 @@ def parse_offset(freq: str) -> tuple[int, str, bool, str | None]: Parameters ---------- freq : str - Frequency offset. + Frequency offset. Returns ------- @@ -504,7 +511,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) @@ -1282,8 +1288,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 diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py index e965703a9..e03e90997 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -1,6 +1,6 @@ """ Data Flags -=========== +========== Pseudo-indicators designed to analyse supplied variables for suspicious/erroneous indicator values. """ diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index 45e545090..c8d6cb37b 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -64,14 +64,16 @@ def __init__( self.modifiers = modifiers self.mapping = mapping - def format(self, format_string: str, /, *args: Any, **kwargs: Any) -> str: + def format( + self, format_string: str, /, *args: Any, **kwargs: dict[str, Any] + ) -> str: r"""Format a string. Parameters ---------- format_string: str - \*args: Any - \*\*kwargs: Any + \*args : Any + \*\*kwargs : dict Returns ------- @@ -463,11 +465,13 @@ def gen_call_string(funcname: str, *args, **kwargs) -> str: ---------- funcname : str Name of the function - \*args, \*\*kwargs + \*args : Any Arguments given to the function. + \*\*kwargs : dict + Keyword arguments given to the function. - Example - ------- + 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=)" diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 1822d04c4..57c19d087 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -187,8 +187,8 @@ class 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 diff --git a/xclim/core/units.py b/xclim/core/units.py index 38b21a3ff..1287f9b95 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -1262,7 +1262,7 @@ def declare_relative_units(**units_by_name) -> Callable: Parameters ---------- - \*\*kwargs + \*\*kwargs : 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]`. diff --git a/xclim/core/utils.py b/xclim/core/utils.py index 16e24dab7..70350c6ab 100644 --- a/xclim/core/utils.py +++ b/xclim/core/utils.py @@ -780,8 +780,8 @@ def split_auxiliary_coordinates( aux_coords : Dataset The auxiliary coordinates as a dataset. Might be empty. - Note - ---- + 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. diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py index cfa9f1061..e925581ea 100644 --- a/xclim/ensembles/_robustness.py +++ b/xclim/ensembles/_robustness.py @@ -75,7 +75,7 @@ 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 @@ -137,8 +137,8 @@ def robustness_fractions( # noqa: C901 :cite:cts:`tebaldi_mapping_2011` :cite:cts:`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. diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py index 3a9cf0139..52f9b8dc9 100644 --- a/xclim/indices/_agro.py +++ b/xclim/indices/_agro.py @@ -1170,8 +1170,8 @@ 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 - ------- + Examples + -------- >>> from datetime import datetime >>> from xclim.indices import standardized_precipitation_index >>> ds = xr.open_dataset(path_to_pr_file) diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py index f5f13e0b4..9f3143bbc 100644 --- a/xclim/indices/_conversion.py +++ b/xclim/indices/_conversion.py @@ -1884,9 +1884,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 ---------- diff --git a/xclim/indices/_multivariate.py b/xclim/indices/_multivariate.py index 843604af4..6cd6a4b1c 100644 --- a/xclim/indices/_multivariate.py +++ b/xclim/indices/_multivariate.py @@ -1144,8 +1144,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 diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py index 5d5e71d97..8bf5ab5de 100644 --- a/xclim/indices/run_length.py +++ b/xclim/indices/run_length.py @@ -972,7 +972,7 @@ def season( 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). diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py index 474973c5f..65d25a555 100644 --- a/xclim/indices/stats.py +++ b/xclim/indices/stats.py @@ -575,7 +575,7 @@ def _dist_method_1D( # noqa: N802 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 @@ -610,7 +610,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 +620,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 [] diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index ed7b4e36c..5800b72bf 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 5c8506071..0643e79cc 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -233,7 +233,7 @@ class TrainAdjust(BaseAdjustment): _repr_hide_params = ["hist_calendar", "train_units"] @classmethod - def train(cls, ref: DataArray, hist: DataArray, **kwargs) -> TrainAdjust: + def train(cls, ref: DataArray, hist: DataArray, **kwargs: dict) -> TrainAdjust: r"""Train the adjustment object. Refer to the class documentation for the algorithm details. @@ -244,7 +244,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) @@ -268,7 +268,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. @@ -277,9 +277,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) @@ -357,7 +357,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 @@ -1728,13 +1728,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`. """ @classmethod diff --git a/xclim/sdba/base.py b/xclim/sdba/base.py index 5036058ef..0820e8ff6 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 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/testing/sdba_utils.py b/xclim/testing/sdba_utils.py index 88353af5b..6ea6a8242 100644 --- a/xclim/testing/sdba_utils.py +++ b/xclim/testing/sdba_utils.py @@ -14,7 +14,7 @@ from xclim.sdba.utils import equally_spaced_nodes -__all__ = ["cannon_2015_dist", "cannon_2015_rvs", "nancov", "series"] +__all__ = ["cannon_2015_dist", "cannon_2015_rvs", "series"] def series(values, name, start="2000-01-01"): @@ -76,9 +76,3 @@ def cannon_2015_rvs(n, random=True): # noqa: D103 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]) diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py index dad66b212..682b1f48d 100644 --- a/xclim/testing/utils.py +++ b/xclim/testing/utils.py @@ -152,15 +152,15 @@ def list_input_variables( 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. + 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. 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 @@ -325,6 +325,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: @@ -430,6 +431,13 @@ def load_registry( ) -> dict[str, str]: """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 ------- dict @@ -548,7 +556,7 @@ def open_dataset( 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 @@ -653,8 +661,29 @@ 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. + + Returns + ------- + 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. " @@ -691,6 +720,18 @@ def gather_testing_data( def audit_url(url: str, context: str | None = None) -> str: """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 ------ URLError From 4d6dd45109dfee63fa616f66b39c17e3ff34ceab Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:59:03 -0500 Subject: [PATCH 03/31] synchronize deps --- environment.yml | 4 ++-- pyproject.toml | 2 +- tox.ini | 14 ++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/environment.yml b/environment.yml index a7d54a980..020be561c 100644 --- a/environment.yml +++ b/environment.yml @@ -32,7 +32,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 @@ -65,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 73170f1c0..fa4c5e606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,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", diff --git a/tox.ini b/tox.ini index 6a5320f62..2ecc444e9 100644 --- a/tox.ini +++ b/tox.ini @@ -33,12 +33,13 @@ 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 = @@ -50,7 +51,8 @@ commands = nbqa black --check docs blackdoc --check --exclude=xclim/indices/__init__.py xclim blackdoc --check docs - codespell xclim tests docs + codespell . + numpydoc lint xclim/**.py deptry . yamllint --config-file=.yamllint.yaml xclim commands_post = From 88d121f23cefd8d8428231d358c848364a5ddc63 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:59:56 -0500 Subject: [PATCH 04/31] write docstrings --- xclim/indices/stats.py | 47 ++++++++++++++---------- xclim/sdba/adjustment.py | 2 +- xclim/testing/conftest.py | 41 +++++++++++++++------ xclim/testing/diagnostics.py | 42 +++++++++++++++++++--- xclim/testing/helpers.py | 69 +++++++++++++++++++++++++++++++++--- xclim/testing/sdba_utils.py | 46 +++++++++++++++++++++--- xclim/testing/utils.py | 13 ++----- 7 files changed, 207 insertions(+), 53 deletions(-) diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py index 65d25a555..f5981a1c7 100644 --- a/xclim/indices/stats.py +++ b/xclim/indices/stats.py @@ -98,7 +98,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 @@ -277,7 +277,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( @@ -403,7 +403,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 : 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 indexer is not provided, all values are considered. @@ -435,7 +435,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 +444,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 @@ -468,7 +473,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 @@ -596,8 +601,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 (eg: 'rvs' with size != 1, 'stats' with more than one moment, 'interval', 'support'). Parameters @@ -620,7 +625,7 @@ def dist_method( See Also -------- - 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 +656,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 +666,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 : dict 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`. @@ -747,7 +754,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 : dict 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`. @@ -866,7 +873,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 : dict 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`. @@ -926,17 +933,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 0643e79cc..c50e59a13 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -233,7 +233,7 @@ class TrainAdjust(BaseAdjustment): _repr_hide_params = ["hist_calendar", "train_units"] @classmethod - def train(cls, ref: DataArray, hist: DataArray, **kwargs: dict) -> TrainAdjust: + def train(cls, ref: DataArray, hist: DataArray, **kwargs) -> TrainAdjust: r"""Train the adjustment object. Refer to the class documentation for the algorithm details. diff --git a/xclim/testing/conftest.py b/xclim/testing/conftest.py index eb0fa520f..7ab3dad27 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,26 @@ @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 +58,14 @@ 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 +82,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 +96,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 +106,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..6eea30874 100644 --- a/xclim/testing/diagnostics.py +++ b/xclim/testing/diagnostics.py @@ -31,9 +31,29 @@ __all__ = ["adapt_freq_graph", "cannon_2015_figure_2", "synth_rainfall"] -def synth_rainfall(shape, scale=1, wet_freq=0.25, size=1): +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 ----- The probability density for the Gamma distribution is: @@ -49,8 +69,16 @@ 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 +158,13 @@ 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..5082d4c56 100644 --- a/xclim/testing/helpers.py +++ b/xclim/testing/helpers.py @@ -38,7 +38,20 @@ 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 +87,13 @@ 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", @@ -137,7 +156,13 @@ 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 +182,30 @@ 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 +233,18 @@ 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 6ea6a8242..b3c219175 100644 --- a/xclim/testing/sdba_utils.py +++ b/xclim/testing/sdba_utils.py @@ -17,8 +17,23 @@ __all__ = ["cannon_2015_dist", "cannon_2015_rvs", "series"] -def series(values, name, start="2000-01-01"): - """Create a DataArray with time, lon and lat dimensions.""" +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 +67,14 @@ 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 +87,21 @@ 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,4 +111,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) + return list(map(lambda x: series(x, "pr"), r)) diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py index 682b1f48d..5a1429dbb 100644 --- a/xclim/testing/utils.py +++ b/xclim/testing/utils.py @@ -152,10 +152,10 @@ def list_input_variables( 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. + realms : Sequence of str, optional + Restrict the output to indicators of a list of realms only. Default None, which parses all indicators. Returns ------- @@ -220,6 +220,7 @@ def publish_release_notes( Returns ------- str, optional + If `file` not provided, the formatted release notes. Notes ----- @@ -623,10 +624,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) @@ -673,10 +670,6 @@ def gather_testing_data( _cache_dir : str or Path, optional The directory to store the testing data. Default is None. - Returns - ------- - None - Raises ------ ValueError From 9dc0c49a8a887d6475cf5c328193cd8dd20b97ac Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:11:49 -0500 Subject: [PATCH 05/31] another pass at numpydoc --- tox.ini | 14 +++----------- xclim/analog.py | 48 ++++++++++++++++++++++++++++++++++++++---------- xclim/cli.py | 25 ++++++++++++++----------- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/tox.ini b/tox.ini index 2ecc444e9..043c98a16 100644 --- a/tox.ini +++ b/tox.ini @@ -44,18 +44,10 @@ deps = 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 . - numpydoc lint xclim/**.py - 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 1f02e72b0..82a012acc 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 @@ -129,9 +129,21 @@ def standardize(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]: return x / s, y / s -def metric(func): +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. """ @@ -435,17 +447,31 @@ 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) -> np.ndarray: + """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 @@ -458,7 +484,9 @@ def pivot(x, y): return np.max(np.abs(cx - cy)) - return max(pivot(x, y), pivot(y, x)) # pylint: disable=arguments-out-of-order + return float( + np.max(pivot(x, y), pivot(y, x)) + ) # pylint: disable=arguments-out-of-order @metric @@ -481,7 +509,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..8028340a6 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,7 +454,7 @@ def get_command(self, ctx, cmd_name): "If not specified, xarray decides.", ) @click.pass_context -def cli(ctx, **kwargs): +def cli(ctx, **kwargs): # numpydoc ignore=PR01 """Entry point for the command line interface. Manages the global options. @@ -508,7 +511,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"]: From 258c12a50b255bb14ae7bfa06a764f7eef09cab6 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:45:56 -0500 Subject: [PATCH 06/31] ignore SDBA failures, more formatting --- .pre-commit-config.yaml | 2 +- Makefile | 2 +- xclim/core/_exceptions.py | 4 ++- xclim/core/_types.py | 2 ++ xclim/core/bootstrapping.py | 17 ++++++++-- xclim/core/calendar.py | 68 +++++++++++++++++++++++-------------- xclim/testing/helpers.py | 8 ++++- xclim/testing/utils.py | 6 ++-- 8 files changed, 75 insertions(+), 34 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a075c90f5..ef8f932d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -103,7 +103,7 @@ repos: rev: v1.8.0 hooks: - id: numpydoc-validation - exclude: ^docs/|^tests/ + exclude: ^docs/|^tests/|^xclim/sdba - repo: https://github.com/gitleaks/gitleaks rev: v8.21.2 hooks: diff --git a/Makefile b/Makefile index eac2729fa..a0d4aba12 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ lint: ## check style with flake8 and black python -m blackdoc --check --exclude=xclim/indices/__init__.py xclim python -m blackdoc --check docs codespell . - python -m numpydoc lint xclim/**.py + 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 diff --git a/xclim/core/_exceptions.py b/xclim/core/_exceptions.py index ce91cff69..af5ff3eaf 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] 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 e5d522050..00a2a946a 100644 --- a/xclim/core/bootstrapping.py +++ b/xclim/core/bootstrapping.py @@ -19,11 +19,23 @@ BOOTSTRAP_DIM = "_bootstrap" -def percentile_bootstrap(func): +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 +65,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) @@ -110,7 +122,6 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray: Compute index function using percentile Average output from index function over all resampled time series Else compute index function using original percentile - """ # Identify the input and the percentile arrays from the bound arguments per_key = None diff --git a/xclim/core/calendar.py b/xclim/core/calendar.py index 7456dbf72..4eb261aef 100644 --- a/xclim/core/calendar.py +++ b/xclim/core/calendar.py @@ -146,7 +146,7 @@ def common_calendar(calendars: Sequence[str], join="outer") -> str: Parameters ---------- - calendars : Sequence of string + calendars : Sequence of str List of calendar names. join : {'inner', 'outer'} The criterion for the common calendar. @@ -161,7 +161,7 @@ def common_calendar(calendars: Sequence[str], join="outer") -> str: Returns ------- str - Returns "default" only if all calendars are "default." + Returns "default" only if all calendars are "default". Examples -------- @@ -212,7 +212,7 @@ def convert_doy( align_on: str = "year", missing: Any = np.nan, dim: str = "time", -) -> xr.DataArray: +) -> xr.DataArray | xr.Dataset: """Convert the calendar of day of year (doy) data. Parameters @@ -236,7 +236,8 @@ def convert_doy( Returns ------- - xr.DataArray + xr.DataArray or xr.Dataset + The converted doy data. """ if isinstance(source, xr.Dataset): return source.map( @@ -314,6 +315,7 @@ def ensure_cftime_array(time: Sequence) -> np.ndarray | Sequence[cftime.datetime Returns ------- np.ndarray + An array of cftime.datetime objects. Raises ------ @@ -357,7 +359,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 @@ -443,6 +445,11 @@ def build_climatology_bounds(da: xr.DataArray) -> list[str]: 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() @@ -459,16 +466,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 @@ -568,12 +575,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 -------- @@ -683,18 +691,22 @@ 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) @@ -798,7 +810,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] @@ -828,7 +840,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) @@ -1095,10 +1107,9 @@ def select_time( ) -> DataType: """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 ---------- @@ -1109,8 +1120,8 @@ def select_time( season : string or sequence of strings, 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 + 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. @@ -1444,7 +1455,9 @@ def stack_periods( return out -def unstack_periods(da: xr.DataArray | xr.Dataset, dim: str = "period"): +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. @@ -1453,11 +1466,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/testing/helpers.py b/xclim/testing/helpers.py index 5082d4c56..500632bc6 100644 --- a/xclim/testing/helpers.py +++ b/xclim/testing/helpers.py @@ -110,7 +110,13 @@ 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", diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py index 5a1429dbb..6425b0e30 100644 --- a/xclim/testing/utils.py +++ b/xclim/testing/utils.py @@ -532,6 +532,7 @@ def nimbus( # noqa: PR01 ) +# 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], @@ -553,7 +554,7 @@ 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. @@ -563,6 +564,7 @@ def open_dataset( Returns ------- Union[Dataset, Path] + The dataset. Raises ------ @@ -571,7 +573,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( From 893bc8de3e5c74f0f15f73d7844b1c520cf62ec3 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:41:27 -0500 Subject: [PATCH 07/31] more docstrings, fix regressions --- xclim/analog.py | 8 +-- xclim/core/calendar.py | 9 +-- xclim/core/cfchecks.py | 42 ++++++++++-- xclim/core/datachecks.py | 11 ++- xclim/core/dataflags.py | 67 +++++++++++++----- xclim/core/formatting.py | 8 ++- xclim/indices/run_length.py | 132 +++++++++++++++++++----------------- xclim/indices/stats.py | 12 ++-- 8 files changed, 186 insertions(+), 103 deletions(-) diff --git a/xclim/analog.py b/xclim/analog.py index 82a012acc..145994786 100644 --- a/xclim/analog.py +++ b/xclim/analog.py @@ -447,7 +447,7 @@ def kolmogorov_smirnov(x: np.ndarray, y: np.ndarray) -> float: :cite:cts:`fasano_multidimensional_1987` """ - def pivot(_x: np.ndarray, _y: np.ndarray) -> np.ndarray: + def pivot(_x: np.ndarray, _y: np.ndarray) -> float: """Pivot function to compute the KS statistic. Parameters @@ -482,11 +482,9 @@ def pivot(_x: np.ndarray, _y: np.ndarray) -> np.ndarray: # 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 float( - np.max(pivot(x, y), pivot(y, x)) - ) # pylint: disable=arguments-out-of-order + return max(pivot(x, y), pivot(y, x)) # pylint: disable=arguments-out-of-order @metric diff --git a/xclim/core/calendar.py b/xclim/core/calendar.py index 4eb261aef..21268818e 100644 --- a/xclim/core/calendar.py +++ b/xclim/core/calendar.py @@ -908,6 +908,7 @@ def within_bnds_doy( Returns ------- xarray.DataArray + Boolean array of values within doy. """ low = resample_doy(low, arr) high = resample_doy(high, arr) @@ -1117,17 +1118,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 + 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. diff --git a/xclim/core/cfchecks.py b/xclim/core/cfchecks.py index a84169d39..789dfa635 100644 --- a/xclim/core/cfchecks.py +++ b/xclim/core/cfchecks.py @@ -11,14 +11,31 @@ 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 +50,25 @@ 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..5fa914410 100644 --- a/xclim/core/datachecks.py +++ b/xclim/core/datachecks.py @@ -17,7 +17,9 @@ @datacheck -def check_freq(var: xr.DataArray, freq: str | Sequence[str], strict: bool = True): +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,9 +59,14 @@ def check_freq(var: xr.DataArray, freq: str | Sequence[str], strict: bool = True ) -def check_daily(var: xr.DataArray): +def check_daily(var: xr.DataArray) -> None: """Raise an error if not 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. diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py index e03e90997..d5a1282d1 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -80,11 +80,16 @@ def __str__(self): ] -def register_methods(variable_name=None): - """Register a data flag functioné. +def register_methods(variable_name: str = None): + """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. """ def _register_methods(func): @@ -118,11 +123,14 @@ def tasmax_below_tasmin( 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 -------- @@ -151,11 +159,14 @@ def tas_exceeds_tasmax( 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 -------- @@ -183,11 +194,14 @@ def tas_below_tasmin( 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 -------- @@ -215,11 +229,15 @@ def temperature_extremely_low( Parameters ---------- da : xarray.DataArray + The DataArray of temperatures being examined. thresh : str + The threshold to search array for that will trigger flag if any day exceeds value. + Default is -90 degrees Celsius. Returns ------- xarray.DataArray, [bool] + Boolean array of True where temperatures are below the threshold. Examples -------- @@ -249,11 +267,15 @@ def temperature_extremely_high( Parameters ---------- da : xarray.DataArray + The DatArray of temperature being examined. thresh : str + The threshold to search array for that will trigger flag if any day exceeds value. + Default is 60 degrees Celsius. Returns ------- xarray.DataArray, [bool] + Boolean array of True where temperatures are above the threshold. Examples -------- @@ -282,10 +304,12 @@ def negative_accumulation_values( Parameters ---------- da : xarray.DataArray + The DataArray being examined. Returns ------- xarray.DataArray, [bool] + Boolean array of True where values are negative. Examples -------- @@ -320,6 +344,7 @@ def very_large_precipitation_events( Returns ------- xarray.DataArray, [bool] + Boolean array of True where precipitation values exceed the threshold. Examples -------- @@ -359,6 +384,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 -------- @@ -409,6 +435,7 @@ def wind_values_outside_of_bounds( Returns ------- xarray.DataArray, [bool] + The boolean array of True where values exceed the bounds. Examples -------- @@ -453,6 +480,7 @@ def outside_n_standard_deviations_of_climatology( Returns ------- xarray.DataArray, [bool] + The boolean array of True where values exceed the bounds. Notes ----- @@ -504,6 +532,7 @@ def values_repeating_for_n_or_more_days( Returns ------- xarray.DataArray, [bool] + The boolean array of True where values repeat for `n` or more days. Examples -------- @@ -528,10 +557,12 @@ def percentage_values_outside_of_bounds(da: xarray.DataArray) -> xarray.DataArra Parameters ---------- da : xarray.DataArray + The DataArray being examined. Returns ------- xarray.DataArray, [bool] + The boolean array of True where values exceed the bounds. Examples -------- @@ -581,6 +612,7 @@ def data_flags( # noqa: C901 Returns ------- xarray.Dataset + The Dataset of boolean flag arrays. Examples -------- @@ -601,14 +633,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 +649,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 +660,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 +710,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 +737,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( @@ -739,6 +771,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 c8d6cb37b..094e006c2 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -64,20 +64,22 @@ def __init__( self.modifiers = modifiers self.mapping = mapping - def format( - self, format_string: str, /, *args: Any, **kwargs: dict[str, Any] - ) -> str: + def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: r"""Format a string. Parameters ---------- 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: diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py index 8bf5ab5de..497c07725 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 @@ -81,36 +82,36 @@ def use_ufunc( 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`. + dim : str + The dimension along which to find runs. freq : str - Resampling frequency. - kwargs - Keyword arguments needed in `compute`. + Resampling frequency. + \*\*kwargs + 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( @@ -176,7 +177,7 @@ 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 +200,7 @@ def rle( Returns ------- xr.DataArray + The run length array. """ if da.dtype == bool: da = da.astype(int) @@ -759,20 +761,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) @@ -789,13 +791,13 @@ def runs_with_holes( 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. @@ -855,9 +857,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) @@ -876,7 +878,6 @@ def season_end( 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 +892,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 +903,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: @@ -953,6 +956,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,7 +967,7 @@ 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``) @@ -983,9 +989,9 @@ def season( See Also -------- - season_start - season_end - season_length + season_start : Start of a season. + season_end : End of a season. + season_length : Length of a season. """ 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 @@ -1064,9 +1070,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 @@ -1308,7 +1314,7 @@ def first_run_1d(arr: Sequence[int | float], window: int) -> int | np.nan: Parameters ---------- - arr : Sequence[Union[int, float]] + arr : sequence of int or float Input array. window : int Minimum duration of consecutive run to accumulate values. @@ -1332,12 +1338,12 @@ def statistics_run_1d(arr: Sequence[bool], reducer: str, window: int) -> int: 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 ------- @@ -1463,9 +1469,9 @@ def statistics_run_ufunc( 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. @@ -1534,7 +1540,7 @@ def lazy_indexing( 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`. @@ -1732,6 +1738,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, @@ -1849,11 +1856,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 +1875,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 f5981a1c7..6c32f24dc 100644 --- a/xclim/indices/stats.py +++ b/xclim/indices/stats.py @@ -98,7 +98,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 : dict + \*\*fitkwargs Other arguments passed directly to :py:func:`_fitstart` and to the distribution's `fit`. Returns @@ -403,7 +403,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 : dict + \*\*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 indexer is not provided, all values are considered. @@ -473,7 +473,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 : dict + \*\*fitkwargs Kwargs passed to fit. Returns @@ -666,7 +666,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 : dict + \*\*indexer Indexing parameters to compute the indicator on a temporal subset of the data. It accepts the same arguments as :py:func:`xclim.indices.generic.select_time`. @@ -754,7 +754,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 : dict + \*\*indexer Indexing parameters to compute the indicator on a temporal subset of the data. It accepts the same arguments as :py:func:`xclim.indices.generic.select_time`. @@ -873,7 +873,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 : dict + \*\*indexer Indexing parameters to compute the indicator on a temporal subset of the data. It accepts the same arguments as :py:func:`xclim.indices.generic.select_time`. From 7c9af18ca67e81660d7ffc8d52086f10097df8d2 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:58:44 -0500 Subject: [PATCH 08/31] more formatting --- xclim/indices/generic.py | 179 ++++++++++++++++++++++++------------ xclim/indices/helpers.py | 73 +++++++++------ xclim/indices/run_length.py | 6 +- 3 files changed, 167 insertions(+), 91 deletions(-) diff --git a/xclim/indices/generic.py b/xclim/indices/generic.py index 98515be73..81358e8b5 100644 --- a/xclim/indices/generic.py +++ b/xclim/indices/generic.py @@ -70,7 +70,7 @@ 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 +82,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 +124,7 @@ 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 +142,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 +161,36 @@ 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 +200,18 @@ 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() @@ -207,6 +240,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.") @@ -357,6 +395,7 @@ def get_daily_events( Returns ------- xr.DataArray + The mask array of daily events. """ events = compare(da, op, threshold, constrain) * 1 events = events.where(~(np.isnan(da))) @@ -380,26 +419,26 @@ def spell_mask( 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 +577,8 @@ 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. @@ -572,6 +611,11 @@ def spell_length_statistics( 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. + Examples -------- >>> spell_length_statistics( @@ -635,8 +679,8 @@ 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. @@ -673,9 +717,14 @@ def bivariate_spell_length_statistics( 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") @@ -709,7 +758,7 @@ def 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 ---------- @@ -759,17 +808,18 @@ def season( See Also -------- - xclim.indices.run_length.season_start - xclim.indices.run_length.season_length - xclim.indices.run_length.season_end + 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. """ 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 @@ -813,6 +863,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) @@ -835,9 +886,9 @@ def count_occurrences( ) -> xr.DataArray: """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 +907,7 @@ def count_occurrences( Returns ------- xr.DataArray + The DataArray of counted occurrences. """ threshold = convert_units_to(threshold, data) @@ -878,12 +930,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) @@ -925,6 +978,7 @@ def first_occurrence( Returns ------- xr.DataArray + The DataArray of times of first occurrences. """ threshold = convert_units_to(threshold, data) @@ -971,6 +1025,7 @@ def last_occurrence( Returns ------- xr.DataArray + The DataArray of times of last occurrences. """ threshold = convert_units_to(threshold, data) @@ -1013,6 +1068,7 @@ def spell_length( Returns ------- xr.DataArray + The DataArray of spell lengths. """ threshold = convert_units_to( threshold, @@ -1047,6 +1103,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"] @@ -1086,6 +1143,7 @@ def thresholded_statistics( Returns ------- xr.DataArray + The DataArray for the given thresholded statistic. """ threshold = convert_units_to(threshold, data) @@ -1121,6 +1179,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) @@ -1150,6 +1209,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) @@ -1178,6 +1238,7 @@ def extreme_temperature_range( Returns ------- xr.DataArray + The DataArray for the extreme temperature range. """ high_data = convert_units_to(high_data, low_data) @@ -1307,6 +1368,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) @@ -1343,7 +1405,7 @@ def first_day_threshold_reached( Parameters ---------- - data xr.DataArray + data : xr.DataArray Dataset being evaluated. threshold : str Threshold on which to base evaluation. @@ -1390,16 +1452,16 @@ def _get_zone_bins( 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 = ( @@ -1431,24 +1493,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]) @@ -1544,25 +1606,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..30c57280e 100644 --- a/xclim/indices/helpers.py +++ b/xclim/indices/helpers.py @@ -72,12 +72,22 @@ 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): +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 @@ -94,13 +104,13 @@ def solar_declination(time: xr.DataArray, method="spencer") -> xr.DataArray: 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 ------- @@ -142,8 +152,8 @@ def time_correction_for_solar_angle(time: xr.DataArray) -> xr.DataArray: Parameters ---------- - time: xr.DataArray - Time coordinate. + time : xr.DataArray + Time coordinate. Returns ------- @@ -176,12 +186,12 @@ def eccentricity_correction_factor( 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 ------- @@ -230,7 +240,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 +258,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 @@ -404,15 +414,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 +448,17 @@ 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. @@ -484,18 +496,18 @@ def wind_speed_height_conversion( 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 ---------- @@ -573,7 +585,7 @@ 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 +609,8 @@ def resample_map( Returns ------- - Resampled object. + xr.DataArray or xr.Dataset + Resampled object. """ resample_kwargs = resample_kwargs or {} map_kwargs = map_kwargs or {} diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py index 497c07725..3ed80cd77 100644 --- a/xclim/indices/run_length.py +++ b/xclim/indices/run_length.py @@ -99,12 +99,12 @@ def resample_and_rl( or after the run length algorithms are applied. compute : Callable Run length function to apply. - args : Any + \*args : Any Positional arguments needed in `compute`. - dim : str - The dimension along which to find runs. freq : str Resampling frequency. + dim : str + The dimension along which to find runs. \*\*kwargs Keyword arguments needed in `compute`. From 62a33ce69145e83f6d4f20fd8da3ef82625bc4a3 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:00:04 -0500 Subject: [PATCH 09/31] More formatting --- xclim/core/datachecks.py | 4 +- xclim/core/dataflags.py | 11 +++- xclim/core/formatting.py | 59 ++++++++++++++++---- xclim/core/indicator.py | 115 ++++++++++++++++++++++++++++----------- 4 files changed, 141 insertions(+), 48 deletions(-) diff --git a/xclim/core/datachecks.py b/xclim/core/datachecks.py index 5fa914410..529369a6c 100644 --- a/xclim/core/datachecks.py +++ b/xclim/core/datachecks.py @@ -71,11 +71,11 @@ def check_daily(var: xr.DataArray) -> None: ----- 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]): +def check_common_time(inputs: Sequence[xr.DataArray]) -> None: """Raise an error if the list of inputs doesn't have a single common frequency. Raises diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py index d5a1282d1..82123ad8e 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -7,7 +7,7 @@ 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 @@ -30,7 +30,7 @@ class DataQualityException(Exception): """Raised when any data evaluation checks are flagged as True. - Attributes + Parameters ---------- flag_array : xarray.Dataset Xarray.Dataset of Data Flags. @@ -80,7 +80,7 @@ def __str__(self): ] -def register_methods(variable_name: str = None): +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 string-like input arguments. @@ -90,6 +90,11 @@ def register_methods(variable_name: str = None): ---------- variable_name : str, optional The output variable name template. Default is None. + + Returns + ------- + callable + The function being registered. """ def _register_methods(func): diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index 094e006c2..78d9a80e4 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -19,6 +19,7 @@ import xarray as xr from boltons.funcutils import wraps +from xclim.core.indicator import Indicator from xclim.core.utils import InputKind DEFAULT_FORMAT_PARAMS = { @@ -40,7 +41,17 @@ class AttrFormatter(string.Formatter): """A formatter for frequently used attribute values. - See the doc of format_field() for more details. + 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`. + Cannot include reserved modifier 'r'. + + Notes + ----- + See the doc of :py:meth:`format_field` for more details. """ def __init__( @@ -69,7 +80,7 @@ def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: Parameters ---------- - format_string: str + format_string : str The string to format. \*args : Any Arguments to format. @@ -86,13 +97,25 @@ def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: kwargs.update({k: v}) return super().format(format_string, *args, **kwargs) - def format_field(self, value, format_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 @@ -321,7 +344,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 @@ -389,7 +412,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__, @@ -411,12 +434,22 @@ def update_history( return merged_history -def update_xclim_history(func: Callable): +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) @@ -466,12 +499,17 @@ def gen_call_string(funcname: str, *args, **kwargs) -> str: Parameters ---------- funcname : str - Name of the function + Name of the function. \*args : Any Arguments given to the function. \*\*kwargs : dict Keyword arguments given to the function. + Returns + ------- + str + The formatted string. + Examples -------- >>> A = xr.DataArray([1], dims=("x",), name="A") @@ -652,17 +690,18 @@ def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str: return section -def generate_indicator_docstring(ind) -> str: +def generate_indicator_docstring(ind: Indicator) -> str: """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" diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 57c19d087..56c32d2b8 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: <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. <var name in compute> : <variable official name> ... @@ -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 @@ -210,7 +207,13 @@ 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) @@ -266,10 +269,17 @@ def __init__(self): _indicators_registry[self.__class__].append(weakref.ref(self)) @classmethod - def get_instance(cls): + def get_instance(cls) -> Any: """Return first found instance. - Raises `ValueError` if no instance exists. + Returns + ------- + Any + FIXME: What is the return type? + + Raises + ------ + ValueError : if no instance exists. """ for inst_ref in _indicators_registry[cls]: inst = inst_ref() @@ -312,7 +322,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 +343,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,7 +516,6 @@ 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 @@ -690,7 +699,7 @@ def from_dict( data: dict, identifier: str, module: str | None = None, - ): + ) -> 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 +713,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: @@ -1170,7 +1184,7 @@ def _translate(cf_attrs, names, var_id=None): return attrs @classmethod - def json(cls, args=None): + def json(cls, args: dict | None = None) -> dict: """Return a serializable dictionary representation of the class. Parameters @@ -1179,10 +1193,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} @@ -1285,8 +1303,8 @@ def compute(*args, **kwds): """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. @@ -1296,6 +1314,11 @@ def cfcheck(self, **das): 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: @@ -1310,12 +1333,16 @@ def datacheck(self, **das): 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. """ if self.src_freq is not None: for da in das.values(): @@ -1340,15 +1367,26 @@ 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): + 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 +1395,15 @@ def parameters(self): } @property - def injected_parameters(self): + def injected_parameters(self) -> dict: """Return a dictionary of all injected parameters. Opposite of :py:meth:`Indicator.parameters`. + + Returns + ------- + dict + A dictionary of all injected parameters. """ return { name: param.value @@ -1369,8 +1412,14 @@ 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): From d7082843c6257623c9f853298ed3dbac84f38723 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:09:09 -0500 Subject: [PATCH 10/31] More formatting --- xclim/core/formatting.py | 3 +- xclim/core/indicator.py | 72 ++++++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index 78d9a80e4..43b18b189 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -19,7 +19,6 @@ import xarray as xr from boltons.funcutils import wraps -from xclim.core.indicator import Indicator from xclim.core.utils import InputKind DEFAULT_FORMAT_PARAMS = { @@ -690,7 +689,7 @@ def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str: return section -def generate_indicator_docstring(ind: Indicator) -> str: +def generate_indicator_docstring(ind) -> str: """Generate an indicator's docstring from keywords. Parameters diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 56c32d2b8..cef22a42d 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -212,7 +212,7 @@ def update(self, other: dict) -> None: Parameters ---------- other : dict - A dictionary of parameters to update the current + A dictionary of parameters to update the current. """ for k, v in other.items(): if hasattr(self, k): @@ -222,7 +222,18 @@ 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( @@ -234,12 +245,24 @@ 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 @@ -269,7 +292,7 @@ def __init__(self): _indicators_registry[self.__class__].append(weakref.ref(self)) @classmethod - def get_instance(cls) -> Any: + def get_instance(cls) -> Any: # numpydoc ignore=RT05 """Return first found instance. Returns @@ -523,6 +546,7 @@ def _parse_indice(compute, passed_parameters): # noqa: F841 compute_sig = signature(compute) # Check that the `Parameters` section of the docstring does not include parameters # that are not in the `compute` function signature. + # FIXME: How can we handle `\*args` and `\*\*kwargs` in the Parameters docstring? if not set(params_dict.keys()).issubset(compute_sig.parameters.keys()): raise ValueError( f"Malformed docstring on {compute} : the parameters " @@ -1135,7 +1159,9 @@ def _check_identifier(identifier: str) -> None: ) @classmethod - def translate_attrs(cls, locale: str | Sequence[str], fill_missing: bool = True): + 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`. @@ -1144,9 +1170,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): @@ -1306,8 +1337,8 @@ def compute(*args, **kwds): """ # 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. @@ -1317,7 +1348,7 @@ def cfcheck(self, **das): Parameters ---------- - das : dict + \*\*das : dict A dictionary of DataArrays to check. """ for varname, vardata in das.items(): @@ -1327,8 +1358,8 @@ 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`. @@ -1341,8 +1372,15 @@ def datacheck(self, **das): Parameters ---------- - das : dict + \*\*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(): @@ -1432,8 +1470,8 @@ 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. @@ -1518,7 +1556,7 @@ def _postprocess(self, outs, das, params): return outs -class ReducingIndicator(CheckMissingIndicator): +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. @@ -1646,7 +1684,7 @@ class Hourly(ResamplingIndicator): base_registry["Daily"] = Daily -def add_iter_indicators(module): +def add_iter_indicators(module: ModuleType): """Create an iterable of loaded indicators.""" if not hasattr(module, "iter_indicators"): From 5c7279c9ebf8103fac344d27549b98f3bbf4ede9 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:59:35 -0500 Subject: [PATCH 11/31] More formatting --- xclim/core/indicator.py | 23 +++-- xclim/core/locales.py | 54 +++++++++--- xclim/core/missing.py | 189 ++++++++++++++++++++++++++++++++++------ 3 files changed, 217 insertions(+), 49 deletions(-) diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index cef22a42d..130f79654 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1577,7 +1577,7 @@ def _get_missing_freq(self, params): return None -class ResamplingIndicator(CheckMissingIndicator): +class ResamplingIndicator(CheckMissingIndicator): # numpydoc ignore=PR02 """Indicator that performs a resampling computation. Compared to the base Indicator, this adds the handling of missing data, @@ -1685,7 +1685,13 @@ class Hourly(ResamplingIndicator): def add_iter_indicators(module: ModuleType): - """Create an iterable of loaded indicators.""" + """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(): @@ -1778,12 +1784,11 @@ 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. Values can be translations dictionaries as defined in :ref:`internationalization:Internationalization`. @@ -1797,14 +1802,14 @@ 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. + 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. 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`. Notes ----- @@ -1821,9 +1826,9 @@ def build_indicator_module_from_yaml( # noqa: C901 See Also -------- - xclim.core.indicator + xclim.core.indicator : Indicator build logic. - build_module + build_module : Function to build a module from a dictionary of indicators. """ filepath = Path(filename) diff --git a/xclim/core/locales.py b/xclim/core/locales.py index f68edff1f..fd6c15c64 100644 --- a/xclim/core/locales.py +++ b/xclim/core/locales.py @@ -70,8 +70,16 @@ _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()) @@ -97,7 +105,7 @@ def get_local_dict(locale: str | Sequence[str] | tuple[str, dict]) -> tuple[str, 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. @@ -109,7 +117,7 @@ def get_local_dict(locale: str | Sequence[str] | tuple[str, dict]) -> tuple[str, Returns ------- str - The best fitting locale string + The best fitting locale string. dict The available translations in this locale. """ @@ -141,7 +149,7 @@ 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 +157,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 @@ -202,9 +210,14 @@ def get_local_formatter( 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 +232,13 @@ 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__( @@ -241,7 +260,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,7 +279,7 @@ def read_locale_file( return locdict -def load_locale(locdata: str | Path | dict[str, dict], locale: str): +def load_locale(locdata: str | Path | dict[str, dict], locale: str) -> None: """Load translations from a json file into xclim. Parameters @@ -281,10 +305,14 @@ def generate_local_dict(locale: str, init_english: bool = False) -> dict: 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..fca994bfd 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 not 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 @@ -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. @@ -327,7 +442,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 +452,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. @@ -408,7 +523,7 @@ class Skip(MissingBase): # pylint: disable=missing-class-docstring def __init__(self, da, freq=None, src_timestep=None, **indexer): pass - def is_missing(self, null, count): + def is_missing(self, null, count, **kwargs): """Return whether the values within each period should be considered missing or not.""" return False @@ -422,8 +537,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 +557,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( + da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer +): # noqa: D103 # numpydoc ignore=GL08 + """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( + da: xr.DataArray, + freq: str, + nm: int = 11, + nc: int = 5, + src_timestep: str | None = None, + **indexer, +): # noqa: D103 # numpydoc ignore=GL08 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( + da: xr.DataArray, + freq: str, + tolerance: float, + src_timestep: str | None = None, + **indexer, +): # noqa: D103 # numpydoc ignore=GL08 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( + da: xr.DataArray, freq: str, n: int = 1, src_timestep: str | None = None, **indexer +): # noqa: D103 # numpydoc ignore=GL08 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( + da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer +): # noqa: D103 # numpydoc ignore=GL08 src_timestep = src_timestep or xr.infer_freq(da.time) return FromContext.execute(da, freq, src_timestep, options={}, indexer=indexer) From a1e66311371c3d2bec3784d29b504336ed2c7393 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:37:01 -0500 Subject: [PATCH 12/31] More formatting --- xclim/core/indicator.py | 8 +-- xclim/core/missing.py | 54 ++++++++++++++----- xclim/core/options.py | 98 +++++++++++++++++++++++++--------- xclim/core/units.py | 115 ++++++++++++++++++++++++++-------------- xclim/core/utils.py | 108 ++++++++++++++++++++++++++++--------- 5 files changed, 277 insertions(+), 106 deletions(-) diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 130f79654..ebedd74ae 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1790,9 +1790,9 @@ def build_indicator_module_from_yaml( # noqa: C901 When creating the indicator, the name in the `index_function` field is first sought 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 @@ -1802,8 +1802,8 @@ 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 diff --git a/xclim/core/missing.py b/xclim/core/missing.py index fca994bfd..dc8cd9677 100644 --- a/xclim/core/missing.py +++ b/xclim/core/missing.py @@ -375,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. @@ -405,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." @@ -557,49 +585,49 @@ def execute(cls, da, freq, src_timestep, options, indexer): # user-friendly. This can also be useful for testing. -def missing_any( +def missing_any( # noqa: D103 # numpydoc ignore=GL08 da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer -): # noqa: D103 # numpydoc ignore=GL08 +) -> 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( +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, -): # noqa: D103 # numpydoc ignore=GL08 +) -> 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( +def missing_pct( # noqa: D103 # numpydoc ignore=GL08 da: xr.DataArray, freq: str, tolerance: float, src_timestep: str | None = None, **indexer, -): # noqa: D103 # numpydoc ignore=GL08 +) -> 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( +def at_least_n_valid( # noqa: D103 # numpydoc ignore=GL08 da: xr.DataArray, freq: str, n: int = 1, src_timestep: str | None = None, **indexer -): # noqa: D103 # numpydoc ignore=GL08 +) -> 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( +def missing_from_context( # noqa: D103 # numpydoc ignore=GL08 da: xr.DataArray, freq: str, src_timestep: str | None = None, **indexer -): # noqa: D103 # numpydoc ignore=GL08 +) -> 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..4993b0d75 100644 --- a/xclim/core/options.py +++ b/xclim/core/options.py @@ -96,7 +96,18 @@ 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 +124,25 @@ 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 +150,53 @@ 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. 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 +217,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 +233,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 1287f9b95..5e2926bdf 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -260,7 +260,17 @@ 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. - The string will be parsed to pint then recast to a string by xclim's `pint2cfunits`. + The string will be parsed to `pint` then recast to a string by :py:func:`xclim.core.units.pint2cfunits`. + + Parameters + ---------- + ustr : str + A unit string. + + Returns + ------- + str + The unit string in CF-compliant form. """ return pint2cfunits(units2pint(ustr)) @@ -282,15 +292,16 @@ def pint_multiply( Returns ------- xr.DataArray + The product DataArray. """ a = 1 * units2pint(da) # noqa - f = a * q.to_base_units() + _f = a * q.to_base_units() if out_units: - f = f.to(out_units) + _f = _f.to(out_units) else: - f = f.to_reduced_units() - out: xr.DataArray = da * f.magnitude - out = out.assign_attrs(units=pint2cfunits(f.units)) + _f = _f.to_reduced_units() + out: xr.DataArray = da * _f.magnitude + out = out.assign_attrs(units=pint2cfunits(_f.units)) return out @@ -301,7 +312,7 @@ def str2pint(val: str) -> pint.Quantity: ---------- 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 ------- @@ -350,11 +361,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" @@ -498,7 +509,7 @@ def infer_sampling_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. """ @@ -531,9 +542,19 @@ def ensure_absolute_temperature(units: str) -> str: 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 @@ -552,7 +573,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 @@ -589,6 +615,7 @@ def to_agg_units( Returns ------- xr.DataArray + The DataArray with aggregated values. Examples -------- @@ -815,11 +842,11 @@ 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. @@ -834,6 +861,7 @@ def rate2amount( Returns ------- xr.DataArray or Quantity + The converted variable. The standard_name of `rate` is modified if a conversion is found. Examples -------- @@ -868,7 +896,7 @@ def rate2amount( See Also -------- - amount2rate + amount2rate : Convert an amount to a rate. """ return _rate_and_amount_converter( rate, @@ -896,11 +924,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. @@ -917,10 +945,11 @@ def amount2rate( Returns ------- xr.DataArray or Quantity + The converted variable. The standard_name of `amount` is modified if a conversion is found. See Also -------- - rate2amount + rate2amount : Convert a rate to an amount. """ return _rate_and_amount_converter( amount, @@ -956,7 +985,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) @@ -992,7 +1021,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) @@ -1062,16 +1091,17 @@ def rate2flux( 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. Examples -------- @@ -1090,7 +1120,7 @@ def rate2flux( See Also -------- - flux2rate + flux2rate : Convert a flux to a rate. """ return _flux_and_rate_converter( rate, @@ -1112,16 +1142,17 @@ def flux2rate( 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. Examples -------- @@ -1143,7 +1174,7 @@ def flux2rate( See Also -------- - rate2flux + rate2flux : Convert a rate to a flux. """ return _flux_and_rate_converter( flux, @@ -1262,14 +1293,15 @@ def declare_relative_units(**units_by_name) -> Callable: Parameters ---------- - \*\*kwargs : dict + \*\*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 `<other_var>` or more complex expressions, - like: `<other_var> * [time]`. + such as `<other_var> * [time]`. Returns ------- Callable + The decorated function. Examples -------- @@ -1294,10 +1326,10 @@ def func(da, thresh, thresh2): ... See Also -------- - declare_units + declare_units : A decorator to check units of function arguments. """ - def dec(func): + def dec(func): # numpydoc ignore=GL08 sig = signature(func) # Check if units are valid @@ -1331,7 +1363,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(): @@ -1369,11 +1401,11 @@ def declare_units(**units_by_name) -> Callable: 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 +1413,7 @@ def declare_units(**units_by_name) -> Callable: Returns ------- Callable + The decorated function. Examples -------- @@ -1395,10 +1428,10 @@ def func(tas): ... See Also -------- - declare_relative_units + declare_relative_units : A decorator to check for relative units of function arguments. """ - 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 +1458,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(): diff --git a/xclim/core/utils.py b/xclim/core/utils.py index 70350c6ab..383759d4a 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 @@ -48,11 +49,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,14 +70,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): +def load_module(path: os.PathLike, name: str | None = None) -> ModuleType: """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 -------- Given a path to a module file (.py): @@ -122,6 +137,7 @@ def ensure_chunk_size(da: xr.DataArray, **minchunks: int) -> xr.DataArray: Returns ------- xr.DataArray + The input DataArray, possibly rechunked. """ if not uses_dask(da): return da @@ -158,11 +174,11 @@ 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 +204,26 @@ 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 +251,28 @@ 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: @@ -239,7 +295,7 @@ def _compute_virtual_index( Parameters ---------- - n : array_like + n : array-like The sample sizes. quantiles : array_like The quantiles values. @@ -264,10 +320,10 @@ def _get_gamma(virtual_indexes: np.ndarray, previous_indexes: np.ndarray): 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 ----- @@ -282,20 +338,23 @@ def _get_indexes( ) -> 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. - 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) @@ -329,16 +388,17 @@ def _linear_interpolation( 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)) @@ -364,7 +424,7 @@ def _nan_quantile( 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] From 1ce8d83fa2f663ca32d31babb89c815799b47ca9 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:18:28 -0500 Subject: [PATCH 13/31] handle \\* symbols on parameter entries --- xclim/core/indicator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index ebedd74ae..fc2a7216f 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -544,9 +544,14 @@ def _parse_indice(compute, passed_parameters): # noqa: F841 params_dict = docmeta.pop("parameters", {}) # override parent's parameters compute_sig = signature(compute) + # Remove the \\* symbols from the parameter names + for param in params_dict.keys(): + if "\\*" in param: + sanitized = param.replace("\\*", "") + params_dict[sanitized] = params_dict.pop(param) + # Check that the `Parameters` section of the docstring does not include parameters # that are not in the `compute` function signature. - # FIXME: How can we handle `\*args` and `\*\*kwargs` in the Parameters docstring? if not set(params_dict.keys()).issubset(compute_sig.parameters.keys()): raise ValueError( f"Malformed docstring on {compute} : the parameters " From 4b4216978187e698b7b517ab2aac889a06fecc29 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:32:51 -0500 Subject: [PATCH 14/31] handle \\* symbols on parameter entries, more formatting --- pyproject.toml | 5 +++-- xclim/core/indicator.py | 6 +++--- xclim/indices/fire/_ffdi.py | 13 ++++++------- xclim/indices/generic.py | 4 ++-- xclim/indices/run_length.py | 4 ++-- xclim/indices/stats.py | 10 +++++----- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6bcb5f45..42aac6daf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -267,9 +267,10 @@ exclude = [ ] override_SS05 = [ # override SS05 to allow docstrings starting with these words - '^Process ', + '^Access ', '^Assess ', - '^Access ' + '^Griffiths ', + '^Process ' ] [tool.pytest.ini_options] diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index fc2a7216f..33c6fb0bc 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -545,10 +545,10 @@ def _parse_indice(compute, passed_parameters): # noqa: F841 compute_sig = signature(compute) # Remove the \\* symbols from the parameter names + _sanitized_params_dict = {} for param in params_dict.keys(): - if "\\*" in param: - sanitized = param.replace("\\*", "") - params_dict[sanitized] = params_dict.pop(param) + _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. diff --git a/xclim/indices/fire/_ffdi.py b/xclim/indices/fire/_ffdi.py index a10d340b4..26dd1b8e7 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. @@ -208,7 +207,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. @@ -296,7 +295,7 @@ def griffiths_drought_factor( Returns ------- - df : xr.DataArray + xr.DataArray The limited Griffiths drought factor. Notes diff --git a/xclim/indices/generic.py b/xclim/indices/generic.py index 81358e8b5..0f88ba221 100644 --- a/xclim/indices/generic.py +++ b/xclim/indices/generic.py @@ -606,7 +606,7 @@ 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. @@ -712,7 +712,7 @@ 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. diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py index 3ed80cd77..e5cb3e54d 100644 --- a/xclim/indices/run_length.py +++ b/xclim/indices/run_length.py @@ -76,7 +76,7 @@ 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( @@ -105,7 +105,7 @@ def resample_and_rl( Resampling frequency. dim : str The dimension along which to find runs. - \*\*kwargs + \*\*kwargs : dict Keyword arguments needed in `compute`. Returns diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py index 6c32f24dc..51250e3b9 100644 --- a/xclim/indices/stats.py +++ b/xclim/indices/stats.py @@ -98,7 +98,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 @@ -403,7 +403,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. @@ -666,7 +666,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`. @@ -754,7 +754,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`. @@ -873,7 +873,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`. From e6ddcfe814c0c691fc68d4b07bb617fcaefb9212 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:11:06 -0500 Subject: [PATCH 15/31] ignore missing submodule, more formatting --- .pre-commit-config.yaml | 3 +- xclim/core/utils.py | 46 ++++++++++++++----- xclim/data/__init__.py | 1 - xclim/ensembles/__init__.py | 1 - xclim/ensembles/_base.py | 12 +++-- xclim/ensembles/_filters.py | 18 +++++++- xclim/ensembles/_reduce.py | 62 +++++++++++++++----------- xclim/ensembles/_robustness.py | 28 +++++++++--- xclim/indicators/atmos/__init__.py | 1 - xclim/indicators/atmos/_conversion.py | 9 +++- xclim/indicators/atmos/_precip.py | 14 +++++- xclim/indicators/atmos/_temperature.py | 13 +++++- xclim/indicators/atmos/_wind.py | 2 + xclim/indicators/generic/_stats.py | 2 + xclim/indicators/land/_snow.py | 2 + xclim/indicators/land/_streamflow.py | 12 ++++- xclim/indicators/seaIce/_seaice.py | 5 +-- 17 files changed, 167 insertions(+), 64 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef8f932d8..5f97ef457 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -103,7 +103,8 @@ repos: rev: v1.8.0 hooks: - id: numpydoc-validation - exclude: ^docs/|^tests/|^xclim/sdba + # 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/xclim/core/utils.py b/xclim/core/utils.py index 383759d4a..383a4f8a7 100644 --- a/xclim/core/utils.py +++ b/xclim/core/utils.py @@ -575,6 +575,12 @@ def infer_kind_from_parameter(param) -> InputKind: Parameters ---------- param : Parameter + An inspect.Parameter instance. + + Returns + ------- + InputKind + The appropriate InputKind constant. Notes ----- @@ -631,10 +637,19 @@ 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"} @@ -787,8 +802,18 @@ def adapt_clix_meta_yaml( # noqa: C901 def is_percentile_dataarray(source: xr.DataArray) -> bool: """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) @@ -826,24 +851,25 @@ def split_auxiliary_coordinates( """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. 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. + 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..25990657b 100644 --- a/xclim/ensembles/_base.py +++ b/xclim/ensembles/_base.py @@ -48,7 +48,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 +72,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 ----- @@ -259,8 +258,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 -------- diff --git a/xclim/ensembles/_filters.py b/xclim/ensembles/_filters.py index 338f307ae..4f2d0c475 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 @@ -150,5 +155,16 @@ 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/_reduce.py b/xclim/ensembles/_reduce.py index 797b9f9fd..ebe27e6dd 100644 --- a/xclim/ensembles/_reduce.py +++ b/xclim/ensembles/_reduce.py @@ -28,7 +28,7 @@ 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 +36,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 +113,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 @@ -140,8 +140,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 +150,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 @@ -218,27 +218,27 @@ 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. Notes ----- @@ -269,11 +269,11 @@ def kmeans_reduce_ensemble( Returns ------- list - Selected model indexes (positions) + Selected model indexes (positions). np.ndarray - KMeans clustering results + KMeans clustering results. dict - Dictionary of input data for creating R² profile plot. 'None' when make_graph=False + Dictionary of input data for creating R² profile plot. 'None' when make_graph=False. References ---------- @@ -420,8 +420,13 @@ 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 +450,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 +481,17 @@ def _get_nclust(method: dict, n_sim, rsq, max_clusters): return n_clusters -def plot_rsqprofile(fig_data): +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 e925581ea..677e1e5fe 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,22 @@ """ -def significance_test(func): +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 +72,7 @@ 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 ---------- @@ -384,7 +398,7 @@ 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 +413,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 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..a8b86da33 100644 --- a/xclim/indicators/atmos/_conversion.py +++ b/xclim/indicators/atmos/_conversion.py @@ -37,7 +37,14 @@ 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..b93d97736 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,17 @@ 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..600404183 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,16 @@ 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..ee3faee14 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,15 @@ 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 From 5bd2a60d799e8bb93829fb8e8a203d0e447f1a29 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:30:48 -0500 Subject: [PATCH 16/31] final adjustments --- pyproject.toml | 5 +- xclim/core/units.py | 1 + xclim/indices/_agro.py | 74 +++++---- xclim/indices/_anuclim.py | 27 ++-- xclim/indices/_conversion.py | 288 +++++++++++++++++---------------- xclim/indices/_hydrology.py | 14 +- xclim/indices/_multivariate.py | 37 +++-- xclim/indices/_simple.py | 13 +- xclim/indices/_synoptic.py | 3 +- xclim/indices/_threshold.py | 77 ++++----- xclim/indices/fire/_cffwis.py | 104 +++++++----- 11 files changed, 335 insertions(+), 308 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 42aac6daf..35a7f77c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -269,8 +269,11 @@ override_SS05 = [ # override SS05 to allow docstrings starting with these words '^Access ', '^Assess ', + '^Days ', + '^Degree-days ', '^Griffiths ', - '^Process ' + '^Process ', + '^Statistics ' ] [tool.pytest.ini_options] diff --git a/xclim/core/units.py b/xclim/core/units.py index 5e2926bdf..189de95da 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -47,6 +47,7 @@ "infer_context", "infer_sampling_units", "lwethickness2amount", + "pint2cfattrs", "pint2cfunits", "pint_multiply", "rate2amount", diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py index 52f9b8dc9..1b82d9340 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 @@ -355,7 +356,7 @@ def biologically_effective_degree_days( Returns ------- xarray.DataArray, [K days] - Biologically effective growing degree days (BEDD) + Biologically effective growing degree days (BEDD). Warnings -------- @@ -542,7 +543,7 @@ 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, @@ -645,7 +646,6 @@ def dryness_index( References ---------- :cite:cts:`tonietto_multicriteria_2004,riou_determinisme_1994` - """ if parse_offset(freq) != (1, "Y", True, "JAN"): raise ValueError(f"Freq not allowed: {freq}. Must be `YS` or `YS-JAN`") @@ -839,8 +839,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,21 +853,21 @@ 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 + xclim.indicators.atmos.potential_evapotranspiration : Potential evapotranspiration calculation. Returns ------- @@ -942,7 +942,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 +984,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 ----- @@ -1133,9 +1136,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 +1151,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`. @@ -1283,7 +1286,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 +1297,7 @@ def standardized_precipitation_evapotranspiration_index( See Also -------- - standardized_precipitation_index + standardized_precipitation_index : Standardized Precipitation Index. """ fitkwargs = fitkwargs or {} @@ -1478,7 +1481,7 @@ 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. @@ -1493,7 +1496,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 +1584,29 @@ 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 ----- @@ -1636,21 +1642,23 @@ def chill_portions( @declare_units(tas="[temperature]") def chill_units(tas: xarray.DataArray, 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, - the potential for bud breaking. + The Utah model assigns a weight to each hour depending on the temperature recognising that high temperatures can + actually decrease the potential for bud breaking. Parameters ---------- tas : xr.DataArray Hourly temperature. + freq : str + Resampling frequency. Returns ------- xr.DataArray, [unitless] - Chill units using the Utah model + Chill units using the Utah model. Examples -------- diff --git a/xclim/indices/_anuclim.py b/xclim/indices/_anuclim.py index 14ef28afb..6f2e8a997 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 @@ -81,7 +82,7 @@ def isothermality( Returns ------- xarray.DataArray, [%] - Isothermality + Isothermality. Notes ----- @@ -120,7 +121,7 @@ def temperature_seasonality( Returns ------- xarray.DataArray, [%] - Mean temperature coefficient of variation + Mean temperature coefficient of variation. freq : str Resampling frequency. @@ -173,7 +174,7 @@ def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArr Returns ------- xarray.DataArray, [%] - Precipitation coefficient of variation + Precipitation coefficient of variation. Examples -------- @@ -235,7 +236,7 @@ def tg_mean_warmcold_quarter( Returns ------- xarray.DataArray, [same as tas] - Mean temperature of {op} quarter + Mean temperature of {op} quarter. Examples -------- @@ -281,7 +282,7 @@ def tg_mean_wetdry_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 +298,7 @@ def tg_mean_wetdry_quarter( Returns ------- xarray.DataArray, [same as tas] - Mean temperature of {op} quarter + Mean temperature of {op} quarter. Notes ----- @@ -332,7 +333,7 @@ def prcptot_wetdry_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,7 +348,7 @@ def prcptot_wetdry_quarter( Returns ------- xarray.DataArray, [length] - Precipitation of {op} quarter + Precipitation of {op} quarter. Examples -------- @@ -392,8 +393,8 @@ def prcptot_warmcold_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 +410,7 @@ def prcptot_warmcold_quarter( Returns ------- xarray.DataArray, [mm] - Precipitation of {op} quarter + Precipitation of {op} quarter. Notes ----- @@ -487,7 +488,7 @@ def prcptot_wetdry_period( Returns ------- xarray.DataArray, [length] - Precipitation of {op} period + Precipitation of {op} period. Notes ----- diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py index 9f3143bbc..032e66e4c 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,25 @@ 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 ----- @@ -163,9 +164,9 @@ 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 ------- @@ -207,19 +208,19 @@ def heat_index(tas: xr.DataArray, hurs: xr.DataArray) -> xr.DataArray: def tas(tasmin: xr.DataArray, tasmax: xr.DataArray) -> xr.DataArray: """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 -------- @@ -244,20 +245,19 @@ 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. Examples -------- @@ -268,8 +268,7 @@ def uas_vas_2_sfcwind( 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°. + 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") @@ -306,17 +305,16 @@ def sfcwind_2_uas_vas( 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 -------- @@ -359,7 +357,7 @@ def saturation_vapor_pressure( 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 +367,7 @@ def saturation_vapor_pressure( Returns ------- xarray.DataArray, [Pa] - Saturation vapour pressure. + Saturation Vapour Pressure. Notes ----- @@ -511,13 +509,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 +529,7 @@ def relative_humidity( Returns ------- xr.DataArray, [%] - Relative humidity. + Relative Humidity. Notes ----- @@ -636,7 +635,7 @@ 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 +643,7 @@ def specific_humidity( Parameters ---------- tas : xr.DataArray - Temperature array + Mean Temperature. hurs : xr.DataArray Relative Humidity. ps : xr.DataArray @@ -663,7 +662,7 @@ def specific_humidity( Returns ------- xarray.DataArray, [dimensionless] - Specific humidity. + Specific Humidity. Notes ----- @@ -738,16 +737,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 ----- @@ -796,9 +795,9 @@ def snowfall_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"} @@ -807,7 +806,7 @@ def snowfall_approximation( Returns ------- xarray.DataArray, [same units as pr] - Solid precipitation flux. + Solid Precipitation Flux. Notes ----- @@ -825,6 +824,10 @@ def snowfall_approximation( References ---------- :cite:cts:`verseghy_class_2009,melton_atmosphericvarscalcf90_2019` + + See Also + -------- + rain_approximation : Rainfall approximation from total precipitation and temperature. """ prsn: xr.DataArray if method == "binary": @@ -894,9 +897,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"} @@ -914,7 +917,7 @@ def rain_approximation( See Also -------- - snowfall_approximation + snowfall_approximation : Snowfall approximation from total precipitation and temperature. """ prra: xr.DataArray = pr - snowfall_approximation( pr, tas, thresh=thresh, method=method @@ -935,19 +938,20 @@ def snd_to_snw( 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 ----- @@ -981,16 +985,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 ----- @@ -1022,24 +1026,24 @@ def prsn_to_prsnd( 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 ---------- @@ -1064,24 +1068,23 @@ def prsnd_to_prsn( 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 ---------- @@ -1334,29 +1337,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 +1371,7 @@ def potential_evapotranspiration( Returns ------- xarray.DataArray + Potential Evapotranspiration. Notes ----- @@ -1839,38 +1844,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 ------- @@ -1940,16 +1945,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 ----- @@ -1988,26 +1993,25 @@ def mean_radiant_temperature( 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 ----- @@ -2087,7 +2091,7 @@ def wind_profile( h_r: Quantified, method: str = "power_law", **kwds, -): +) -> 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. @@ -2095,20 +2099,25 @@ def wind_profile( 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:: @@ -2116,7 +2125,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") @@ -2152,9 +2160,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. @@ -2166,7 +2175,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 -------- @@ -2204,7 +2213,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..e35f2f599 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 @@ -53,13 +54,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) @@ -116,7 +115,7 @@ def snd_max(snd: xr.DataArray, freq: str = "YS-JUL") -> xr.DataArray: Parameters ---------- - snw : xarray.DataArray + snd : xarray.DataArray Snow depth (mass per area). freq : str Resampling frequency. @@ -170,7 +169,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 @@ -262,7 +261,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 @@ -287,8 +286,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 6cd6a4b1c..ea50ed415 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 @@ -84,7 +85,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 +98,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: "<". @@ -171,7 +172,7 @@ def cold_and_dry_days( Parameters ---------- tas : xarray.DataArray - Mean daily temperature values + Mean daily temperature values. pr : xarray.DataArray Daily precipitation. tas_per : xarray.DataArray @@ -201,7 +202,6 @@ def cold_and_dry_days( References ---------- :cite:cts:`beniston_trends_2009` - """ tas_per = convert_units_to(tas_per, tas) thresh = resample_doy(tas_per, tas) @@ -237,7 +237,7 @@ def warm_and_dry_days( Parameters ---------- tas : xarray.DataArray - Mean daily temperature values + Mean daily temperature values. pr : xarray.DataArray Daily precipitation. tas_per : xarray.DataArray @@ -267,7 +267,6 @@ def warm_and_dry_days( References ---------- :cite:cts:`beniston_trends_2009` - """ tas_per = convert_units_to(tas_per, tas) thresh = resample_doy(tas_per, tas) @@ -303,7 +302,7 @@ def warm_and_wet_days( Parameters ---------- tas : xarray.DataArray - Mean daily temperature values + Mean daily temperature values. pr : xarray.DataArray Daily precipitation. tas_per : xarray.DataArray @@ -373,7 +372,7 @@ def cold_and_wet_days( Parameters ---------- tas : xarray.DataArray - Mean daily temperature values + Mean daily temperature values. pr : xarray.DataArray Daily precipitation. tas_per : xarray.DataArray @@ -825,7 +824,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 @@ -907,7 +906,7 @@ def liquid_precip_ratio( See Also -------- - winter_rain_ratio + winter_rain_ratio : The ratio of rainfall to total precipitation during winter. """ if prsn is None and tas is not None: prsn = snowfall_approximation(pr, tas=tas, thresh=thresh, method="binary") @@ -1096,12 +1095,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") @@ -1262,7 +1261,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") @@ -1674,7 +1672,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 +1690,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) @@ -1728,7 +1725,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 @@ -1839,7 +1836,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 @@ -1882,6 +1879,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..c13ab9e7e 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 @@ -86,7 +87,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 ----- @@ -335,7 +336,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`. @@ -529,7 +530,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) @@ -593,7 +594,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 ----- @@ -721,7 +722,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 ----- diff --git a/xclim/indices/_synoptic.py b/xclim/indices/_synoptic.py index a27beeb80..0c1b3d3c8 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 diff --git a/xclim/indices/_threshold.py b/xclim/indices/_threshold.py index dba665096..25bca2407 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 @@ -215,8 +216,7 @@ def cold_spell_frequency( ) -> xarray.DataArray: 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 +231,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=("<", "<=")) @@ -473,8 +472,7 @@ def snw_season_start( ) -> xarray.DataArray: 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 +493,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) @@ -1037,8 +1034,8 @@ def 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. @@ -1057,7 +1054,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"} @@ -1099,7 +1096,6 @@ def growing_season_length( References ---------- :cite:cts:`project_team_eca&d_algorithm_2013` - """ return season( tas, @@ -1644,7 +1640,7 @@ def first_snowfall( 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. @@ -1709,7 +1705,6 @@ 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`. @@ -1758,7 +1753,7 @@ def days_with_snow( Parameters ---------- prsn : xarray.DataArray - Snowfall flux + Snowfall flux. low : Quantified Minimum threshold snowfall flux or liquid water equivalent snowfall rate. high : Quantified @@ -2178,7 +2173,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 ------- @@ -2272,7 +2267,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) @@ -3090,10 +3084,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. @@ -3192,15 +3186,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 +3205,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 -------- @@ -3267,9 +3260,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 +3273,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 ----- @@ -3336,14 +3328,14 @@ 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 + xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics. Returns ------- @@ -3393,28 +3385,27 @@ 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 + xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics. Returns ------- @@ -3473,16 +3464,15 @@ 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 + xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics. Returns ------- @@ -3542,17 +3532,16 @@ 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 + xclim.indices.generic.spell_length_statistics : The parent function that computes the spell length statistics. Returns ------- diff --git a/xclim/indices/fire/_cffwis.py b/xclim/indices/fire/_cffwis.py index 6c3a74b4b..25be1bcda 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. @@ -482,20 +482,20 @@ 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 +507,32 @@ 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 +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 +540,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 @@ -566,12 +573,12 @@ def _fire_season( 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 +899,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, @@ -925,17 +932,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 @@ -1197,15 +1204,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 +1310,15 @@ 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 +1331,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 +1354,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 ----- @@ -1427,7 +1445,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 +1465,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 ----- @@ -1524,7 +1542,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 +1560,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 ---------- @@ -1632,7 +1650,7 @@ def fire_season( Returns ------- xr.DataArray - Fire season mask + Fire season mask. References ---------- From 64f5c40b11c08a232760045494710634507d8d51 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:47:43 -0500 Subject: [PATCH 17/31] fix regressions --- tests/test_indicators.py | 2 +- xclim/core/missing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/xclim/core/missing.py b/xclim/core/missing.py index dc8cd9677..ed3aa59dc 100644 --- a/xclim/core/missing.py +++ b/xclim/core/missing.py @@ -551,7 +551,7 @@ class Skip(MissingBase): # pylint: disable=missing-class-docstring def __init__(self, da, freq=None, src_timestep=None, **indexer): pass - def is_missing(self, null, count, **kwargs): + def is_missing(self, null, count): """Return whether the values within each period should be considered missing or not.""" return False From 08781af17e21bce95712f6abc6191a1faf915770 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:05:12 -0500 Subject: [PATCH 18/31] add missing function to __all__ --- xclim/core/units.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xclim/core/units.py b/xclim/core/units.py index 189de95da..fa7bc15fb 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", From d2e9611fa5e5695923c0359cda097f89ddb9424d Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:34:38 -0500 Subject: [PATCH 19/31] add numpydoc to dependencies, update CHANGELOG.rst --- CHANGELOG.rst | 6 ++++++ environment.yml | 1 + pyproject.toml | 1 + 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index edd3e3e23..5d2da7d5b 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`). +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 ^^^^^^^^^ @@ -23,6 +28,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/environment.yml b/environment.yml index e5977f72e..d3beabe90 100644 --- a/environment.yml +++ b/environment.yml @@ -56,6 +56,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 diff --git a/pyproject.toml b/pyproject.toml index 1fe2f8b81..b10b80317 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", From 366b5e36e2b2f4b0a0d6433cf3aedf689bed05cd Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:57:47 -0500 Subject: [PATCH 20/31] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Bourgault <bourgault.pascal@ouranos.ca> Co-authored-by: Éric Dupuis <71575674+coxipi@users.noreply.github.com> --- xclim/core/datachecks.py | 2 +- xclim/core/dataflags.py | 8 +++----- xclim/core/indicator.py | 4 ++-- xclim/core/missing.py | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/xclim/core/datachecks.py b/xclim/core/datachecks.py index 529369a6c..390965205 100644 --- a/xclim/core/datachecks.py +++ b/xclim/core/datachecks.py @@ -60,7 +60,7 @@ def check_freq( def check_daily(var: xr.DataArray) -> None: - """Raise an error if not series has a frequency other that daily, or is not monotonically increasing. + """Raise an error if series has a frequency other that daily, or is not monotonically increasing. Parameters ---------- diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py index 82123ad8e..0c41217e5 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -236,8 +236,7 @@ def temperature_extremely_low( da : xarray.DataArray The DataArray of temperatures being examined. thresh : str - The threshold to search array for that will trigger flag if any day exceeds value. - Default is -90 degrees Celsius. + Threshold below which temperatures are considered problematic and a flag is raised. Default is -90 degrees Celsius. Returns ------- @@ -274,8 +273,7 @@ def temperature_extremely_high( da : xarray.DataArray The DatArray of temperature being examined. thresh : str - The threshold to search array for that will trigger flag if any day exceeds value. - Default is 60 degrees Celsius. + Threshold above which temperatures are considered problematic and a flag is raised. Default is 60 degrees Celsius. Returns ------- @@ -638,7 +636,7 @@ def data_flags( # noqa: C901 ... ) """ - def _get_variable_name(function, _kwargs): # + def _get_variable_name(function, _kwargs): format_args = {} _kwargs = _kwargs or {} for arg, param in signature(function).parameters.items(): diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 33c6fb0bc..4259ba397 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -222,7 +222,7 @@ 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 ---------- @@ -232,7 +232,7 @@ def is_parameter_dict(cls, other: dict) -> bool: Returns ------- bool - Whether other can update a parameter dictionary. + Whether `other` can update a parameter dictionary. """ # Passing compute_name is forbidden. # name is valid, but is handled by the indicator diff --git a/xclim/core/missing.py b/xclim/core/missing.py index ed3aa59dc..65ae8536f 100644 --- a/xclim/core/missing.py +++ b/xclim/core/missing.py @@ -109,7 +109,7 @@ def execute( 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. + If no indexer is given, all values are considered. Returns ------- From 04caf25779821c760fbda5fdde17ec62754548fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:58:41 +0000 Subject: [PATCH 21/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xclim/core/dataflags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/core/dataflags.py b/xclim/core/dataflags.py index 0c41217e5..8bec3ef56 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -636,7 +636,7 @@ def data_flags( # noqa: C901 ... ) """ - def _get_variable_name(function, _kwargs): + def _get_variable_name(function, _kwargs): format_args = {} _kwargs = _kwargs or {} for arg, param in signature(function).parameters.items(): From 5205801a1f51d47f86edbc11bb27b5e65e86e1d3 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:14:26 -0500 Subject: [PATCH 22/31] WIP - remove GL01 exception, small fixes --- pyproject.toml | 1 - xclim/analog.py | 30 ++++++--- xclim/core/formatting.py | 62 ++++++++++++------- xclim/indices/generic.py | 130 ++++++++++++++++++++++++--------------- 4 files changed, 142 insertions(+), 81 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b10b80317..011ad2f67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -253,7 +253,6 @@ checks = [ "all", # report on all checks, except the below "ES01", # "No extended summary found" "EX01", # "No examples section found" - "GL01", # "Docstring text (summary) should start in the line immediately after the opening quotes (not in the same line, or leaving a blank line in between)" "GL06", # "Found unknown section \"{section}\"" "SA01", # "See Also section not found", "SS01" # "No summary found" diff --git a/xclim/analog.py b/xclim/analog.py index 145994786..35d7cc854 100644 --- a/xclim/analog.py +++ b/xclim/analog.py @@ -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 @@ -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 ---------- @@ -130,7 +132,8 @@ def standardize(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]: def metric(func: Callable): - """Register a metric function in the `metrics` mapping and add some preparation/checking code. + """ + Register a metric function in the `metrics` mapping and add some preparation/checking code. Parameters ---------- @@ -178,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. @@ -212,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. @@ -249,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. @@ -382,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. @@ -426,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. @@ -448,7 +456,8 @@ def kolmogorov_smirnov(x: np.ndarray, y: np.ndarray) -> float: """ def pivot(_x: np.ndarray, _y: np.ndarray) -> float: - """Pivot function to compute the KS statistic. + """ + Pivot function to compute the KS statistic. Parameters ---------- @@ -491,7 +500,8 @@ def pivot(_x: np.ndarray, _y: np.ndarray) -> float: 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: diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index 43b18b189..428094559 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -38,14 +38,16 @@ class AttrFormatter(string.Formatter): - """A formatter for frequently used attribute values. + """ + A formatter for frequently used attribute values. Parameters ---------- - mapping : dict[str, Sequence[str]] + mapping : dict of str, sequence of 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`. + 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 @@ -58,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__() @@ -75,7 +79,8 @@ def __init__( self.mapping = mapping def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: - r"""Format a string. + r""" + Format a string. Parameters ---------- @@ -97,7 +102,8 @@ def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: return super().format(format_string, *args, **kwargs) def format_field(self, value, format_spec: str) -> str: - """Format a value given a formatting spec. + """ + 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. @@ -232,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`. @@ -279,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'}"). @@ -335,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: "<input name> : <input attribute>". @@ -387,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 "[<timestamp>] <new_name>: <hist_str> - xclim version: <xclim.__version__>." @@ -434,7 +444,8 @@ def update_history( def update_xclim_history(func: Callable) -> Callable: - """Decorator that auto-generates and fills the history attribute. + """ + 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 @@ -487,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 < >. @@ -536,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 ---------- @@ -562,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 ---------- @@ -610,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 ---------- @@ -622,6 +637,7 @@ def _gen_parameters_section( Returns ------- str + The formatted section. """ section = "Parameters\n----------\n" for name, param in parameters.items(): @@ -656,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 ---------- @@ -666,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: @@ -690,7 +708,8 @@ 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 ---------- @@ -735,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/indices/generic.py b/xclim/indices/generic.py index 0f88ba221..c7657a45a 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: - r"""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 ---------- @@ -124,7 +125,8 @@ def select_rolling_resample_op( out_units=None, **indexer, ) -> xr.DataArray: - r"""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 ---------- @@ -161,7 +163,8 @@ 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 ---------- @@ -179,7 +182,8 @@ def doymax(da: xr.DataArray) -> xr.DataArray: 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 ---------- @@ -200,7 +204,8 @@ def doymin(da: xr.DataArray) -> xr.DataArray: def default_freq(**indexer) -> str: - r"""Return the default frequency. + r""" + Return the default frequency. Parameters ---------- @@ -230,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. @@ -280,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 ---------- @@ -308,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 ---------- @@ -341,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]`. @@ -371,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 ---------- @@ -384,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: @@ -391,11 +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 - The mask array of daily events. """ events = compare(da, op, threshold, constrain) * 1 events = events.where(~(np.isnan(da))) @@ -413,7 +423,8 @@ 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. @@ -578,7 +589,8 @@ def spell_length_statistics( resample_before_rl: bool = True, **indexer, ) -> xr.DataArray | Sequence[xr.DataArray]: - r"""Generate statistic on spells lengths. + 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. @@ -616,6 +628,11 @@ def spell_length_statistics( 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( @@ -644,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( @@ -680,7 +692,8 @@ def bivariate_spell_length_statistics( resample_before_rl: bool = True, **indexer, ) -> xr.DataArray | Sequence[xr.DataArray]: - r"""Generate statistic on spells lengths based on two variables. + 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. @@ -754,7 +767,8 @@ 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 @@ -785,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") @@ -805,12 +825,6 @@ 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 : 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. """ thresh = convert_units_to(thresh, data, context="infer") cond = compare(data, op, thresh, constrain=constrain) @@ -840,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. @@ -884,7 +899,8 @@ 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: Then the thresholding is performed as condition(data, threshold), @@ -920,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 ---------- @@ -956,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. @@ -1003,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. @@ -1046,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. @@ -1089,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 ---------- @@ -1119,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. @@ -1158,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. @@ -1195,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 ---------- @@ -1224,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 ---------- @@ -1256,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 ---------- @@ -1351,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 ---------- @@ -1398,7 +1425,8 @@ 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. @@ -1447,7 +1475,8 @@ 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 ---------- @@ -1485,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. @@ -1500,7 +1530,7 @@ def get_zones( Right boundary of the last zone. zone_step : Quantity, optional Size of zones. - bins : xr.DataArray or list of Quantity], optional + 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`\ [. @@ -1551,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 ---------- @@ -1588,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. From d3802fd3110bb0b5a66bf05bac46840464184b23 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:03:08 -0500 Subject: [PATCH 23/31] WIP - no more \* when docstring is raw --- .flake8 | 2 ++ xclim/analog.py | 2 +- xclim/core/bootstrapping.py | 2 +- xclim/core/formatting.py | 16 ++++++++-------- xclim/core/indicator.py | 6 +++--- xclim/core/locales.py | 2 +- xclim/core/missing.py | 16 ++++++++-------- xclim/core/options.py | 4 ++-- xclim/core/units.py | 4 ++-- xclim/core/utils.py | 4 ++-- xclim/ensembles/_base.py | 4 ++-- xclim/ensembles/_filters.py | 2 +- xclim/ensembles/_reduce.py | 2 +- xclim/ensembles/_robustness.py | 2 +- xclim/indicators/atmos/_conversion.py | 2 +- xclim/indices/_agro.py | 6 +++--- xclim/indices/_conversion.py | 2 +- xclim/indices/_simple.py | 2 +- xclim/indices/_threshold.py | 12 ++++++------ xclim/indices/fire/_cffwis.py | 6 +++--- xclim/indices/generic.py | 10 +++++----- xclim/indices/run_length.py | 4 ++-- xclim/indices/stats.py | 18 +++++++++--------- xclim/sdba/_adjustment.py | 2 +- xclim/sdba/adjustment.py | 8 ++++---- xclim/sdba/base.py | 6 +++--- xclim/testing/utils.py | 2 +- 27 files changed, 75 insertions(+), 73 deletions(-) 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/xclim/analog.py b/xclim/analog.py index 35d7cc854..239856463 100644 --- a/xclim/analog.py +++ b/xclim/analog.py @@ -46,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 : dict + **kwargs : dict Any other parameter passed directly to the dissimilarity method. Returns diff --git a/xclim/core/bootstrapping.py b/xclim/core/bootstrapping.py index 00a2a946a..b7dab3353 100644 --- a/xclim/core/bootstrapping.py +++ b/xclim/core/bootstrapping.py @@ -95,7 +95,7 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray: ---------- compute_index_func : Callable Index function. - \*\*kwargs : dict + **kwargs : dict Arguments to `func`. Returns diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index 428094559..e76e6502c 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -86,9 +86,9 @@ def format(self, format_string: str, /, *args: Any, **kwargs: dict) -> str: ---------- format_string : str The string to format. - \*args : Any + *args : Any Arguments to format. - \*\*kwargs : dict + **kwargs : dict Keyword arguments to format. Returns @@ -352,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 @@ -361,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. @@ -405,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. @@ -511,9 +511,9 @@ def gen_call_string(funcname: str, *args, **kwargs) -> str: ---------- funcname : str Name of the function. - \*args : Any + *args : Any Arguments given to the function. - \*\*kwargs : dict + **kwargs : dict Keyword arguments given to the function. Returns diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 4259ba397..0ce7e8e2e 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -547,7 +547,7 @@ def _parse_indice(compute, passed_parameters): # noqa: F841 # Remove the \\* symbols from the parameter names _sanitized_params_dict = {} for param in params_dict.keys(): - _sanitized_params_dict[param.replace("\\*", "")] = params_dict[param] + _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 @@ -1353,7 +1353,7 @@ def cfcheck(self, **das) -> None: Parameters ---------- - \*\*das : dict + **das : dict A dictionary of DataArrays to check. """ for varname, vardata in das.items(): @@ -1377,7 +1377,7 @@ def datacheck(self, **das) -> None: Parameters ---------- - \*\*das : dict + **das : dict A dictionary of DataArrays to check. Raises diff --git a/xclim/core/locales.py b/xclim/core/locales.py index fd6c15c64..ff086c407 100644 --- a/xclim/core/locales.py +++ b/xclim/core/locales.py @@ -157,7 +157,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 diff --git a/xclim/core/missing.py b/xclim/core/missing.py index 65ae8536f..ed644b3ce 100644 --- a/xclim/core/missing.py +++ b/xclim/core/missing.py @@ -70,7 +70,7 @@ class MissingBase: Resampling frequency. src_timestep : str, Optional The expected input frequency. If not given, it will be inferred from the input array. - \*\*indexer : Indexer + **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. @@ -153,7 +153,7 @@ def is_null( Input data. freq : str Resampling frequency, from the periods defined in :ref:`timeseries.resampling`. - \*\*indexer : {dim: indexer}, optional + **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. @@ -191,7 +191,7 @@ def prepare( 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. @@ -279,7 +279,7 @@ def validate(**kwargs) -> bool: Parameters ---------- - \*\*kwargs : dict + **kwargs : dict Options arguments. Returns @@ -312,7 +312,7 @@ class MissingAny(MissingBase): Resampling frequency. 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. @@ -337,7 +337,7 @@ def is_missing( Boolean array indicating which values are null. count : xr.DataArray Array of expected number of valid values. - \*\*kwargs : dict + **kwargs : dict Additional arguments. Returns @@ -375,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. @@ -480,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. diff --git a/xclim/core/options.py b/xclim/core/options.py index 4993b0d75..20547b9b7 100644 --- a/xclim/core/options.py +++ b/xclim/core/options.py @@ -133,9 +133,9 @@ def run_check(func, option, *args, **kwargs): Function to run. option : str Option to use. - \*args : tuple + *args : tuple Positional arguments to pass to the function. - \*\*kwargs : dict + **kwargs : dict Keyword arguments to pass to the function. Raises diff --git a/xclim/core/units.py b/xclim/core/units.py index fa7bc15fb..0e3237d05 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -1295,7 +1295,7 @@ def declare_relative_units(**units_by_name) -> Callable: Parameters ---------- - \*\*units_by_name : dict + **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 `<other_var>` or more complex expressions, such as `<other_var> * [time]`. @@ -1407,7 +1407,7 @@ def declare_units(**units_by_name) -> Callable: Parameters ---------- - \*\*units_by_name : dict + **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. diff --git a/xclim/core/utils.py b/xclim/core/utils.py index 383a4f8a7..612f6a69b 100644 --- a/xclim/core/utils.py +++ b/xclim/core/utils.py @@ -130,7 +130,7 @@ 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. @@ -178,7 +178,7 @@ def uses_dask(*das: xr.DataArray | xr.Dataset) -> bool: Parameters ---------- - \*das : xr.DataArray or xr.Dataset + *das : xr.DataArray or xr.Dataset DataArrays or Datasets to check. Returns diff --git a/xclim/ensembles/_base.py b/xclim/ensembles/_base.py index 25990657b..823d10b0b 100644 --- a/xclim/ensembles/_base.py +++ b/xclim/ensembles/_base.py @@ -72,7 +72,7 @@ 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 : dict + **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 @@ -410,7 +410,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 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 4f2d0c475..8a72db7a4 100644 --- a/xclim/ensembles/_filters.py +++ b/xclim/ensembles/_filters.py @@ -16,7 +16,7 @@ def _concat_hist(da: xr.DataArray, **hist) -> xr.DataArray: ---------- 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 diff --git a/xclim/ensembles/_reduce.py b/xclim/ensembles/_reduce.py index ebe27e6dd..e73d431f9 100644 --- a/xclim/ensembles/_reduce.py +++ b/xclim/ensembles/_reduce.py @@ -150,7 +150,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 : dict + **cdist_kwargs : dict All extra arguments are passed as-is to `scipy.spatial.distance.cdist`, see its docs for more information. Returns diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py index 677e1e5fe..309e3d1be 100644 --- a/xclim/ensembles/_robustness.py +++ b/xclim/ensembles/_robustness.py @@ -89,7 +89,7 @@ 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 : dict + **kwargs : dict Other arguments specific to the statistical test. See notes. Returns diff --git a/xclim/indicators/atmos/_conversion.py b/xclim/indicators/atmos/_conversion.py index a8b86da33..cb0259d13 100644 --- a/xclim/indicators/atmos/_conversion.py +++ b/xclim/indicators/atmos/_conversion.py @@ -42,7 +42,7 @@ def cfcheck(self, **das) -> None: Parameters ---------- - \*\*das : Mapping[str, xarray.DataArray] + **das : Mapping[str, xarray.DataArray] The input data arrays. """ for varname, vardata in das.items(): diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py index 1b82d9340..fcff347a0 100644 --- a/xclim/indices/_agro.py +++ b/xclim/indices/_agro.py @@ -1151,7 +1151,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 : {dim: indexer}, optional + **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`. @@ -1286,7 +1286,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 : {dim: indexer}, optional + **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`. @@ -1599,7 +1599,7 @@ def chill_portions( Hourly temperature. freq : str Resampling frequency. - \*\*indexer : {dim: indexer}, optional + **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`. diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py index 032e66e4c..39d6c8f63 100644 --- a/xclim/indices/_conversion.py +++ b/xclim/indices/_conversion.py @@ -2106,7 +2106,7 @@ def wind_profile( Reference height. method : {"power_law"} Method to use. Currently only "power_law" is implemented. - \*\*kwds : dict + **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. diff --git a/xclim/indices/_simple.py b/xclim/indices/_simple.py index c13ab9e7e..f9697e9cd 100644 --- a/xclim/indices/_simple.py +++ b/xclim/indices/_simple.py @@ -336,7 +336,7 @@ def frost_days( Freezing temperature. freq : str Resampling frequency. - \*\*indexer : {dim: indexer}, optional + **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`. diff --git a/xclim/indices/_threshold.py b/xclim/indices/_threshold.py index 25bca2407..6495056ac 100644 --- a/xclim/indices/_threshold.py +++ b/xclim/indices/_threshold.py @@ -3193,7 +3193,7 @@ def dry_spell_frequency( 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 : {dim: indexer}, optional + **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. @@ -3261,7 +3261,7 @@ def dry_spell_total_length( 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 : {dim: indexer}, optional + **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. @@ -3328,7 +3328,7 @@ 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 : {dim: indexer}, optional + **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. @@ -3398,7 +3398,7 @@ def wet_spell_frequency( 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 : {dim: indexer}, optional + **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. @@ -3465,7 +3465,7 @@ def wet_spell_total_length( 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 : {dim: indexer}, optional + **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. @@ -3534,7 +3534,7 @@ def wet_spell_max_length( 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 : {dim: indexer}, optional + **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. diff --git a/xclim/indices/fire/_cffwis.py b/xclim/indices/fire/_cffwis.py index 25be1bcda..feffbe97c 100644 --- a/xclim/indices/fire/_cffwis.py +++ b/xclim/indices/fire/_cffwis.py @@ -1354,7 +1354,7 @@ 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 : dict + **params : dict Any other keyword parameters as defined in :py:func:`fire_weather_ufunc` and in :py:data:`default_params`. Returns @@ -1465,7 +1465,7 @@ 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 : dict + **params : dict Any other keyword parameters as defined in `xclim.indices.fire.fire_weather_ufunc` and in :py:data:`default_params`. Returns @@ -1560,7 +1560,7 @@ 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 : dict + **params : dict Any other keyword parameters as defined in `xclim.indices.fire.fire_weather_ufunc` and in :py:data:`default_params`. Returns diff --git a/xclim/indices/generic.py b/xclim/indices/generic.py index c7657a45a..510c6eb45 100644 --- a/xclim/indices/generic.py +++ b/xclim/indices/generic.py @@ -83,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. @@ -144,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. @@ -209,7 +209,7 @@ def default_freq(**indexer) -> str: Parameters ---------- - \*\*indexer : {dim: indexer, } + **indexer : {dim: indexer, } The indexer to use to compute the frequency. Returns @@ -618,7 +618,7 @@ 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 : {dim: indexer, }, optional + **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. @@ -725,7 +725,7 @@ 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 : {dim: indexer, }, optional + **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. diff --git a/xclim/indices/run_length.py b/xclim/indices/run_length.py index e5cb3e54d..df1646676 100644 --- a/xclim/indices/run_length.py +++ b/xclim/indices/run_length.py @@ -99,13 +99,13 @@ def resample_and_rl( or after the run length algorithms are applied. compute : Callable Run length function to apply. - \*args : Any + *args : Any Positional arguments needed in `compute`. freq : str Resampling frequency. dim : str The dimension along which to find runs. - \*\*kwargs : dict + **kwargs : dict Keyword arguments needed in `compute`. Returns diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py index 51250e3b9..a1709ca6d 100644 --- a/xclim/indices/stats.py +++ b/xclim/indices/stats.py @@ -98,7 +98,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 : dict + **fitkwargs : dict Other arguments passed directly to :py:func:`_fitstart` and to the distribution's `fit`. Returns @@ -403,7 +403,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 : {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 indexer is not provided, all values are considered. @@ -473,7 +473,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 Kwargs passed to fit. Returns @@ -574,13 +574,13 @@ def _dist_method_1D( # noqa: N802 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 : dict + **kwargs : dict Other parameters to pass to the function call. Returns @@ -615,7 +615,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 : dict + **kwargs : dict Other parameters to pass to the function call. Returns @@ -666,7 +666,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 : {dim: indexer, }, optional + **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`. @@ -754,7 +754,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 : {dim: indexer, }, optional + **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`. @@ -873,7 +873,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 : {dim: indexer, }, optional + **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`. diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index 5800b72bf..238058824 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 : dict + **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 c7611fddb..2f16cc26e 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -245,7 +245,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 : dict + **kwargs : dict Algorithm-specific keyword arguments, see class doc. """ kwargs = parse_group(cls._train, kwargs) @@ -278,9 +278,9 @@ def adjust(self, sim: DataArray, *args: xr.DataArray, **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 : dict + **kwargs : dict Algorithm-specific keyword arguments, see class doc. """ skip_checks = kwargs.pop("skip_input_checks", False) @@ -358,7 +358,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 : dict + **kwargs : dict Algorithm-specific keyword arguments, see class doc. Returns diff --git a/xclim/sdba/base.py b/xclim/sdba/base.py index 3e66c33aa..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 : dict + **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/testing/utils.py b/xclim/testing/utils.py index d2ea0da89..83fa276ec 100644 --- a/xclim/testing/utils.py +++ b/xclim/testing/utils.py @@ -609,7 +609,7 @@ def open_dataset( 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 : dict + **kwargs : dict For NetCDF files, keywords passed to :py:func:`xarray.open_dataset`. Returns From 4ce36c423dbb07fe471de51bc55e42aae776f0d5 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:03:46 -0500 Subject: [PATCH 24/31] Update xclim/core/indicator.py Co-authored-by: Pascal Bourgault <bourgault.pascal@ouranos.ca> --- xclim/core/indicator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 0ce7e8e2e..dc35f6acd 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -297,8 +297,8 @@ def get_instance(cls) -> Any: # numpydoc ignore=RT05 Returns ------- - Any - FIXME: What is the return type? + Indicator + First instance found of this class in the indicators registry. Raises ------ From 2cac5aafdd901e087749b237e48a78c0e61f0c29 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:12:47 -0500 Subject: [PATCH 25/31] WIP - address shadowing issue --- xclim/core/units.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xclim/core/units.py b/xclim/core/units.py index 0e3237d05..9542ed4b4 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -110,8 +110,8 @@ 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 = {} @@ -297,13 +297,13 @@ def pint_multiply( The product DataArray. """ a = 1 * units2pint(da) # noqa - _f = a * q.to_base_units() + f = a * q.to_base_units() if out_units: - _f = _f.to(out_units) + f = f.to(out_units) else: - _f = _f.to_reduced_units() - out: xr.DataArray = da * _f.magnitude - out = out.assign_attrs(units=pint2cfunits(_f.units)) + f = f.to_reduced_units() + out: xr.DataArray = da * f.magnitude + out = out.assign_attrs(units=pint2cfunits(f.units)) return out From c01da2e9af694f0534581d1060daeb91c01ffdfb Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:31:03 -0500 Subject: [PATCH 26/31] adjust docstrings for GL01, minor corrections --- xclim/cli.py | 3 +- xclim/core/_exceptions.py | 3 +- xclim/core/bootstrapping.py | 17 +- xclim/core/calendar.py | 81 +++-- xclim/core/cfchecks.py | 6 +- xclim/core/datachecks.py | 19 +- xclim/core/dataflags.py | 93 +++--- xclim/core/indicator.py | 85 +++-- xclim/core/locales.py | 44 +-- xclim/core/options.py | 15 +- xclim/core/units.py | 140 +++++---- xclim/core/utils.py | 52 +-- xclim/ensembles/_base.py | 14 +- xclim/ensembles/_filters.py | 12 +- xclim/ensembles/_partitioning.py | 16 +- xclim/ensembles/_reduce.py | 33 +- xclim/ensembles/_robustness.py | 23 +- xclim/indicators/atmos/_conversion.py | 3 +- xclim/indicators/atmos/_precip.py | 3 +- xclim/indicators/atmos/_temperature.py | 3 +- xclim/indicators/land/_streamflow.py | 3 +- xclim/indices/_agro.py | 115 ++++--- xclim/indices/_anuclim.py | 114 ++++--- xclim/indices/_conversion.py | 155 +++++---- xclim/indices/_hydrology.py | 27 +- xclim/indices/_multivariate.py | 150 +++++---- xclim/indices/_simple.py | 67 ++-- xclim/indices/_synoptic.py | 11 +- xclim/indices/_threshold.py | 417 ++++++++++++++----------- xclim/indices/fire/_cffwis.py | 45 ++- xclim/indices/fire/_ffdi.py | 9 +- xclim/indices/helpers.py | 52 +-- xclim/indices/run_length.py | 178 +++++++---- xclim/indices/stats.py | 35 ++- xclim/sdba/properties.py | 2 +- xclim/testing/conftest.py | 9 +- xclim/testing/diagnostics.py | 9 +- xclim/testing/helpers.py | 18 +- xclim/testing/sdba_utils.py | 9 +- xclim/testing/utils.py | 36 ++- 40 files changed, 1283 insertions(+), 843 deletions(-) diff --git a/xclim/cli.py b/xclim/cli.py index 8028340a6..a326fc686 100644 --- a/xclim/cli.py +++ b/xclim/cli.py @@ -455,7 +455,8 @@ def get_command(self, ctx, cmd_name) -> click.Command: # numpydoc ignore=PR01,R ) @click.pass_context def cli(ctx, **kwargs): # numpydoc ignore=PR01 - """Entry point for the command line interface. + """ + Entry point for the command line interface. Manages the global options. """ diff --git a/xclim/core/_exceptions.py b/xclim/core/_exceptions.py index af5ff3eaf..87db9d706 100644 --- a/xclim/core/_exceptions.py +++ b/xclim/core/_exceptions.py @@ -29,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/bootstrapping.py b/xclim/core/bootstrapping.py index b7dab3353..5dbe7a449 100644 --- a/xclim/core/bootstrapping.py +++ b/xclim/core/bootstrapping.py @@ -20,7 +20,8 @@ def percentile_bootstrap(func: Callable) -> Callable: - """Decorator applying a bootstrap step to the calculation of exceedance over a percentile threshold. + """ + Decorator applying a bootstrap step to the calculation of exceedance over a percentile threshold. This feature is experimental. @@ -78,7 +79,8 @@ def wrapper(*args, **kwargs): # numpydoc ignore=GL08 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 @@ -103,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. @@ -122,6 +120,10 @@ def bootstrap_func(compute_index_func: Callable, **kwargs) -> xarray.DataArray: Compute index function using percentile 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 @@ -236,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 21268818e..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,7 +142,8 @@ 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. @@ -213,7 +216,8 @@ def convert_doy( missing: Any = np.nan, dim: str = "time", ) -> xr.DataArray | xr.Dataset: - """Convert the calendar of day of year (doy) data. + """ + Convert the calendar of day of year (doy) data. Parameters ---------- @@ -303,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). @@ -345,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`. @@ -438,7 +444,8 @@ 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 ---------- @@ -456,7 +463,8 @@ def build_climatology_bounds(da: xr.DataArray) -> list[str]: 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), @@ -495,7 +503,8 @@ 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. @@ -535,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 ---------- @@ -566,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. @@ -630,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). @@ -672,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). @@ -712,7 +725,8 @@ def has_similar_doys( 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 ---------- @@ -752,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. @@ -866,7 +881,8 @@ def shift_time(t): # numpydoc ignore=GL08 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 ---------- @@ -894,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 ---------- @@ -918,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 ---------- @@ -968,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. @@ -1039,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. @@ -1106,7 +1126,8 @@ 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`, @@ -1252,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. @@ -1459,7 +1481,8 @@ def 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`. + """ + 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, diff --git a/xclim/core/cfchecks.py b/xclim/core/cfchecks.py index 789dfa635..8e648fe98 100644 --- a/xclim/core/cfchecks.py +++ b/xclim/core/cfchecks.py @@ -20,7 +20,8 @@ @cfcheck 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. + r""" + Check that a variable's attribute has one of the expected values and raise a ValidationError if otherwise. Parameters ---------- @@ -53,7 +54,8 @@ def check_valid(var: xr.DataArray, key: str, expected: str | Sequence[str]): 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. + """ + Perform cfchecks on a DataArray using specifications from xclim's default variables. Parameters ---------- diff --git a/xclim/core/datachecks.py b/xclim/core/datachecks.py index 390965205..dd9316a06 100644 --- a/xclim/core/datachecks.py +++ b/xclim/core/datachecks.py @@ -20,7 +20,8 @@ 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. + """ + Raise an error if not series has not the expected temporal frequency or is not monotonically increasing. Parameters ---------- @@ -60,7 +61,8 @@ def check_freq( def check_daily(var: xr.DataArray) -> None: - """Raise an error if series has a frequency other that daily, or is not monotonically increasing. + """ + Raise an error if series has a frequency other that daily, or is not monotonically increasing. Parameters ---------- @@ -76,7 +78,13 @@ def check_daily(var: xr.DataArray) -> None: @datacheck def check_common_time(inputs: Sequence[xr.DataArray]) -> None: - """Raise an error if the list of inputs doesn't have a single common frequency. + """ + Raise an error if the list of inputs doesn't have a single common frequency. + + Parameters + ---------- + inputs : Sequence of xr.DataArray + Input arrays. Raises ------ @@ -84,11 +92,6 @@ def check_common_time(inputs: Sequence[xr.DataArray]) -> None: - 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 8bec3ef56..f521a8eb5 100644 --- a/xclim/core/dataflags.py +++ b/xclim/core/dataflags.py @@ -28,7 +28,8 @@ class DataQualityException(Exception): - """Raised when any data evaluation checks are flagged as True. + """ + Raised when any data evaluation checks are flagged as `True`. Parameters ---------- @@ -81,7 +82,8 @@ def __str__(self): def register_methods(variable_name: str = None) -> Callable: - """Register a data flag as functional. + """ + Register a data flag as functional. 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. @@ -89,7 +91,7 @@ def register_methods(variable_name: str = None) -> Callable: Parameters ---------- variable_name : str, optional - The output variable name template. Default is None. + The output variable name template. Default is `None`. Returns ------- @@ -123,7 +125,8 @@ 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 ---------- @@ -159,7 +162,8 @@ 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 ---------- @@ -194,7 +198,8 @@ 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 ---------- @@ -229,14 +234,16 @@ 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 - The DataArray of temperatures being examined. + Temperature. thresh : str - Threshold below which temperatures are considered problematic and a flag is raised. Default is -90 degrees Celsius. + Threshold below which temperatures are considered problematic and a flag is raised. + Default is -90 degrees Celsius. Returns ------- @@ -266,12 +273,13 @@ 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 - The DatArray of temperature being examined. + Temperature. thresh : str Threshold above which temperatures are considered problematic and a flag is raised. Default is 60 degrees Celsius. @@ -302,12 +310,13 @@ 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 - The DataArray being examined. + Variable array. Returns ------- @@ -335,12 +344,13 @@ 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. @@ -371,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"} @@ -424,16 +435,17 @@ 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 ------- @@ -469,16 +481,17 @@ 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 ------- @@ -487,7 +500,11 @@ def outside_n_standard_deviations_of_climatology( 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 -------- @@ -500,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( @@ -523,12 +536,13 @@ 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. @@ -555,12 +569,13 @@ 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 - The DataArray being examined. + Variable array. Returns ------- @@ -588,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. @@ -755,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``. diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 0ce7e8e2e..629b0f53e 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -180,7 +180,8 @@ 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". @@ -207,7 +208,8 @@ 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 ---------- @@ -222,7 +224,8 @@ 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 ---------- @@ -245,7 +248,8 @@ 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 ------- @@ -256,7 +260,8 @@ def asdict(self) -> dict: @property def injected(self) -> bool: - """Indicate whether values are injected. + """ + Indicate whether values are injected. Returns ------- @@ -293,7 +298,8 @@ def __init__(self): @classmethod def get_instance(cls) -> Any: # numpydoc ignore=RT05 - """Return first found instance. + """ + Return first found instance. Returns ------- @@ -315,7 +321,8 @@ def get_instance(cls) -> Any: # numpydoc ignore=RT05 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 @@ -729,7 +736,8 @@ def from_dict( identifier: str, module: str | None = None, ) -> Indicator: - """Create an indicator subclass and instance from a dictionary of parameters. + """ + Create an indicator subclass and instance from a dictionary of parameters. Most parameters are passed directly as keyword arguments to the class constructor, except: @@ -1167,7 +1175,8 @@ def _check_identifier(identifier: str) -> None: def translate_attrs( cls, locale: str | Sequence[str], fill_missing: bool = True ) -> dict: - """Return a dictionary of unformatted translated translatable attributes. + """ + Return a dictionary of unformatted translated translatable attributes. Translatable attributes are defined in :py:const:`xclim.core.locales.TRANSLATABLE_ATTRS`. @@ -1221,7 +1230,8 @@ def _translate(cf_attrs, names, var_id=None): @classmethod def json(cls, args: dict | None = None) -> dict: - """Return a serializable dictionary representation of the class. + """ + Return a serializable dictionary representation of the class. Parameters ---------- @@ -1271,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 ---------- @@ -1336,14 +1347,16 @@ 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`. """ # numpydoc ignore=PR01 raise NotImplementedError() def cfcheck(self, **das) -> None: - r"""Compare metadata attributes to CF-Convention standards. + 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. @@ -1364,7 +1377,8 @@ def cfcheck(self, **das) -> None: pass def datacheck(self, **das) -> None: - r"""Verify that input data is valid. + r""" + Verify that input data is valid. When subclassing this method, use functions decorated using `xclim.core.options.datacheck`. @@ -1411,7 +1425,8 @@ def __getattr__(self, attr): @property def n_outs(self) -> int: - """Return the length of all cf_attrs. + """ + Return the length of all cf_attrs. Returns ------- @@ -1422,7 +1437,8 @@ def n_outs(self) -> int: @property def parameters(self) -> dict: - """Create a dictionary of controllable parameters. + """ + Create a dictionary of controllable parameters. Similar to :py:attr:`Indicator._all_parameters`, but doesn't include injected parameters. @@ -1439,9 +1455,10 @@ def parameters(self) -> dict: @property def injected_parameters(self) -> dict: - """Return a dictionary of all injected parameters. + """ + Return a dictionary of all injected parameters. - Opposite of :py:meth:`Indicator.parameters`. + Inverse of :py:meth:`Indicator.parameters`. Returns ------- @@ -1456,7 +1473,8 @@ def injected_parameters(self) -> dict: @property def is_generic(self) -> bool: - """Return True if the indicator is "generic", meaning that it can accept variables with any units. + """ + Return True if the indicator is "generic", meaning that it can accept variables with any units. Returns ------- @@ -1476,7 +1494,8 @@ def _show_deprecation_warning(self): class CheckMissingIndicator(Indicator): # numpydoc ignore=PR01,PR02 - r"""Class adding missing value checks to indicators. + 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. @@ -1562,7 +1581,8 @@ def _postprocess(self, outs, das, params): class ReducingIndicator(CheckMissingIndicator): # numpydoc ignore=PR01,PR02 - """Indicator that performs a time-reducing computation. + """ + Indicator that performs a time-reducing computation. Compared to the base Indicator, this adds the handling of missing data. @@ -1583,7 +1603,8 @@ def _get_missing_freq(self, params): class ResamplingIndicator(CheckMissingIndicator): # numpydoc ignore=PR02 - """Indicator that performs a resampling computation. + """ + Indicator that performs a resampling computation. Compared to the base Indicator, this adds the handling of missing data, and the check of allowed periods. @@ -1690,7 +1711,8 @@ class Hourly(ResamplingIndicator): def add_iter_indicators(module: ModuleType): - """Create an iterable of loaded indicators. + """ + Create an iterable of loaded indicators. Parameters ---------- @@ -1715,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`. @@ -1778,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 @@ -1816,6 +1840,11 @@ def build_indicator_module_from_yaml( # noqa: C901 ModuleType 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 ----- When the given `filename` has no suffix (usually '.yaml' or '.yml'), the function will try to load @@ -1828,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 : Indicator build logic. - - build_module : Function to build a module from a dictionary of indicators. """ filepath = Path(filename) diff --git a/xclim/core/locales.py b/xclim/core/locales.py index ff086c407..bde4c411f 100644 --- a/xclim/core/locales.py +++ b/xclim/core/locales.py @@ -71,7 +71,8 @@ def list_locales() -> list: - """List of loaded locales. + """ + List of loaded locales. Includes all loaded locales, no matter how complete the translations are. @@ -101,7 +102,8 @@ 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 ---------- @@ -109,17 +111,17 @@ def get_local_dict(locale: str | Sequence[str] | tuple[str, dict]) -> tuple[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. dict The available translations in this locale. + + Raises + ------ + UnavailableLocaleError + If the given locale is not available. """ _valid_locales([locale]) @@ -149,7 +151,8 @@ def get_local_attrs( names: Sequence[str] | None = None, append_locale_name: bool = True, ) -> dict: - r"""Get all attributes of an indicator in the requested locales. + r""" + Get all attributes of an indicator in the requested locales. Parameters ---------- @@ -165,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] @@ -206,7 +209,8 @@ 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 ---------- @@ -232,7 +236,8 @@ 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 ---------- @@ -249,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 ---------- @@ -280,7 +286,8 @@ def read_locale_file( def load_locale(locdata: str | Path | dict[str, dict], locale: str) -> None: - """Load translations from a json file into xclim. + """ + Load translations from a json file into xclim. Parameters ---------- @@ -300,7 +307,8 @@ def load_locale(locdata: str | Path | dict[str, dict], locale: str) -> None: 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 ---------- diff --git a/xclim/core/options.py b/xclim/core/options.py index 20547b9b7..dcfd8cf3b 100644 --- a/xclim/core/options.py +++ b/xclim/core/options.py @@ -96,7 +96,8 @@ def _set_metadata_locales(locales): def register_missing_method(name: str) -> Callable: - """Register missing method. + """ + Register missing method. Parameters ---------- @@ -125,7 +126,8 @@ def _register_missing_method(cls): def run_check(func, option, *args, **kwargs): - r"""Run function and customize exception handling based on option. + r""" + Run function and customize exception handling based on option. Parameters ---------- @@ -150,7 +152,8 @@ 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 ---------- @@ -171,7 +174,8 @@ def _run_check(*args, **kwargs): 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. @@ -194,7 +198,8 @@ def _run_check(*args, **kwargs): class set_options: # numpydoc ignore=PR01,PR02 - r"""Set options for xclim in a controlled context. + r""" + Set options for xclim in a controlled context. Parameters ---------- diff --git a/xclim/core/units.py b/xclim/core/units.py index 9542ed4b4..909808a8d 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -118,7 +118,8 @@ # 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`. @@ -142,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 ---------- @@ -206,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 ---------- @@ -226,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 ---------- @@ -260,7 +264,8 @@ 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`. @@ -280,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 ---------- @@ -308,7 +314,8 @@ 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 ---------- @@ -336,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`. @@ -448,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 ---------- @@ -492,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 ---------- @@ -503,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). 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) @@ -540,7 +550,8 @@ 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. @@ -567,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. @@ -599,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 ---------- @@ -834,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 @@ -855,15 +869,19 @@ def rate2amount( 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 - The converted variable. The standard_name of `rate` is modified if a conversion is found. + See Also + -------- + amount2rate : Convert an amount to a rate. Examples -------- @@ -895,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 : Convert an amount to a rate. """ return _rate_and_amount_converter( rate, @@ -916,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 @@ -939,16 +954,16 @@ def amount2rate( out_units : str, optional Specific output units, if needed. - Raises - ------ - ValueError - If the time coordinate is irregular and `sampling_rate_from_coord` is False (default). - 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). + See Also -------- rate2amount : Convert a rate to an amount. @@ -966,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`. @@ -1003,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`. @@ -1086,7 +1103,8 @@ 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`. @@ -1105,6 +1123,10 @@ def rate2flux( xr.DataArray The converted flux value. + See Also + -------- + flux2rate : Convert a flux to a rate. + Examples -------- The following converts an array of snowfall rate in mm/s to snowfall flux in kg m-2 s-1, @@ -1119,10 +1141,6 @@ def rate2flux( 'kg m-2 s-1' >>> float(prsn[0]) 0.1 - - See Also - -------- - flux2rate : Convert a flux to a rate. """ return _flux_and_rate_converter( rate, @@ -1137,7 +1155,8 @@ 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`. @@ -1156,6 +1175,10 @@ def flux2rate( xr.DataArray The converted rate value. + See Also + -------- + rate2flux : Convert a rate to a flux. + Examples -------- The following converts an array of snowfall flux in kg m-2 s-1 to snowfall flux in mm/s, @@ -1173,10 +1196,6 @@ def flux2rate( 'mm s-1' >>> float(prsnd[0]) 1.0 - - See Also - -------- - rate2flux : Convert a rate to a flux. """ return _flux_and_rate_converter( flux, @@ -1190,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 ---------- @@ -1270,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. """ @@ -1288,7 +1309,8 @@ 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. @@ -1305,6 +1327,10 @@ def declare_relative_units(**units_by_name) -> Callable: Callable The decorated function. + See Also + -------- + declare_units : A decorator to check units of function arguments. + Examples -------- In the following function definition: @@ -1325,10 +1351,6 @@ def func(da, thresh, thresh2): ... temperature_func = declare_units(da="[temperature]")(func) This call will replace the "<da>" by "[temperature]" everywhere needed. - - See Also - -------- - declare_units : A decorator to check units of function arguments. """ def dec(func): # numpydoc ignore=GL08 @@ -1400,7 +1422,8 @@ def wrapper(*args, **kwargs): # numpydoc ignore=GL08 # 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 an 'in_units' attribute. @@ -1417,6 +1440,10 @@ def declare_units(**units_by_name) -> Callable: Callable The decorated function. + See Also + -------- + declare_relative_units : A decorator to check for relative units of function arguments. + Examples -------- In the following function definition: @@ -1427,10 +1454,6 @@ 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 : A decorator to check for relative units of function arguments. """ def dec(func): # numpydoc ignore=GL08 @@ -1481,7 +1504,8 @@ def wrapper(*args, **kwargs): # numpydoc ignore=GL08 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 612f6a69b..feb3513e2 100644 --- a/xclim/core/utils.py +++ b/xclim/core/utils.py @@ -37,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 ---------- @@ -76,7 +77,8 @@ def _wrapper(*args, **kwargs): def load_module(path: os.PathLike, name: str | None = None) -> ModuleType: - """Load a python module from a python file, optionally changing its name. + """ + Load a python module from a python file, optionally changing its name. Parameters ---------- @@ -121,7 +123,8 @@ def load_module(path: os.PathLike, name: str | None = None) -> ModuleType: 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. @@ -174,7 +177,8 @@ def ensure_chunk_size(da: xr.DataArray, **minchunks: int) -> xr.DataArray: def uses_dask(*das: xr.DataArray | xr.Dataset) -> bool: - r"""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 ---------- @@ -204,7 +208,8 @@ 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 ---------- @@ -251,7 +256,8 @@ 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 ---------- @@ -289,7 +295,8 @@ 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`. @@ -316,7 +323,8 @@ 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 ---------- @@ -336,7 +344,8 @@ 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. + """ + Get the valid indexes of arr neighbouring virtual_indexes. Parameters ---------- @@ -384,7 +393,8 @@ 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 ---------- @@ -417,7 +427,8 @@ 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. @@ -478,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 @@ -504,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. @@ -570,7 +582,8 @@ 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 ---------- @@ -641,7 +654,8 @@ def infer_kind_from_parameter(param) -> InputKind: def adapt_clix_meta_yaml( # noqa: C901 raw: os.PathLike | StringIO | str, adapted: os.PathLike ) -> None: - """Read in a clix-meta yaml representation and refactor it to fit xclim YAML specifications. + """ + Read in a clix-meta yaml representation and refactor it to fit xclim YAML specifications. Parameters ---------- @@ -800,7 +814,8 @@ 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 quantile or percentiles coordinate, the window is not mandatory. @@ -848,7 +863,8 @@ 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 from its dimension(s) is flagged as auxiliary. diff --git a/xclim/ensembles/_base.py b/xclim/ensembles/_base.py index 823d10b0b..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'). @@ -138,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. @@ -225,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. @@ -390,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 ---------- @@ -410,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 8a72db7a4..bd5d352d0 100644 --- a/xclim/ensembles/_filters.py +++ b/xclim/ensembles/_filters.py @@ -10,7 +10,8 @@ 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 ---------- @@ -67,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 ---------- @@ -108,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 ---------- @@ -155,7 +158,8 @@ def _single_member(da: xr.DataArray, dimensions: dict | None = None) -> xr.DataA def reverse_dict(d: dict) -> dict: - """Reverse dictionary. + """ + Reverse dictionary. Parameters ---------- 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 e73d431f9..fabe81b04 100644 --- a/xclim/ensembles/_reduce.py +++ b/xclim/ensembles/_reduce.py @@ -28,7 +28,8 @@ def make_criteria(ds: xarray.Dataset | xarray.DataArray): - """Reshape 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 @@ -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. @@ -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 @@ -240,6 +243,15 @@ def kmeans_reduce_ensemble( 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 ----- Parameters for method in call must follow these conventions: @@ -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` @@ -423,7 +426,8 @@ def kmeans_reduce_ensemble( def _calc_rsq( z, method: dict, make_graph: bool, n_sim: np.ndarray, random_state, sample_weights ): - """Sub-function to kmeans_reduce_ensemble. + """ + Sub-function to kmeans_reduce_ensemble. Calculates r-square profile (r-square versus number of clusters). """ @@ -482,7 +486,8 @@ def _get_nclust(method: dict, n_sim: int, rsq: float, max_clusters: int): def plot_rsqprofile(fig_data: dict) -> None: - """Create an R² profile plot using kmeans_reduce_ensemble output. + """ + 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. diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py index 309e3d1be..e15ad2f58 100644 --- a/xclim/ensembles/_robustness.py +++ b/xclim/ensembles/_robustness.py @@ -41,7 +41,8 @@ def significance_test(func: Callable) -> Callable: - """Register a significance test for use in :py:func:`robustness_fractions`. + """ + Register a significance test for use in :py:func:`robustness_fractions`. Parameters ---------- @@ -72,7 +73,8 @@ def robustness_fractions( # noqa: C901 weights: xr.DataArray | None = None, **kwargs, ) -> xr.Dataset: - r"""Calculate 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 ---------- @@ -314,7 +316,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`). @@ -398,7 +401,8 @@ def robustness_categories( def robustness_coefficient( fut: xr.DataArray | xr.Dataset, ref: xr.DataArray | xr.Dataset ) -> xr.DataArray | xr.Dataset: - """Calculate the 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`. @@ -413,7 +417,7 @@ def robustness_coefficient( Parameters ---------- - fut : xr.DataArray or 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 @@ -482,7 +486,8 @@ 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. @@ -513,7 +518,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'. """ @@ -591,7 +597,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 diff --git a/xclim/indicators/atmos/_conversion.py b/xclim/indicators/atmos/_conversion.py index cb0259d13..4d7ba5f9f 100644 --- a/xclim/indicators/atmos/_conversion.py +++ b/xclim/indicators/atmos/_conversion.py @@ -38,7 +38,8 @@ class Converter(Indicator): """Class for indicators doing variable conversion (dimension-independent 1-to-1 computation).""" def cfcheck(self, **das) -> None: - r"""Verify the CF-compliance of the input data. + r""" + Verify the CF-compliance of the input data. Parameters ---------- diff --git a/xclim/indicators/atmos/_precip.py b/xclim/indicators/atmos/_precip.py index b93d97736..a05d28d22 100644 --- a/xclim/indicators/atmos/_precip.py +++ b/xclim/indicators/atmos/_precip.py @@ -101,7 +101,8 @@ class PrTasxWithIndexing(ResamplingIndicatorWithIndexing): @staticmethod def cfcheck(pr: DataArray, tas: DataArray): - r"""Verify the CF-compliance of the input data. + r""" + Verify the CF-compliance of the input data. Parameters ---------- diff --git a/xclim/indicators/atmos/_temperature.py b/xclim/indicators/atmos/_temperature.py index 600404183..bc652a1bb 100644 --- a/xclim/indicators/atmos/_temperature.py +++ b/xclim/indicators/atmos/_temperature.py @@ -1317,7 +1317,8 @@ class FireSeasonBase(Indicator): keywords = "fire" def cfcheck(self, tas: DataArray, snd: DataArray = None): - r"""Verify the CF-compliance of the input data. + r""" + Verify the CF-compliance of the input data. Parameters ---------- diff --git a/xclim/indicators/land/_streamflow.py b/xclim/indicators/land/_streamflow.py index ee3faee14..1dc5b0786 100644 --- a/xclim/indicators/land/_streamflow.py +++ b/xclim/indicators/land/_streamflow.py @@ -39,7 +39,8 @@ class Streamflow(ResamplingIndicator): @staticmethod def cfcheck(q: DataArray): - r"""Verify the CF-compliance of the input data. + r""" + Verify the CF-compliance of the input data. Parameters ---------- diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py index fcff347a0..8d4d9f2ad 100644 --- a/xclim/indices/_agro.py +++ b/xclim/indices/_agro.py @@ -66,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`. @@ -152,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`. @@ -316,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 @@ -469,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 @@ -494,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 @@ -506,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") @@ -550,19 +554,14 @@ def dryness_index( # numpydoc ignore=SS05 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 @@ -582,6 +581,12 @@ def dryness_index( # numpydoc ignore=SS05 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, @@ -638,14 +643,14 @@ def dryness_index( # numpydoc ignore=SS05 JPm = \max\left( P / 5, N \right) + 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") - - References - ---------- - :cite:cts:`tonietto_multicriteria_2004,riou_determinisme_1994` """ 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. @@ -865,14 +872,14 @@ def water_budget( method : str Method to use to calculate the potential evapotranspiration. - See Also - -------- - xclim.indicators.atmos.potential_evapotranspiration : Potential evapotranspiration calculation. - 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 @@ -1122,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 ---------- @@ -1173,6 +1182,10 @@ 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}` + References + ---------- + :cite:cts:`mckee_relationship_1993` + Examples -------- >>> from datetime import datetime @@ -1203,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"]} @@ -1253,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 @@ -1333,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. @@ -1390,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`. @@ -1484,7 +1496,8 @@ def effective_growing_degree_days( 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. @@ -1584,7 +1597,8 @@ def _apply_chill_portion_one_season(tas_K): def chill_portions( tas: xarray.DataArray, freq: str = "YS", **indexer ) -> xarray.DataArray: - r"""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 @@ -1619,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 @@ -1627,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 @@ -1642,7 +1656,8 @@ def chill_portions( @declare_units(tas="[temperature]") def chill_units(tas: xarray.DataArray, 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 @@ -1660,6 +1675,10 @@ def chill_units(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: xr.DataArray, [unitless] Chill units using the Utah model. + References + ---------- + :cite:cts:`richardson_chill_1974` + Examples -------- >>> from xclim.indices import chill_units @@ -1668,10 +1687,6 @@ def chill_units(tas: xarray.DataArray, freq: str = "YS") -> xarray.DataArray: >>> 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 6f2e8a997..ff1dc4413 100644 --- a/xclim/indices/_anuclim.py +++ b/xclim/indices/_anuclim.py @@ -66,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. @@ -106,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. @@ -121,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 ----- @@ -148,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") @@ -158,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. @@ -176,19 +179,6 @@ def precip_seasonality(pr: xarray.DataArray, freq: str = "YS") -> xarray.DataArr xarray.DataArray, [%] Precipitation coefficient of variation. - Examples - -------- - The following would compute for each grid cell of file `pr.day.nc` the annual precipitation seasonality: - - >>> import xclim.indices as xci - >>> p = xr.open_dataset(path_to_pr_file).pr - >>> pday_seasonality = xci.precip_seasonality(p) - >>> p_weekly = xci.precip_accumulation(p, freq="7D") - - # Input units need to be a rate - >>> p_weekly.attrs["units"] = "mm/week" - >>> pweek_seasonality = xci.precip_seasonality(p_weekly) - Notes ----- According to the ANUCLIM user-guide (:cite:t:`xu_anuclim_2010`, ch. 6), input values should be at a weekly @@ -202,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"): @@ -218,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 @@ -238,15 +242,6 @@ def tg_mean_warmcold_quarter( 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") - Notes ----- According to the ANUCLIM user-guide (:cite:t:`xu_anuclim_2010`, ch. 6), input values should be at a weekly @@ -257,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) @@ -278,7 +282,8 @@ 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, @@ -330,7 +335,8 @@ 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 as @@ -350,14 +356,6 @@ def prcptot_wetdry_quarter( 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") - Notes ----- According to the ANUCLIM user-guide (:cite:t:`xu_anuclim_2010`, ch. 6), input values should be at a weekly @@ -368,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) @@ -390,7 +396,8 @@ 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, @@ -443,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. @@ -472,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. @@ -561,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 39d6c8f63..b8868baef 100644 --- a/xclim/indices/_conversion.py +++ b/xclim/indices/_conversion.py @@ -63,7 +63,8 @@ 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. @@ -156,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. @@ -173,16 +175,16 @@ def heat_index(tas: xr.DataArray, hurs: xr.DataArray) -> xr.DataArray: 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") @@ -206,7 +208,8 @@ 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. @@ -237,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°. @@ -259,16 +263,16 @@ def uas_vas_2_sfcwind( 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. + 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 -------- >>> from xclim.indices import 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") @@ -298,7 +302,8 @@ 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. @@ -352,7 +357,8 @@ 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 ---------- @@ -380,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") @@ -501,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. @@ -566,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 @@ -578,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"): @@ -635,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`. @@ -684,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 @@ -695,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, "") @@ -729,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`. @@ -759,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 @@ -767,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] @@ -788,7 +797,8 @@ 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. @@ -808,6 +818,10 @@ def snowfall_approximation( xarray.DataArray, [same units as pr] Solid Precipitation Flux. + See Also + -------- + rain_approximation : Rainfall approximation from total precipitation and temperature. + Notes ----- The following methods are available to approximate snowfall and are drawn from the @@ -824,10 +838,6 @@ def snowfall_approximation( References ---------- :cite:cts:`verseghy_class_2009,melton_atmosphericvarscalcf90_2019` - - See Also - -------- - rain_approximation : Rainfall approximation from total precipitation and temperature. """ prsn: xr.DataArray if method == "binary": @@ -889,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. @@ -910,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 : Snowfall approximation from total precipitation and temperature. """ prra: xr.DataArray = pr - snowfall_approximation( pr, tas, thresh=thresh, method=method @@ -933,7 +944,8 @@ 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 ---------- @@ -977,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 ---------- @@ -1021,7 +1034,8 @@ 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 ---------- @@ -1063,7 +1077,8 @@ 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 ---------- @@ -1102,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 ---------- @@ -1126,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 ---------- @@ -1156,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 @@ -1242,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 ---------- @@ -1258,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 @@ -1267,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") @@ -1329,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. @@ -1836,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. @@ -1937,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. @@ -1986,7 +2008,8 @@ 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. @@ -2092,7 +2115,8 @@ def wind_profile( method: str = "power_law", **kwds, ) -> xr.DataArray: - r"""Wind speed at a given height estimated from the wind speed at a reference height. + 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. @@ -2152,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. diff --git a/xclim/indices/_hydrology.py b/xclim/indices/_hydrology.py index e35f2f599..e01377b1a 100644 --- a/xclim/indices/_hydrology.py +++ b/xclim/indices/_hydrology.py @@ -29,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. @@ -71,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`. @@ -109,7 +111,8 @@ 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. @@ -130,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. @@ -161,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. @@ -182,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. @@ -215,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. @@ -249,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. @@ -286,7 +294,8 @@ 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 ea50ed415..8303e37e9 100644 --- a/xclim/indices/_multivariate.py +++ b/xclim/indices/_multivariate.py @@ -75,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. @@ -165,7 +166,8 @@ 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. @@ -182,22 +184,22 @@ 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 ---------- @@ -230,7 +232,8 @@ 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. @@ -247,22 +250,22 @@ 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 ---------- @@ -295,7 +298,8 @@ 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. @@ -312,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 ---------- @@ -360,15 +364,11 @@ 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 @@ -387,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 ---------- @@ -430,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. @@ -511,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. @@ -554,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. @@ -593,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. @@ -646,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. @@ -724,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. @@ -804,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. @@ -870,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'. @@ -893,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 @@ -903,10 +920,6 @@ def liquid_precip_ratio( PR_{ij} = \sum_{i=a}^{b} PR_i PRwet_{ij} - - See Also - -------- - winter_rain_ratio : The ratio of rainfall to total precipitation during winter. """ if prsn is None and tas is not None: prsn = snowfall_approximation(pr, tas=tas, thresh=thresh, method="binary") @@ -928,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. @@ -968,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 @@ -991,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. @@ -1031,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 @@ -1054,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℃. @@ -1120,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. @@ -1170,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. @@ -1231,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. @@ -1297,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. @@ -1355,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. @@ -1413,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. @@ -1471,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. @@ -1529,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. @@ -1587,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. @@ -1650,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. @@ -1714,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 @@ -1789,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. @@ -1827,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. @@ -1869,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. diff --git a/xclim/indices/_simple.py b/xclim/indices/_simple.py index f9697e9cd..61a7e4312 100644 --- a/xclim/indices/_simple.py +++ b/xclim/indices/_simple.py @@ -43,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. @@ -73,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. @@ -112,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. @@ -142,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. @@ -172,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. @@ -202,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. @@ -232,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. @@ -262,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. @@ -292,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. @@ -324,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. @@ -364,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. @@ -400,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. @@ -439,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. @@ -476,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. @@ -516,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. @@ -539,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. @@ -580,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. @@ -623,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. @@ -664,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. @@ -708,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. @@ -752,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. @@ -770,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 0c1b3d3c8..419f055bf 100644 --- a/xclim/indices/_synoptic.py +++ b/xclim/indices/_synoptic.py @@ -24,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 @@ -44,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 6495056ac..13420027b 100644 --- a/xclim/indices/_threshold.py +++ b/xclim/indices/_threshold.py @@ -114,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). @@ -155,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). @@ -214,7 +216,8 @@ 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}. @@ -261,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}. @@ -310,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}. @@ -356,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. @@ -394,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. @@ -432,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. @@ -470,7 +478,8 @@ 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. @@ -507,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. @@ -545,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. @@ -580,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 @@ -602,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. @@ -621,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 @@ -643,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. @@ -665,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). @@ -742,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. @@ -784,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). @@ -835,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℃). @@ -872,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℃). @@ -912,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 @@ -943,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, @@ -965,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 @@ -998,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 @@ -1031,7 +1051,8 @@ 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. @@ -1040,10 +1061,6 @@ def growing_season_length( 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 @@ -1065,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 @@ -1080,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 @@ -1092,10 +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, @@ -1118,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 @@ -1150,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 @@ -1199,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 @@ -1260,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 @@ -1327,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 @@ -1407,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. @@ -1456,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 ---------- @@ -1485,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=("<", "<=")) @@ -1514,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 ---------- @@ -1543,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 @@ -1567,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 ---------- @@ -1597,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 @@ -1627,14 +1656,11 @@ 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 @@ -1650,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 ----- @@ -1661,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 @@ -1682,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 @@ -1705,9 +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 ----- @@ -1716,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 @@ -1742,14 +1773,11 @@ 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 @@ -1766,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") @@ -1787,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 @@ -1809,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 ----- @@ -1820,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 @@ -1842,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 @@ -1864,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 ----- @@ -1875,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") @@ -1895,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℃. @@ -1941,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 @@ -1963,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) @@ -1991,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. @@ -2035,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}. @@ -2097,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}. @@ -2155,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}. @@ -2215,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). @@ -2248,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. @@ -2281,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℃). @@ -2322,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℃). @@ -2363,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℃). @@ -2404,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℃). @@ -2445,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℃). @@ -2486,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℃). @@ -2527,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. @@ -2568,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. @@ -2600,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). @@ -2642,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). @@ -2685,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 @@ -2711,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 @@ -2743,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). @@ -2798,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 @@ -2824,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 @@ -2856,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℃). @@ -2907,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%. @@ -2945,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%. @@ -2982,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). @@ -3022,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. @@ -3068,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℃). @@ -3170,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. @@ -3238,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. @@ -3308,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. @@ -3333,15 +3397,15 @@ def dry_spell_max_length( 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 : The parent function that computes the 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 @@ -3375,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. @@ -3403,15 +3468,15 @@ def wet_spell_frequency( 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 : The parent function that computes the 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 @@ -3443,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. @@ -3470,15 +3536,15 @@ def wet_spell_total_length( 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 : The parent function that computes the 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 @@ -3512,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. @@ -3539,15 +3606,15 @@ def wet_spell_max_length( 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 : The parent function that computes the 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 feffbe97c..e59dc2cb6 100644 --- a/xclim/indices/fire/_cffwis.py +++ b/xclim/indices/fire/_cffwis.py @@ -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 ---------- @@ -483,7 +488,8 @@ def build_up_index(dmc, dc): # TODO: Does this need to be renamed? def fire_weather_index(isi: np.ndarray, bui: np.ndarray) -> np.ndarray: - """Fire Weather Index. + """ + Fire Weather Index. Parameters ---------- @@ -507,7 +513,8 @@ def fire_weather_index(isi: np.ndarray, bui: np.ndarray) -> np.ndarray: def daily_severity_rating(fwi: np.ndarray) -> np.ndarray: - """Daily Severity Rating. + """ + Daily Severity Rating. Parameters ---------- @@ -526,7 +533,8 @@ def daily_severity_rating(fwi: np.ndarray) -> np.ndarray: 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. + """ + Compute the season-starting drought code based on the previous season's last drought code and the total winter precipitation. Parameters ---------- @@ -569,7 +577,8 @@ 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 ---------- @@ -920,7 +929,8 @@ def fire_weather_ufunc( # noqa: C901 # numpydoc ignore=PR01,PR02 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 @@ -1192,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. @@ -1310,7 +1321,8 @@ def cffwis_indices( ) -> tuple[ xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray, xr.DataArray ]: - r"""Canadian Fire Weather Index System indices. + r""" + Canadian Fire Weather Index System indices. Computes the six (6) fire weather indexes, as defined by the Canadian Forest Service: - The Drought Code @@ -1433,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. @@ -1528,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. @@ -1619,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. diff --git a/xclim/indices/fire/_ffdi.py b/xclim/indices/fire/_ffdi.py index 26dd1b8e7..5c06ee0b2 100644 --- a/xclim/indices/fire/_ffdi.py +++ b/xclim/indices/fire/_ffdi.py @@ -193,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 @@ -275,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 @@ -360,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/helpers.py b/xclim/indices/helpers.py index 30c57280e..abe8e86de 100644 --- a/xclim/indices/helpers.py +++ b/xclim/indices/helpers.py @@ -73,7 +73,8 @@ def distance_from_sun(dates: xr.DataArray) -> xr.DataArray: def day_angle(time: xr.DataArray) -> xr.DataArray: - """Day of year as an angle. + """ + Day of year as an angle. 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". @@ -97,7 +98,8 @@ def day_angle(time: xr.DataArray) -> 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. @@ -145,7 +147,8 @@ 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. @@ -179,7 +182,8 @@ 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`. @@ -231,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 @@ -344,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 @@ -399,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 @@ -448,7 +454,8 @@ def day_lengths( lat: xr.DataArray, method: str = "spencer", ) -> xr.DataArray: - r"""Calculate day-length 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`. @@ -491,7 +498,8 @@ 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 ---------- @@ -527,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 ---------- @@ -552,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 ---------- @@ -585,7 +595,8 @@ def resample_map( resample_kwargs: dict | None = None, map_kwargs: dict | None = None, ) -> xr.DataArray | xr.Dataset: - r"""Wrap 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 ---------- @@ -662,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. @@ -693,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. @@ -720,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 ---------- @@ -741,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 df1646676..fc75d7577 100644 --- a/xclim/indices/run_length.py +++ b/xclim/indices/run_length.py @@ -37,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` @@ -52,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. @@ -88,7 +89,8 @@ def resample_and_rl( dim: str = "time", **kwargs, ) -> xr.DataArray: - r"""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 ---------- @@ -132,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 ---------- @@ -177,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 @@ -234,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 ---------- @@ -245,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 @@ -288,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 @@ -331,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 ---------- @@ -341,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 @@ -386,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 ---------- @@ -396,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 @@ -438,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 ---------- @@ -448,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. @@ -484,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 ---------- @@ -494,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 @@ -516,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: @@ -528,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": @@ -584,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 ---------- @@ -594,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 @@ -633,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 ---------- @@ -643,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 @@ -677,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 ---------- @@ -742,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 ------- @@ -786,7 +802,8 @@ 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 ---------- @@ -831,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`. @@ -872,7 +890,8 @@ 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 @@ -939,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 @@ -972,6 +992,12 @@ def season( 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. @@ -986,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 : Start of a season. - season_end : End of a season. - season_length : Length of a season. """ 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 @@ -1050,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 ---------- @@ -1085,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. @@ -1140,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 ---------- @@ -1182,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 ---------- @@ -1221,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 ---------- @@ -1274,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. @@ -1310,7 +1336,8 @@ 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 ---------- @@ -1334,7 +1361,8 @@ 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 ---------- @@ -1360,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 ---------- @@ -1379,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 ---------- @@ -1400,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 ---------- @@ -1431,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 ---------- @@ -1465,7 +1497,8 @@ 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 ---------- @@ -1500,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 ---------- @@ -1533,7 +1567,8 @@ 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 ---------- @@ -1620,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 ---------- @@ -1634,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]) @@ -1669,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 ---------- @@ -1685,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 @@ -1718,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. @@ -1754,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 @@ -1846,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. diff --git a/xclim/indices/stats.py b/xclim/indices/stats.py index a1709ca6d..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 ---------- @@ -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 ---------- @@ -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 ---------- @@ -461,7 +466,8 @@ def get_dist(dist: str | rv_continuous) -> 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. @@ -473,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 @@ -482,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() @@ -567,7 +573,8 @@ 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. @@ -602,7 +609,7 @@ def dist_method( 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', + (e.g. `nnlf`) or create new dimensions (e.g. 'rvs' with size != 1, 'stats' with more than one moment, 'interval', 'support'). Parameters @@ -728,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. @@ -837,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` @@ -890,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: 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 7ab3dad27..15ae34a5c 100644 --- a/xclim/testing/conftest.py +++ b/xclim/testing/conftest.py @@ -29,7 +29,8 @@ @pytest.fixture(autouse=True, scope="session") def threadsafe_data_dir(tmp_path_factory): # numpydoc ignore=PR01 - """Return a threadsafe temporary directory for storing testing data. + """ + Return a threadsafe temporary directory for storing testing data. Yields ------ @@ -41,7 +42,8 @@ def threadsafe_data_dir(tmp_path_factory): # numpydoc ignore=PR01 @pytest.fixture(scope="session") def nimbus(threadsafe_data_dir, worker_id): # numpydoc ignore=PR01 - """Return a nimbus object for the test data. + """ + Return a nimbus object for the test data. Returns ------- @@ -59,7 +61,8 @@ def nimbus(threadsafe_data_dir, worker_id): # numpydoc ignore=PR01 @pytest.fixture(scope="session") def open_dataset(nimbus): # numpydoc ignore=PR01 - """Return a function that opens a dataset from the test data. + """ + Return a function that opens a dataset from the test data. Returns ------- diff --git a/xclim/testing/diagnostics.py b/xclim/testing/diagnostics.py index 6eea30874..0b28c9a47 100644 --- a/xclim/testing/diagnostics.py +++ b/xclim/testing/diagnostics.py @@ -34,7 +34,8 @@ 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. + r""" + Return gamma distributed rainfall values for wet days. The returned values are zero for dry days. @@ -70,7 +71,8 @@ def synth_rainfall( def cannon_2015_figure_2() -> plt.Figure: - """Create a graphic similar to figure 2 of Cannon et al. 2015. + """ + 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 @@ -158,7 +160,8 @@ def cannon_2015_figure_2() -> plt.Figure: 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 ------- diff --git a/xclim/testing/helpers.py b/xclim/testing/helpers.py index 500632bc6..11fb0ac2e 100644 --- a/xclim/testing/helpers.py +++ b/xclim/testing/helpers.py @@ -38,7 +38,8 @@ 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 ---------- @@ -87,7 +88,8 @@ 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 ------- @@ -110,7 +112,8 @@ 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 ------- @@ -162,7 +165,8 @@ 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 ------- @@ -188,7 +192,8 @@ 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 ---------- @@ -239,7 +244,8 @@ 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 ---------- diff --git a/xclim/testing/sdba_utils.py b/xclim/testing/sdba_utils.py index b3c219175..6018f64a6 100644 --- a/xclim/testing/sdba_utils.py +++ b/xclim/testing/sdba_utils.py @@ -18,7 +18,8 @@ def series(values: np.ndarray, name: str, start: str = "2000-01-01"): - """Create a DataArray with time, lon and lat dimensions. + """ + Create a DataArray with time, lon and lat dimensions. Parameters ---------- @@ -68,7 +69,8 @@ def series(values: np.ndarray, name: str, start: str = "2000-01-01"): def cannon_2015_dist() -> (gamma, gamma, gamma): # noqa: D103 - """Generate the distributions used in Cannon et al. 2015. + """ + Generate the distributions used in Cannon et al. 2015. Returns ------- @@ -88,7 +90,8 @@ def cannon_2015_dist() -> (gamma, gamma, gamma): # 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. + """ + Generate the Random Variables used in Cannon et al. 2015. Parameters ---------- diff --git a/xclim/testing/utils.py b/xclim/testing/utils.py index 83fa276ec..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,7 +149,8 @@ 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. @@ -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 ---------- @@ -315,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 ---------- @@ -431,7 +437,8 @@ 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 ---------- @@ -492,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 ---------- @@ -593,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. @@ -664,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 ---------- @@ -712,7 +722,8 @@ def gather_testing_data( worker_id: str, _cache_dir: str | os.PathLike[str] | None = TESTDATA_CACHE_DIR, ) -> None: - """Gather testing data across workers. + """ + Gather testing data across workers. Parameters ---------- @@ -764,7 +775,8 @@ 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 ---------- From db4ee9c19cba28e1a0609e13c016946921642145 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:33:50 -0500 Subject: [PATCH 27/31] spacing fix --- xclim/core/indicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 53c56e778..d27103765 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -304,7 +304,7 @@ def get_instance(cls) -> Any: # numpydoc ignore=RT05 Returns ------- Indicator - First instance found of this class in the indicators registry. + First instance found of this class in the indicators registry. Raises ------ From 81e3dbe8d04cc47f7a00ea4c3d8e2202631b13f3 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:38:21 -0500 Subject: [PATCH 28/31] remove defaults --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index d3beabe90..bfc0fcece 100644 --- a/environment.yml +++ b/environment.yml @@ -1,7 +1,6 @@ name: xclim channels: - conda-forge - - defaults dependencies: - python >=3.10,<3.14 - boltons >=20.1 From 9aa4e4a42e5ae8756d759f15011f730c5f1f07b3 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:51:36 -0500 Subject: [PATCH 29/31] allow connections to conda.anaconda.org --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1faca5919..b2f795152 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -348,6 +348,7 @@ jobs: api.electricitymap.org:443 api.github.com:443 api.green-coding.io:443 + conda.anaconda.org:443 coveralls.io:443 files.pythonhosted.org:443 github.com:443 From f223540bba9e369df1fb3bab119c26f7053e37a2 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:45:31 -0500 Subject: [PATCH 30/31] reformat docstring logic --- xclim/ensembles/_robustness.py | 73 +++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py index e15ad2f58..fda47b4a6 100644 --- a/xclim/ensembles/_robustness.py +++ b/xclim/ensembles/_robustness.py @@ -97,27 +97,32 @@ def robustness_fractions( # noqa: C901 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 ----- @@ -141,17 +146,17 @@ 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`. Examples -------- @@ -490,6 +495,7 @@ def _ttest(fut, ref, *, p_change=0.05): 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. """ @@ -575,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'. """ @@ -629,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 + ], ) From 496b95c9f00d8647a16c0be00d9d6610406777b4 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:10:42 -0500 Subject: [PATCH 31/31] Update _agro.py --- xclim/indices/_agro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/indices/_agro.py b/xclim/indices/_agro.py index 871b25270..c66408f0d 100644 --- a/xclim/indices/_agro.py +++ b/xclim/indices/_agro.py @@ -1659,7 +1659,7 @@ 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,