Ouranosinc / xclim

Library of derived climate variables, ie climate indicators, based on xarray.
https://xclim.readthedocs.io/en/stable/
Apache License 2.0
311 stars 54 forks source link

UTCI for low wind velocities #1634

Closed profesorpaiche closed 5 months ago

profesorpaiche commented 5 months ago

Addressing a Problem?

The current implementation of the UTCI algorithm produces NaN whenever the wind velocity is less than or equal to 0.5 m/s. This is fine and consistent with the original version. However, Bröde 2012 advises users in the "Usage Guidelines" to use 0.5 m/s when winds are below this boundary. This wind change shouldn't have a significant impact on the UTCI because it barely changes the wind felt by a person according to Bröde 2023 (see Figure 2).

Potential Solution

Add an additional option to the universal_thermal_climate_index function to change the winds. This option should be False by default, so that it is a conscious decision by the user to use it.

The code could be something simple like:

    if wind_cap_min:
        sfcWind = sfcWind.where((sfcWind > 0.5) | (sfcWind.isnull()), other=0.5)

where wind_cap_min is the condition for changing the winds.

Also, the mask_invalid section should be updated to include winds greater or equal to 0.5 m/s. Currently, 0.5 m/s winds are set to NaN, which would conflict with my proposed feature.

From & (0.5 < sfcWind) to & (0.5 <= sfcWind)

Additional context

Just a bit of context on how this feature might be useful. I am currently analyzing the behavior of UTCI during heat waves. It is common to have very low wind velocities during these events, which would result in missing UTCI values when high values are expected.

utci_original

By implementing this feature, the UTCI missing areas are reduced.

utci_corrected

Contribution

Code of Conduct

aulemahal commented 5 months ago

This is a good idea!

I did a small test and I would suggest the following:

if wind_cap_min:
    sfcWind = sfcWind.clip(0.5, None)

My very simple speed test shows it could be faster:

>>> da = xr.DataArray(np.random.randint(0, 20, (200, 200)), dims=('x', 'y'))
>>> da = da.astype(float).where(da != 10)
>>> %timeit da.clip(5, None)
129 µs ± 3.14 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
>>> %timeit da.where((da > 5) | (da.isnull()), 5)
643 µs ± 2.78 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
profesorpaiche commented 5 months ago

Oh that's cool! I wasn't aware of clip. Then, I will proceed implementing the feature