Editing a cube's dimensions and variables¶
Four members change a cube's structure rather than its cells - rename an axis, restamp an axis's coordinates, remove an axis, or fold in variables from another cube:
| Member | What it does |
|---|---|
rename_dims |
rename a dimension (time -> t) |
assign_coords |
replace an existing dimension's coordinate values |
drop_dims |
remove a dimension, and the variables defined along it |
update |
add or replace variables from another cube, in place |
They act on band dimensions - a cube's non-spatial axes, such as time, plev or level. The
horizontal (y, x) plane is pinned by the geotransform and is never one of them.
All four are deliberately faithful to the file: a rename leaves no orphan dimension behind, keeps the variable inventory (CF bounds and sub-groups included), and carries coordinate and global attributes across. The cells below show each of those on real sample files.
Setup¶
One notebook-relative data path. The sample files ship in the repo's examples/data/netcdf/ folder.
from pathlib import Path
import numpy as np
from pyramids.netcdf import NetCDF
SAMPLES = Path('../../../examples/data/netcdf/samples')
# A CF file with a time axis, a 17-level pressure axis, CF bounds and 18 global attributes.
CF = SAMPLES / 'cf__12v__1d4-2d5-3d2-4d1__y-asc.nc'
# A COARDS cube whose `level` axis has four steps - handy for restamping.
LEVELS = SAMPLES / 'coards__5v__1d4-4d1__y-desc.nc'
# A NetCDF-4 store that keeps its variables in sub-groups.
GROUPS = SAMPLES / 'none__35v__1d35__groups-nc4.nc'
The cube we start from¶
dimension_sizes lists every dimension and its length; variable_names lists the data variables
(dimension coordinate arrays are not data variables, so they are not in it).
nc = NetCDF.read_file(str(CF))
nc.dimension_sizes, nc.variable_names, len(nc.global_attributes)
/home/runner/work/pyramids/pyramids/.pixi/envs/docs/lib/python3.14/site-packages/osgeo/gdal.py:8289: RuntimeWarning: Cannot find variable corresponding to coordinate height return _gdal.MDArray_GetCoordinateVariables(self, *args)
({'lat': 128, 'lon': 256, 'bnds': 2, 'plev': 17, 'time': 1},
['area', 'msk_rgn', 'pr', 'tas', 'ua'],
18)
rename_dims - relabel a band dimension¶
Pass {old: new}, or old=new keywords. Renaming moves no cells.
renamed = nc.rename_dims(time='t')
renamed.dimension_names
['bnds', 'lat', 'lon', 'plev', 't']
time is gone, not left behind as an unused dimension - and nothing else moved. The variable
inventory is identical, and all 18 global attributes came across:
(
renamed.variable_names == nc.variable_names,
len(renamed.global_attributes),
)
(True, 18)
The renamed axis keeps its own coordinate values, under the new name:
np.asarray(renamed.coords['t'])
array([730135.5])
Renaming on a single variable is free - it relabels metadata and keeps the variable's lazy read, without copying any cells:
variable = nc.get_variable('tas')
variable.rename_dims(time='t').dimension_names
assign_coords - restamp an existing axis¶
This replaces the coordinate values of a band dimension that already exists. pyramids has no index model, so attaching a brand-new coordinate is not supported - restamping an existing axis is. The values must match the axis length, which is checked up front.
levels = NetCDF.read_file(str(LEVELS))
np.asarray(levels.coords['level'])
array([1000., 925., 850., 700.], dtype=float32)
restamped = levels.assign_coords(level=[1000.0, 850.0, 700.0, 500.0])
np.asarray(restamped.coords['level'])
array([1000., 850., 700., 500.])
drop_dims - drop an axis and the variables along it¶
A dimension cannot be dropped on its own: the variables defined along it go with it. Here only ua
spans the pressure axis.
[
name
for name in nc.variable_names
if 'plev' in (nc.get_variable(name).dimension_names or [])
]
['ua']
without_plev = nc.drop_dims('plev')
without_plev.dimension_names, without_plev.variable_names
/home/runner/work/pyramids/pyramids/.pixi/envs/docs/lib/python3.14/site-packages/osgeo/gdal.py:8289: RuntimeWarning: Cannot find variable corresponding to coordinate height return _gdal.MDArray_GetCoordinateVariables(self, *args)
(['bnds', 'lat', 'lon', 'time'], ['area', 'msk_rgn', 'pr', 'tas'])
ua is gone and so is plev itself. Each surviving variable keeps its stored form exactly - a
CF-packed variable stays packed at its on-disk dtype, with its scale, offset and no-data value.
drop_dims does not mutate the cube it was called on; it returns a new one:
nc.variable_names
['area', 'msk_rgn', 'pr', 'tas', 'ua']
update - fold variables in, in place¶
update writes another cube's variables into this one, replacing same-named ones and adding the
rest. Unlike the three above it mutates the receiver and returns None, as xarray's does. Every
donor is checked against the receiver's grid and band axes before anything is written, so a
mismatched update leaves the cube untouched rather than half-applied.
base = NetCDF.read_file(str(CF))
base.update({'tas_copy': base.get_variable('tas')})
sorted(base.variable_names)
['area', 'msk_rgn', 'pr', 'tas', 'tas_copy', 'ua']
Hierarchical stores¶
NetCDF-4 files can keep their variables in sub-groups. The bookkeeping members rebuild the whole store, so the groups and everything in them come across - all 29 variables across 7 sub-groups here, with the shared axis relabelled.
groups = NetCDF.read_file(str(GROUPS))
len(groups.variable_names), len(groups.group_names), groups.dimension_names
(29, 7, ['recNum'])
regrouped = groups.rename_dims(recNum='record')
len(regrouped.variable_names), len(regrouped.group_names), regrouped.dimension_names
(29, 7, ['record'])
Recap¶
rename_dims(old=new)renames a dimension; no orphan left behind, inventory and attributes kept.assign_coords(dim=values)replaces an existing dimension's coordinates; length checked up front.drop_dims(name)removes a dimension and the variables along it, returning a new cube.update(other)adds or replaces variables in place, validating every donor before it writes.
All four act on the non-spatial (band) axes only - the horizontal plane stays pinned to the
geotransform. For the wider tour of the class, see the NetCDF anatomy notebook.