pint-cf¶
pint-cf is a lightweight, pure-Python package that extends Pint with a
CF-compliant unit registry and formatter.
Features¶
A CF-compliant Pint
UnitRegistrythat understands UDUNITS-2 unit strings.A
cfformat forUnit/Quantitythat writes results back out as CF strings.CFContext, which correctly reads CF’sunits_metadatatemperature attribute.CF units that UDUNITS-2 itself doesn’t define (
level,psu,decibel,sverdrup’sSvsymbol, …), included by default.
Notes¶
No support for time coordinates (e.g.
days since 2001-01-01) or climate calendars (e.g.360_day) - Pint has no notion of a time origin or calendar, so it can’t represent these as units. Use cftime for those instead.decibel/belare treated as plain dimensionless ratio units, with no physical reference level attached, matching the convention used bycfunitsandcf-units.
Installation¶
Install via pip:
pip install pint-cf
or via conda, from conda-forge:
conda install conda-forge::pint-cf
Usage¶
Create a CF-ready unit registry with cf_unitregistry(). This also
registers the cf format.
from pint_cf import cf_unitregistry
ureg = cf_unitregistry()
q = ureg("10 meters per second^2").to("km s-2")
print(f"{q:cf}") # 0.01 kilometer-second^-2
print(f"{q:~cf}") # 0.01 km/s2
Temperature units_metadata¶
CF distinguishes an on-scale temperature (e.g. today’s temperature) from a
temperature difference (e.g. an anomaly) with a units_metadata
attribute - the plain units string alone can’t tell them apart.
When reading a variable that has this attribute, wrap the value in
CFContext:
from pint_cf import cf_unitregistry, CFContext
ureg = cf_unitregistry()
with CFContext(units_metadata="temperature: difference"):
q = ureg.Quantity(1, "degree_C")
print(q.units) # delta_degree_Celsius
Without CFContext (or with units_metadata absent), nothing changes - an
on-scale temperature is read as usual.
When writing a result back out, cf_attributes_for does the reverse: it
derives the units_metadata attribute from an already-computed Unit or
Quantity, ready to merge into a NetCDF variable’s attributes:
from pint_cf import cf_attributes_for
print(cf_attributes_for(q)) # {"units_metadata": "temperature: difference"}
print(cf_attributes_for(ureg.Unit("meter"))) # {} - not a temperature unit
ds[name].attrs.update(cf_attributes_for(q.units))
This round-trips exactly for Celsius and Fahrenheit. Kelvin and Rankine
have no zero-offset, so on-scale and difference look identical for them -
cf_attributes_for reports "temperature: unknown" rather than guessing.
CF units not in UDUNITS-2¶
CF defines a few units that UDUNITS-2 doesn’t, sourced from
cfunits. cf_unitregistry()
includes them by default:
Unit |
Behavior |
|---|---|
|
Dimensionless; kept only for legacy COARDS files, so using one raises a |
|
|
|
Plain dimensionless ratio units (see “Notes” above). |
|
|
Pass cf_extensions=False to cf_unitregistry() for a registry that
matches plain UDUNITS-2 instead, without any of these additions.
Third-party notices¶
The UDUNITS-2 unit database this package is built on is redistributed under its own license - see THIRD_PARTY_LICENSES.md.