Download this notebook from github.

Extending xclim

xclim tries to make it easy for users to add their own indices and indicators. The following goes into details on how to create indices and document them so that xclim can parse most of the metadata directly. We then explain the multiple ways new Indicators can be created and, finally, how we can regroup and structure them in virtual submodules.

Central to xclim are the Indicators, objects computating indices over climate variables, but xclim also provides other modules:

modules

Where subset is a phantom module, kept for legacy code, as it only redirects the calls to clisops.core.subset.

This introduction will focus on the Indicator/Indice part of xclim and how one can extend it by implementing new ones.

Indices vs Indicators

Internally and in the documentation, xclim makes a distinction between “indices” and “indicators”.

indice

  • A python function accepting DataArrays and other parameters (usually bultin types)

  • Returns one or several DataArrays.

  • Handles the units : checks input units and set proper CF-compliant output units. But doesn’t usually prescribe specific units, the output will at minimum have the proper dimensionality.

  • Performs no other checks or set any (non-unit) metadata.

  • Accessible through xclim.indices.

indicator

  • An instance of a subclass of xclim.core.indicator.Indicator that wraps around an indice (stored in its compute property).

  • Returns one or several DataArrays.

  • Handles missing values, performs input data and metadata checks (see usage).

  • Always ouputs data in the same units.

  • Adds dynamically generated metadata to the output after computation.

  • Accessible through xclim.indicators 

Most metadata stored in the Indicators is parsed from the underlying indice documentation, so defining indices with complete documentation and an appropriate signature helps the process. The two next sections go into details on the definition of both objects.

Call sequence

The following graph shows the steps done when calling an Indicator. Attributes and methods of the Indicator object relating to those steps are listed on the right side.

indicator

Defining new indices

The annotated example below shows the general template to be followed when defining proper indices. In the comments Ind is the indicator instance that would be created from this function.

Note that it is not needed to follow these standards when writing indices that will be wrapped in indicators. Problems in parsing will not raise errors at runtime, but will result in Indicators with poorer metadata than expected by most users, especially those that dynamically use indicators in other applications where the code is inaccessible, like web services.

indice doc

The following code is another example.

[1]:
import xarray as xr
import xclim as xc
from xclim.core.units import declare_units, convert_units_to
from xclim.indices.generic import threshold_count

@declare_units(tasmax="[temperature]", thresh="[temperature]")
def tx_days_compare(tasmax: xr.DataArray, thresh: str = "0 degC", op: str = '>', freq: str = "YS"):
    r"""Number of days where maximum daily temperature. is above or under a threshold.

    The daily maximum temperature is compared to a threshold using a given operator and the number
    of days where the condition is true is returned.

    It assumes a daily input.

    Parameters
    ----------
    tasmax : xarray.DataArray
      Maximum daily temperature.
    thresh : str
      Threshold temperature to compare to.
    op : {'>', '<'}
      The operator to use.
      # A fixed set of choices can be imposed. Only strings, numbers, booleans or None are accepted.
    freq : str
      Resampling frequency.

    Returns
    -------
    xarray.DataArray, [temperature]
      Maximum value of daily maximum temperature.

    Notes
    -----
    Let :math:`TX_{ij}` be the maximum temperature at day :math:`i` of period :math:`j`. Then the maximum
    daily maximum temperature for period :math:`j` is:

    .. math::

        TXx_j = max(TX_{ij})

    References
    ----------
    Smith, John and Tremblay, Robert, An dummy citation for examples in documentation. J. RTD. (2020).
    """
    thresh = convert_units_to(thresh, tasmax)
    out = threshold_count(tasmax, op, thresh, freq)
    out.attrs['units'] = "days"
    return out

Naming and conventions

Variable names should correspond to CMIP6 variables, whenever possible. The file xclim/data/variables.yml lists all variables that xclim can use when generating indicators from yaml files (see below), and new indices should try to reflect these also. For new variables, the xclim.testing.get_all_CMIP6_variables function downloads the official table of CMIP6 variables and puts everything in a dictionary. If possible, use variables names from this list, add them to variables.yml as needed.

Generic functions for common operations

The xclim.indices.generic submodule contains useful functions for common computations (like threshold_count or select_resample_op) and many basic indice functions, as defined by clix-meta. In order to reduce duplicate code, their use is recommended for xclim’s indices. As previously said, the units handling has to be made explicitly when non trivial, xclim.core.units also exposes a few helpers for that (like convert_units_to, to_agg_units or rate2amount).

Defining new indicators

xclim’s Indicators are instances of (subclasses of) xclim.core.indicator.Indicator. While they are the central to xclim, their construction can be somewhat tricky as a lot happens backstage. Essentially, they act as self-aware functions, taking a set of input variables (DataArrays) and parameters (usually strings, integers or floats), performing some health checks on them and returning one or multiple DataArrays, with CF-compliant (and potentially translated) metadata attributes, masked according to a given missing value set of rules. They define the following key attributes:

  • the identifier, as string that uniquely identifies the indicator,

  • the realm, one of “atmos”, “land”, “seaIce” or “ocean”, classifying the domain of use of the indicator.

  • the compute function that returns one or more DataArrays, the “indice”,

  • the cfcheck and datacheck methods that make sure the inputs are appropriate and valid.

  • the missing function that masks elements based on null values in the input.

  • all metadata attributes that will be attributed to the output and that document the indicator:

    • Indicator-level attribute are : title, abstract, keywords, references and notes.

    • Ouput variables attributes (respecting CF conventions) are: var_name, standard_name, long_name, units, cell_methods, description and comment.

Output variables attributes are regrouped in Indicator.cf_attrs and input parameters are documented in Indicator.parameters.

A particularity of Indicators is that each instance corresponds to a single class: when creating a new indicator, a new class is automatically created. This is done for easy construction of indicators based on others, like shown further down.

See the class documentation for more info on the meaning of each attribute. The indicators module contains over 50 examples of indicators to draw inspiration from.

Metadata parsing vs explicit setting

As explained above, most metadata can be parsed from the indice’s signature and docstring. Otherwise, it can always be set when creating a new Indicator instance or a new subclass. When creating an indicator, output metadata attributes can be given as strings, or list of strings in the case of indicator returning multiple outputs. However, they are stored in the cf_attrs list of dictionaries on the instance.

Internationalization of metadata

xclim offers the possibility to translate the main Indicator metadata field and automatically add the translations to the outputs. The mechnanic is explained in the Internationalization page.

Inputs and checks

There are two ways that xclim uses to decide which input arguments of the indicator’s call function are considered variables and which are parameters.

  • The nvar indicator integer attribute sets the number of arguments that are sent to the datacheck and cfcheck methods (in the signature’s order).

  • The annotations of the underlying indice (the compute method). Arguments annotated with the xarray.DataArray type are considered variables and can be read from the dataset passed in ds.

Indicator creation

There a two ways for creating indicators:

  1. By initializing an existing indicator (sub)class

  2. From a dictionary

The first method is best when defining indicators in scripts of external modules and are explained here. The second is best used when building virtual modules through YAML files, and is explained further down and in the submodule doc.

Creating a new indicator that simply modifies a few metadata output of an existing one is a simple call like:

[2]:
from xclim.core.indicator import registry
from xclim.core.utils import wrapped_partial
from xclim.indices import tg_mean

# An indicator based on tg_mean, but returning Celsius and fixed on annual resampling
tg_mean_c = registry['TG_MEAN'](
    identifier='tg_mean_c',
    units='degC',
    title='Mean daily mean temperature but in degC',
    compute=wrapped_partial(tg_mean, freq='YS')  # We inject the freq arg.
)
[3]:
print(tg_mean_c.__doc__)
Mean daily mean temperature but in degC (realm: atmos)

Resample the original daily mean temperature series by taking the mean over each period.

This indicator will check for missing values according to the method "skip".
Based on indice :py:func:`xclim.indices._simple.tg_mean`.
With injected parameters: freq=YS.

Parameters
----------
tas : str or DataArray
  Mean daily temperature.
  Default : `ds.tas`. [Required units : [temperature]]
ds : Dataset, optional
  Input dataset.
  Default: None.

Returns
-------
tg_mean : DataArray
  Mean daily mean temperature (air_temperature) [degC]
  cell_methods: time: mean within days time: mean over days
  description: {freq} mean of daily mean temperature.

Notes
-----
Let :math:`TN_i` be the mean daily temperature of day :math:`i`, then for a period :math:`p` starting at
day :math:`a` and finishing on day :math:`b`:

.. math::

   TG_p = \frac{\sum_{i=a}^{b} TN_i}{b - a + 1}


The registry is a dictionary mapping indicator identifiers (in uppercase) to their class. This way, we could subclass tg_mean to create our new indicator. tg_mean_c is the exact same as atmos.tg_mean, but outputs the result in Celsius instead of Kelvins, has a different title and resamples to “YS”. The identifier keyword is here needed in order to differentiate the new indicator from tg_mean itself. If it wasn’t given, a warning would have been raised and further subclassing of tg_mean would have in fact subclassed tg_mean_c, which is not wanted!

This method of class initialization is good for the cases where only metadata and perhaps the compute function is changed. However, to modify the CF compliance and data checks, we recommend creating a class first:

[4]:
class TG_MAX_C(registry['TG_MAX']):
    identifier = "tg_max_c"
    missing = "wmo"
    title = 'Maximum daily mean temperature'
    units = 'degC'

    @staticmethod
    def cfcheck(tas):
        xc.core.cfchecks.check_valid(tas, "standard_name", "air_temperature")
        # Add a very strict check on the long name.
        # glob-like wildcards can be used (here *)
        xc.core.cfchecks.check_valid(tas, "long_name", "Surface * daily temperature")

    @staticmethod
    def datacheck(tas):
        xc.core.datachecks.check_daily(tas)

tg_max_c = TG_MAX_C()
[5]:
from xclim.testing import open_dataset
ds = open_dataset('ERA5/daily_surface_cancities_1990-1993.nc')

ds.tas.attrs['long_name'] = 'Surface average daily temperature'

with xc.set_options(cf_compliance='raise'):
    # The data passes the test we implemented ("average" is caught by the *)
    tmaxc = tg_max_c(tas=ds.tas)
tmaxc
[5]:
<xarray.DataArray 'tg_max' (location: 5, time: 4)>
array([[19.930664, 19.59961 , 19.0271  , 21.183167],
       [25.169556, 28.736847, 24.145935, 27.655853],
       [12.7453  , 13.685822, 10.571136, 14.26416 ],
       [25.14737 , 26.903625, 23.966248, 22.563019],
       [18.974976, 17.129333, 17.96048 , 18.791718]], dtype=float32)
Coordinates:
  * time      (time) datetime64[ns] 1990-01-01 1991-01-01 1992-01-01 1993-01-01
    lat       (location) float32 44.5 45.5 63.75 52.0 48.5
  * location  (location) object 'Halifax' 'Montréal' ... 'Saskatoon' 'Victoria'
    lon       (location) float32 -63.5 -73.5 -68.5 -106.8 -123.2
Attributes:
    standard_name:  air_temperature
    long_name:      Maximum daily mean temperature
    units:          degC
    cell_methods:   time: mean within days time: mean within days time: maxim...
    history:        [2022-01-10 15:42:00] tg_max: __main__.TG_MAX_C(tas=tas, ...
    description:    Annual maximum of daily mean temperature.

A caveat of this method is that the new indicator is added to the registry with a non-trivial name. When an indicator subclass is created in a module outside xclim.indicators, the name of its parent module is prepended to its identifier in the registry. Here, the module is __main__, so:

[6]:
'__main__.TG_MAX_C' in registry
[6]:
True

A simple way to workaround this is to provided a (fake) module name. Passing one of atmos, land, seaIce or ocean will result in a normal entry in the registry. However, one could want to keep the distinction between the newly created indicators and the “official” ones by passing a custom name upon instantiation:

[7]:
# Fake module is passed upon instantiation
tg_max_c2 = TG_MAX_C(module ='example')
print(tg_max_c2.__module__)
print('example.TG_MAX_C' in registry)
xclim.indicators.example
True

One pattern to create multiple indicators is to write a standard subclass that declares all the attributes that are common to indicators, then call this subclass with the custom attributes. See for example in xclim.indicators.atmos how indicators based on daily mean temperatures are created from the Tas subclass of the Daily class.

Virtual modules

xclim gives users the ability to generate their own modules from existing indices library. These mappings can help in emulating existing libraries (such as ICCLIM), with the added benefit of CF-compliant metadata, multilingual metadata support, and optimized calculations using federated resources (using Dask). This can be used for example to tailor existing indices with predefined thresholds without having to rewrite indices.

Presently, xclim is capable of approximating the indices developed in ICCLIM (https://icclim.readthedocs.io/en/latest/intro.html), ANUCLIM (https://fennerschool.anu.edu.au/files/anuclim61.pdf) and clix-meta (https://github.com/clix-meta/clix-meta) and is open to contributions of new indices and library mappings.

This notebook serves as an example of how one might go about creating their own library of mapped indices. Two ways are possible:

  1. From a YAML file (recommended way)

  2. From a mapping (dictionary) of indicators

YAML file

The first method is based on the YAML syntax proposed by clix-meta, expanded to xclim’s needs. The full documentation on that syntax is here. This notebook shows an example different complexities of indicator creation. It creates a minimal python module defining a indice, creates a YAML file with the metadata for several indicators and then parses it into xclim.

[9]:
# These variables were generated by a hidden cell above that syntax-colored them.
print('Content of example.py :')
print(highlighted_py)
print('\n\nContent of example.yml :')
print(highlighted_yaml)
print('\n\nContent of example.fr.json :')
print(highlighted_json)
Content of example.py :
import xarray as xr

from xclim.core.units import declare_units, rate2amount


@declare_units(pr="[precipitation]")
def extreme_precip_accumulation(pr: xr.DataArray, perc: float = 95, freq: str = "YS"):
    """Total precipitation accumulation during extreme events.

    The `perc` percentile of the precipitation (including all values, not in a day-of-year manner)
    is computed. Then, for each period, the days where `pr` is above the threshold are accumulated,
    to get the total precip related to those extreme events.

    Parameters
    ----------
    pr: xr.DataArray
      Precipitation flux (both phases).
    perc: float
      Percentile corresponding to "extreme" precipitation, [0-100].
    freq: str
      Resampling frequency.

    Returns
    -------
    xarray.DataArray
      Precipitation accumulated during events where pr was above the {perc}th percentile of the whole series.
    """
    pr_thresh = pr.quantile(perc / 100, dim="time")

    pr_extreme = rate2amount(pr).where(pr >= pr_thresh)

    out = pr_extreme.resample(time=freq).sum().drop_vars("quantile")
    out.attrs["units"] = pr_extreme.units
    return out



Content of example.yml :
realm: atmos
doc: |
  ==============
  Example module
  ==============
  This module is an example of YAML generated xclim submodule.
references: xclim documentation https://xclim.readthedocs.io
indices:
  RX1day:
    base: rx1day
    period:
      allowed:
      - seasonal
      - annual
      default: annual
    output:
      long_name: Highest 1-day precipitation amount
  RX5day:
    base: max_n_day_precipitation_amount
    output:
      long_name: Highest 5-day precipitation amount
    index_function:
      parameters:
        window: 5
        freq: QS-DEC
  R75pdays:
    base: days_over_precip_thresh
    index_function:
      parameters:
        thresh:
          data: 1 mm/day
        per:
          data: {per}
          description: Daily 75th percentile of wet day precipitation flux.
  fd:
    reference: ETCCDI
    period: weekly
    output:
      var_name: "fd"
      standard_name: number_of_days_with_air_temperature_below_threshold
      long_name: "Number of Frost Days (Tmin < 0C)"
      units: "days"
      cell_methods:
        - time: minimum within days
        - time: sum over days
    input:
      data: tasmin
    index_function:
      name: count_occurrences
      parameters:
        threshold: 0 degC
        condition: "<"
  R95p:
    reference: climdex
    period: annual
    output:
      var_name: R95p
      long_name: Annual total PRCP when RR > {perc}th percentile
      units: m
      cell_methods:
        - time: sum within days
        - time: sum over days
    input:
      pr: pr
    index_function:
      name: extreme_precip_accumulation
      parameters:
        perc: 95
  R99p:
    base: .R95p
    index_function:
      name: extreme_precip_accumulation
      parameters:
        perc: 99
  LPRatio:
    base: liquid_precip_ratio
    input:
      tas: tas
      pr: pr
    output:
      long_name: Liquid precip to total precip ratio.
      description: Precipitation is estimated to be solid when tas is under 0.5°C
    index_function:
      parameters:
        thresh: 0.5 degC



Content of example.fr.json :
{
  "FD": {
    "title": "Nombre de jours de gel",
    "long_name" : "Nombre de jours de gel (Tmin < 0°C)",
    "description": "Nombre de jours où la température minimale passe sous 0°C."
  },
  "R95P": {
    "title": "Précpitations accumulées lors des jours de fortes pluies (> 95e percentile)",
    "long_name": "Accumulation {freq:f} des précipitations lors des jours de fortes pluies (> 95e percentile)",
    "description": "Épaisseur équivalent des précipitations accumulées lors des jours où la pluie est plus forte que le 95e percentile de la série."
  }
}

example.yml created a module of 4 indicators.

  • RX1day is simply the same as registry['RX1DAY'], but with an updated long_name.

  • RX5day is based on registry['MAX_N_DAY_PRECIPITATION_AMOUNT'], changed the long_name and injects the window and freq arguments.

  • R75pdays is based on registry['DAYS_OVER_PRECIP_THRESH'], injects the thresh argument and changes the description of the per argument. Passing “data: {per}” tells xclim the value is still to be determined by the user, but other parameter’s metadata field might be changed.

  • fd is a more complex example. As there were no base: entry, the Daily class serves as a base. As it is pretty much empty, a lot has to be given explicitly:

    • A list of allowed resampling frequency is passed

    • Many output metadata fields are given

    • A index_function name if given (here it refers to a function in xclim.indices.generic).

    • Some parameters are injected.

    • The input variable data is mapped to a known variable. Functions in xclim.indices.generic are indeed generic. Here we tell xclim that the data argument is minimal daily temperature. This will set the proper units check, default value and CF-compliance checks.

  • R95p is similar to fd but here the index_function is not defined in xclim but rather in example.py.

  • R99p is the same as R95p but changes the injected value. In order to avoid rewriting the output metadata, and allowed periods, we based it on R95p : as the latter was defined within the current yaml file, the identifier is prefixed by a dot (.). However, in order to inject a parameter we still need to repeat the index_function name (and retrigger the indice function wrapping process under the hood).

  • LPRatio is a version of “liquid precip ratio” where we we force the use of tas (instead of having it an optional variable). We also inject a specific threshold.

A few ways of prescribing default or allowed periods (resampling frequencies) are shown here. In fd and R95p, only the default value of freq is given. R75pdays will keep the default value in the signature of the underlying indice. RX1day goes in more details by prescribing a default value and a list of allowed values. xclim will be relax and accept any freq values equivalent to those listed here. Finally, RX5day directly injects the freq argument, so that it doesn’t even appear in the docstring.

Additionnaly, the yaml specified a realm and references to be used on all indices and provided a submodule docstring. Creating the module is then simply:

Finally, french translations for the main attributes and the new indicaters are given in example.fr.json. Even though new indicator objects are created for each yaml entry, non-specified translations are taken from the base classes if missing in the json file.

Note that all files are named the same way : example.<ext>, with the translations having an additionnal suffix giving the locale name. In the next cell, we build the module by passing only the path without extension. This absence of extension is what tells xclim to try to parse a module (*.py) and custom translations (*.<locale>.json). Those two could also be read beforehand and passed through the indices= and translations= arguments.

[10]:
import xclim as xc

example = xc.core.indicator.build_indicator_module_from_yaml('example', mode='raise')
[11]:
print(example.__doc__)
print('--')
print(xc.indicators.example.R99p.__doc__)
==============
Example module
==============
This module is an example of YAML generated xclim submodule.

--
Total precipitation accumulation during extreme events. (realm: atmos)

The `perc` percentile of the precipitation (including all values, not in a day-of-year manner) is computed. Then, for each period, the days where `pr` is above the threshold are accumulated, to get the total precip related to those extreme events.

This indicator will check for missing values according to the method "from_context".
Based on indice :py:func:`example.extreme_precip_accumulation`.
With injected parameters: perc=99.

Parameters
----------
pr : str or DataArray
  Precipitation flux (both phases).
  Default : `ds.pr`. [Required units : [precipitation]]
freq : offset alias (string)
  Resampling frequency.
  Default : YS.
ds : Dataset, optional
  Input dataset.
  Default: None.

Returns
-------
R99p : DataArray
  Annual total PRCP when RR > {perc}th percentile [m]
  cell_methods: time: sum within days time: sum over days

References
----------
climdex


Useful for using this technique in large projects, we can iterate over the indicators like so:

[12]:
ds2 = ds.assign(per=xc.core.calendar.percentile_doy(ds.pr, window=5, per=75).isel(percentiles=0, drop=True))

outs = []
with xc.set_options(metadata_locales='fr'):
    for name, ind in example.iter_indicators():
        print(f'Indicator: {name}')
        print(f'\tIdentifier: {ind.identifier}')
        print(f'\tTitle: {ind.title}')
        out = ind(ds=ds2)  # Use all default arguments and variables from the dataset,
        outs.append(out)
Indicator: RX1day
        Identifier: RX1day
        Title: Highest 1-day precipitation amount for a period (frequency).
Indicator: RX5day
        Identifier: RX5day
        Title: Highest precipitation amount cumulated over a n-day moving window.
Indicator: R75pdays
        Identifier: R75pdays
        Title: Number of wet days with daily precipitation over a given percentile.
Indicator: fd
        Identifier: fd
        Title: Calculate the number of times some condition is met.
Indicator: R95p
        Identifier: R95p
        Title: Total precipitation accumulation during extreme events.
Indicator: R99p
        Identifier: R99p
        Title: Total precipitation accumulation during extreme events.
Indicator: LPRatio
        Identifier: LPRatio
        Title: Ratio of rainfall to total precipitation.

out contains all the computed indices, with translated metadata. Note that this merge doesn’t make much sense with the current list of indicators since they have different frequencies (freq).

[13]:
out = xr.merge(outs)
out.attrs = {'title': 'Indicators computed from the example module.'} # Merge puts the attributes of the first variable, we don't want that.
out
[13]:
<xarray.Dataset>
Dimensions:   (time: 227, location: 5)
Coordinates:
  * time      (time) datetime64[ns] 1989-12-01 1990-01-01 ... 1994-01-02
    lat       (location) float32 44.5 45.5 63.75 52.0 48.5
  * location  (location) object 'Halifax' 'Montréal' ... 'Saskatoon' 'Victoria'
    lon       (location) float32 -63.5 -73.5 -68.5 -106.8 -123.2
Data variables:
    RX1day    (location, time) float32 nan 61.13 nan nan nan ... nan nan nan nan
    RX5day    (location, time) float64 59.69 nan nan nan nan ... nan nan nan nan
    R75pdays  (location, time) float64 nan 97.0 nan nan nan ... nan nan nan nan
    fd        (location, time) float64 nan nan 6.0 7.0 6.0 ... 0.0 0.0 0.0 nan
    R95p      (location, time) float64 nan 0.7553 nan nan ... nan nan nan nan
    R99p      (location, time) float64 nan 0.2054 nan nan ... nan nan nan nan
    LPRatio   (location, time) float32 nan nan nan nan nan ... nan nan nan nan
Attributes:
    title:    Indicators computed from the example module.

Mapping of indicators

For more complex mappings, submodules can be constructed from Indicators directly. This is not the recommended way, but can sometimes be a workaround when the YAML version is lacking features.

[14]:
from xclim.core.indicator import build_indicator_module, registry
from xclim.core.utils import wrapped_partial

mapping = dict(
    egg_cooking_season=registry["MAXIMUM_CONSECUTIVE_WARM_DAYS"](
        module='awesome',
        compute=wrapped_partial(xc.indices.maximum_consecutive_tx_days, thresh="35 degC"),
        long_name="Season for outdoor egg cooking.",
    ),
    fish_feeling_days=registry["WETDAYS"](
        module='awesome',
        compute=wrapped_partial(xc.indices.wetdays, thresh="14.0 mm/day"),
        long_name="Days where we feel we are fishes"
    ),
    sweater_weather=xc.atmos.tg_min
)

awesome = build_indicator_module(
    name="awesome",
    objs=mapping,
    doc="""
        =========================
        My Awesome Custom indices
        =========================
        There are only 3 indices that really matter when you come down to brass tacks.
        This mapping library exposes them to users who want to perform real deal
        climate science.
        """,
)
[15]:
print(xc.indicators.awesome.__doc__)

        =========================
        My Awesome Custom indices
        =========================
        There are only 3 indices that really matter when you come down to brass tacks.
        This mapping library exposes them to users who want to perform real deal
        climate science.

[16]:
# Let's look at our new awesome module
print(awesome.__doc__)
for name, ind in awesome.iter_indicators():
    print(f"{name} : {ind}")

        =========================
        My Awesome Custom indices
        =========================
        There are only 3 indices that really matter when you come down to brass tacks.
        This mapping library exposes them to users who want to perform real deal
        climate science.

egg_cooking_season : <xclim.indicators.awesome.MAXIMUM_CONSECUTIVE_WARM_DAYS object at 0x7f0b5d209910>
fish_feeling_days : <xclim.indicators.awesome.WETDAYS object at 0x7f0b5d389bd0>
sweater_weather : <xclim.indicators.atmos._temperature.TG_MIN object at 0x7f0b5d4f44d0>