Unit-Aware Calculations#

Where possible, HuracanPy functions are doing unit-aware calculations. This is achieved using functionality from metpy and the metpy accessor.

Typically, for metpy unit-aware calculations to work, the units must be included in the attributes of the variable. e.g.

[2]:
import huracanpy

tracks = huracanpy.load(huracanpy.example_csv_file)

# Add units to some variables
tracks.slp.attrs["units"] = "Pa"
tracks.wind10.attrs["units"] = "m s-1"
tracks.lon.attrs["units"] = "degrees"
tracks.lat.attrs["units"] = "degrees"

# Use a unit-aware huracanpy calculation
dv_dt = tracks.hrcn.get_rate(var_name="wind10")
print(dv_dt)
<xarray.DataArray (record: 99)> Size: 792B
array([-3.05402778e-05, -1.35523148e-05,  1.98257870e-04, ...,
        1.23847222e-05, -2.56800926e-05,             nan], shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    meter / second ** 2

MetPy functionality#

When the track data has units in the attributes you can use functionalities from the metpy accessor (see the metpy docs for more details).

Unit Conversion#

For example, if we want to convert the intensification to more readable units we can use the metpy unit conversion

[3]:
print(dv_dt.metpy.convert_units("m s-1 day-1"))
<xarray.DataArray (record: 99)> Size: 792B
<Quantity([-2.63868 -1.17092 17.12948 ...  1.07004 -2.21876      nan], 'meter / second / day')>
Dimensions without coordinates: record

dequantify and quantify#

Note that, the returned Dataarray contains a pint.Quantity instead of a numpy array. This is a numpy-like array that enables the unit-aware calculations. However it can sometimes result in strange behaviour. The metpy accessor provides a method for undoing this (and HuracanPy functions will do this automatically)

[4]:
print(dv_dt.metpy.convert_units("m s-1 day-1").metpy.dequantify())
<xarray.DataArray (record: 99)> Size: 792B
array([-2.63868, -1.17092, 17.12948, ...,  1.07004, -2.21876,      nan],
      shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    meter / day / second

Using dequantify has reverted to a numpy array with the units in the attributes

Be aware that fairly trivial operations will lose these units if they are not done in a unit-aware way

[5]:
print(dv_dt + 0)
<xarray.DataArray (record: 99)> Size: 792B
array([-3.05402778e-05, -1.35523148e-05,  1.98257870e-04, ...,
        1.23847222e-05, -2.56800926e-05,             nan], shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    meter / second ** 2

If you want to do your own unit-aware calculations, you will need to use the metpy methods, e.g.

[6]:
from metpy.units import units

print((dv_dt.metpy.quantify() + 0 * units("m s-2")).metpy.dequantify())
<xarray.DataArray (record: 99)> Size: 792B
array([-3.05402778e-05, -1.35523148e-05,  1.98257870e-04, ...,
        1.23847222e-05, -2.56800926e-05,             nan], shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    meter / second ** 2

HuracanPy functionality#

The following functions support unit-aware calculations

Assumed units#

Unless you have a netCDF file which includes units, or manually add the units, your track variables will not have units. To account for this, HuracanPy functions that use units will assume that your variables have default units with the following convention

  • Distance in metres

  • Speed in metres per second

  • Pressure in hectoPascals (with checks on reasonable values)

  • Latitude/longitude in degrees

e.g. calculating accumulated cyclone energy (ACE), the input is assumed to be in metres per second and converted to knots

[7]:
# Reload the tracks without units
tracks = huracanpy.load(huracanpy.example_csv_file)

# Calculate ACE without specifying units
ace = tracks.hrcn.get_ace(wind_name="wind10", sum_by="track_id")

# Result has units of knots**2
print(ace)
<xarray.DataArray (track_id: 3)> Size: 24B
array([3.03623809, 2.21637375, 4.83686787])
Coordinates:
  * track_id  (track_id) int64 24B 0 1 2
Attributes:
    units:    knot ** 2

The units can also be specified directly as a function argument

[8]:
# Tell the function that the input wind is in metres per second (same result)
ace = tracks.hrcn.get_ace(wind_name="wind10", sum_by="track_id", wind_units="m s-1")
print(ace)
<xarray.DataArray (track_id: 3)> Size: 24B
array([3.03623809, 2.21637375, 4.83686787])
Coordinates:
  * track_id  (track_id) int64 24B 0 1 2
Attributes:
    units:    knot ** 2
[9]:
# Tell the function that the input wind is in kilometres per second for a wildly
# inaccurate result
ace = tracks.hrcn.get_ace(wind_name="wind10", sum_by="track_id", wind_units="km s-1")
print(ace)
<xarray.DataArray (track_id: 3)> Size: 24B
array([4334686.13967299, 2604709.4088113 , 6294523.15763654])
Coordinates:
  * track_id  (track_id) int64 24B 0 1 2
Attributes:
    units:    knot ** 2

In some cases, the default will check that the values have a reasonable magnitude for the assumed units. e.g. calculated tropical-cyclone category from pressure the units of the track are in Pascals but assumed to be hectoPascals if unspecified.

[10]:
category = tracks.hrcn.get_pressure_category()
print(category)
<xarray.DataArray (record: 99)> Size: 792B
array([0, 0, 0, ..., 2, 2, 1], shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    dimensionless
/home/docs/checkouts/readthedocs.org/user_builds/huracanpy/envs/v1.3.1_a/lib/python3.12/site-packages/huracanpy/tc/_category.py:73: UserWarning: Caution, pressure are likely in Pa, they are being converted to hPa for categorization. In future specify the units explicitly by passing slp_units="Pa" to this function or setting slp.attrs["units"] = "Pa"
  warnings.warn(

If you want to override this check, you can specify the units, either through the attributes or the function arguments

[11]:
category = tracks.hrcn.get_pressure_category(slp_units="hPa")
print(category)
<xarray.DataArray (record: 99)> Size: 792B
array([-1, -1, -1, ..., -1, -1, -1], shape=(99,))
Dimensions without coordinates: record
Attributes:
    units:    dimensionless