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.