.. currentmodule:: xarray Creating and saving objects with units ====================================== Attaching units --------------- .. ipython:: python :suppress: import pint import pint_xarray import xarray as xr Usually, when loading data from disk we get a :py:class:`Dataset` or :py:class:`DataArray` with units in attributes: .. ipython:: In [1]: ds = xr.Dataset( ...: { ...: "a": (("lon", "lat"), [[11.84, 3.12, 9.7], [7.8, 9.3, 14.72]]), ...: "b": (("lon", "lat"), [[13, 2, 7], [5, 4, 9]], {"units": "m"}), ...: }, ...: coords={"lat": [10, 20, 30], "lon": [74, 76]}, ...: ) ...: ds In [2]: da = ds.b ...: da In order to get :py:class:`pint.Quantity` instances, we can use the :py:meth:`Dataset.pint.quantify` or :py:meth:`DataArray.pint.quantify` methods: .. ipython:: In [3]: ds.pint.quantify() We can also override the units of a variable: .. ipython:: In [4]: ds.pint.quantify(b="km") In [5]: da.pint.quantify("degree") Overriding works even if there is no ``units`` attribute, so we could use this to attach units to a normal :py:class:`Dataset`: .. ipython:: In [6]: temporary_ds = xr.Dataset({"a": ("x", [0, 5, 10])}, coords={"x": [1, 2, 3]}) ...: temporary_ds.pint.quantify({"a": "m"}) Of course, we could use :py:class:`pint.Unit` instances instead of strings to specify units, too. .. note:: Unit objects tied to different registries cannot interact with each other. In order to avoid this, :py:meth:`DataArray.pint.quantify` and :py:meth:`Dataset.pint.quantify` will make sure only a single registry is used per ``xarray`` object. If we wanted to change the units of the data of a :py:class:`DataArray`, we could do so using the :py:attr:`DataArray.name` attribute: .. ipython:: In [7]: da.pint.quantify({da.name: "J", "lat": "degree", "lon": "degree"}) However, `xarray`_ currently doesn't support `units in indexes`_, so the new units were set as attributes. To really observe the changes the ``quantify`` methods make, we have to first swap the dimensions: .. ipython:: In [8]: ds_with_units = ds.swap_dims({"lon": "x", "lat": "y"}).pint.quantify( ...: {"lat": "degree", "lon": "degree"} ...: ) ...: ds_with_units In [9]: da_with_units = da.swap_dims({"lon": "x", "lat": "y"}).pint.quantify( ...: {"lat": "degree", "lon": "degree"} ...: ) ...: da_with_units By default, :py:meth:`Dataset.pint.quantify` and :py:meth:`DataArray.pint.quantify` will use the unit registry at :py:obj:`pint_xarray.unit_registry` (the :py:func:`application registry `). If we want a different registry, we can either pass it as the ``unit_registry`` parameter: .. ipython:: In [10]: ureg = pint.UnitRegistry(force_ndarray_like=True) ...: # set up the registry In [11]: da.pint.quantify("degree", unit_registry=ureg) or overwrite the default registry: .. ipython:: In [12]: pint_xarray.unit_registry = ureg In [13]: da.pint.quantify("degree") .. note:: To properly work with ``xarray``, the ``force_ndarray_like`` or ``force_ndarray`` options have to be enabled on the custom registry. Without it, python scalars wrapped by :py:class:`pint.Quantity` may raise errors or have their units stripped. Saving with units ----------------- In order to not lose the units when saving to disk, we first have to call the :py:meth:`Dataset.pint.dequantify` and :py:meth:`DataArray.pint.dequantify` methods: .. ipython:: In [10]: ds_with_units.pint.dequantify() In [11]: da_with_units.pint.dequantify() This will get the string representation of a :py:class:`pint.Unit` instance and attach it as a ``units`` attribute. The data of the variable will now be whatever `pint`_ wrapped. .. _pint: https://pint.readthedocs.io/en/stable/ .. _xarray: https://docs.xarray.dev/en/stable/ .. _units in indexes: https://github.com/pydata/xarray/issues/1603