Variable attributes
Fields Polaris writes to a NetCDF file should say what they are: a
long_name, and units where the quantity has a meaningful unit. Getting
that right takes a little care, because a variable computed from another one
does not start out with a blank slate.
xarray propagates attributes
xarray keeps attrs through binary arithmetic, comparisons, where(),
zeros_like() and NumPy ufuncs such as np.maximum:
>>> bottom_pressure.attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
>>> (bottom_pressure / (rho0 * gravity)).attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
>>> xr.zeros_like(bottom_pressure).astype(int).attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
A depth in meters and a one-based level index both come out of that claiming to be a pressure in Pascals. Older versions of xarray dropped attributes in arithmetic, so code written against that behaviour changed meaning silently on upgrade.
Setting individual keys is not enough to undo it:
# leaves any other attribute the parent had, such as a stale
# ``cell_measures`` or a non-standard ``unit``
ds.bottomDepth.attrs['long_name'] = 'seafloor geometric depth'
ds.bottomDepth.attrs['units'] = 'm'
Nor is xr.set_options(keep_attrs=False): it suppresses propagation through
arithmetic and comparisons, but zeros_like() and where() keep attributes
regardless.
Use set_attrs()
polaris.attrs.set_attrs() replaces a variable’s attributes
wholesale, so a field carries exactly what it is given and nothing it
inherited:
from polaris.attrs import set_attrs
# in place, on a variable already in a dataset
ds['maxLevelCell'] = max_level_cell + 1
set_attrs(
ds.maxLevelCell,
long_name='Index to the last active ocean cell in each column.',
)
# inline, on an expression
ds['bottomDepth'] = set_attrs(
-geom_z_bot, long_name='seafloor geometric depth', units='m'
)
Both patterns work: set_attrs() modifies the array in place and returns it.
ds[name] shares the underlying Variable, so labelling ds.maxLevelCell
reaches the dataset; and ds[name] = da copies the attributes, so two dataset
variables built from one array can still describe themselves differently.
Leave units out for quantities that have no meaningful unit — a one-based
level index, a boolean mask — rather than inventing one. Anything else, such
as a note or a standard_name, can be passed as an extra keyword argument.