Gather, scatter & indexing
XTensor methods that select, split, or scatter
along named dimensions — coordinate-aware sel/isel/interp, PyTorch's
gather/scatter family, and slicing/splitting.
Bases: ExtendedTensor
A tensor with named dimensions and, optionally, per-dimension coordinate
labels -- an xarray-like DataArray over a
live torch.Tensor.
- Dimensions are named through
names(self-managed in_axis_names, independent of PyTorch's experimental builtin named-tensor feature, so the class works even where that API has been removed). - Coordinates label the positions along a named dimension. They live
in
coords-- a mapping dim name -> labels -- keyed by dimension name, so they follow their dimension through reshaping/reordering with no positional bookkeeping. A labelled dimension must be named. - Axis descriptors may enrich a name with extra fields -- any custom
key you like (
typeis the OME-NGFF convention shown in examples;orientationis the one field with built-in behaviour) -- passed as a dict in place of a bare name ({"name": "x", "type": "space"}).namesstays the ergonomic view (bare names);axesreturns the full descriptors. The extra fields live in_axis_meta, keyed by dimension name, so they follow the dimension like coordinates do.
Select by label with sel, by integer position with isel, or reach a
single label by attribute (x.red).
Methods:
sel
sel(*indexers_positional: Mapping, mode: Optional[str] = None, tolerance: Any = None, method: Optional[str] = None, **indexers_kwargs: Any) -> Self
Select by coordinate label (or numeric value) along named dims.
x.sel(channel="red") selects the position whose label is "red". A
list of labels selects several positions; a single label drops the
dimension (like integer indexing). For structured coordinates, a
str matches a label's "name", and a dict queries the labels'
fields (x.sel(channel={"type": "signal"})), keeping the axis and
selecting every match.
On a numeric coordinate, the selector is a value (x.sel(t="2s")).
mode chooses which tick an inexact value snaps to:
"round"(default) — the nearest tick by value;"floor"/"ceil"— the largest tick<=/ smallest tick>=the value (value space, robust to a descending coordinate);"prev"/"next"— the neighbouring tick at the lower / higher index (tick order; needs a monotonic coordinate).
tolerance (a value in the position unit) caps the allowed gap. A
bare .sel(t=v) is exact (tolerance=0); passing a mode
implies an unbounded snap unless a tolerance is given.
A slice(lo, hi) on a numeric coordinate is a value range,
unit-aware, resolving to a contiguous integer slice — half-open
like ordinary Python indexing (lo <= value < hi), not xarray's
inclusive-both-ends convention (see the "Differences from xarray"
guide). Bounds are compared numerically regardless of order or of
the coordinate's own direction: t=slice(1, 5) and t=slice(5, 1)
select the same range. A one-sided range keeps the bound in the
slot it was given (slice(1, None) -> value >= 1; slice(None,
5) -> value < 5); an out-of-range or empty result is a
well-formed empty axis, not an error. slice.step is not supported
(mode/tolerance don't apply to a range either).
A joint query over several dims sharing one coordinate (e.g. a
lat/lon pair that together locate a point on a 2-D grid) picks
all of those dims' positions in one shot: pass a value for every
coordinate name that spans the same dims (x.sel(lat=52.1,
lon=4.3)) — no dedicated syntax, ordinary keyword arguments that
happen to share dims are recognised as one joint system. Only a
square query is supported (exactly one coordinate value per
spanned dim); an under- or over-determined query raises rather than
guessing. Only mode="round" (the default) applies to a joint
query — floor/ceil/prev/next have no well-defined meaning
across several coupled dims at once. tolerance still applies, per
queried coordinate name (a bare query is exact by default), checked
against the chosen position's own value.
On an irregular grid coordinate (an explicit multi-dim array
with no regular spacing, e.g. an irregular satellite-swath
lat/lon), a joint query works the same way — one value per
coordinate name spanning the same dims — but resolves to the
single nearest grid point across the queried coordinates' raw
magnitudes. Mixing coordinates with very different units (degrees
and metres, say) weighs the nearer one more heavily, same as any
unnormalised distance always does. Only a single point is
supported per call, not a vectorized query over many points at
once. tolerance/mode/method behave the same as the joint case
above (only the default "nearest" mode applies; a gap over
tolerance raises).
Pass indexers as an explicit mapping (x.sel({"mode": "red"}))
instead of keyword arguments when a dim's name collides with one
of sel's own keyword parameters (mode, tolerance, method)
— xarray's own escape hatch for exactly this, since a keyword
argument matching one of those names is always bound to the
parameter, never reaching the indexers. Passing both raises.
isel
Select by integer position along named dimensions.
x.isel(row=0, col=slice(1, 3)) indexes row at position 0 and col
at positions 1..2, leaving the other axes untouched.
interp
interp(*indexers_positional: Mapping, method: Any = 'linear', bound: Any = None, extrapolate: Any = None, name: Optional[str] = None, **indexers_kwargs: Any) -> Self
Interpolate onto new coordinate values along named dims.
Where sel picks existing positions,
interp computes values at arbitrary positions of a numeric
coordinate, the xarray way:
x.interp(t=2.5) # one point -> drops the axis
x.interp(t=[0.0, 0.5, 1.0]) # several -> keeps the axis
x.interp(t="2.5s") # unitful (backend converts)
x.interp(t=q, method="cubic") # a query tensor (grads flow)
method is the interpolation order -- "nearest" (built in) or a
higher order ("linear" (default), "quadratic", "cubic", or an
int), which needs the optional fiery.interpol backend
(pip install fiery-xtensor[interp]). An out-of-range query follows
bound (default: the interp_bound option -- "replicate" clamps
to the edge) and extrapolate (default: the interp_extrapolate
option); both can be set with
set_options.
A scalar query drops the axis (like sel); a list/tensor keeps
it, its coordinate becoming the queried positions. A regular
(evenly-spaced) coordinate supports every method; an irregular
(explicit values) one only supports "nearest"/"linear", both
exact, since the map between value space and index space is locally
linear between two bracketing ticks. A higher order needs a true
non-uniform spline in value space, which isn't currently supported
for an irregular coordinate.
A joint query over several dims that share one coordinate (e.g. a
lat/lon pair spanning several dims at once) resolves to a
fractional position -- never rounded -- across all of them at
once, then interpolates in that many dimensions together (falling
back to a built-in nearest gather for method="nearest", no extra
backend needed). A query with every name given as a scalar is a
single point: all the spanned dims drop, like the 1-D scalar case
above. Any name given as a list/tensor makes it "many": every
name's query broadcasts to a common length N, and the spanned dims
collapse into one new axis of N sampled points -- not an
outer-product grid, since the dims are coupled and you can't vary
one queried name without moving through every spanned dim at once
(mirroring xarray's own vectorized/pointwise-indexing convention for
a value-based query on a multi-dim coordinate). The new axis is
named name if given, else the shared name of any query that is
itself a named 1-D XTensor -- x.interp(lat=XTensor([...],
names=("pts",)), lon=[...]) needs no name= at all, mirroring how
xarray derives the result's new dimension from the indexer
arrays' own shared dim name -- else unnamed (matching
xstack's convention for a brand-new axis
with nothing to infer from). When a name is resolved, the axis
carries every queried name's own sampled values as a riding
coordinate -- an unnamed axis can't be keyed, so it has none. Only
one such joint group is supported per call; call interp
again for a second group.
Pass indexers as an explicit mapping (x.interp({"method":
5.0})) instead of keyword arguments when a dim's name collides
with one of interp's own keyword parameters (method, bound,
extrapolate, name) -- xarray's own escape hatch for exactly
this, since a keyword argument matching one of those names is
always bound to the parameter, never reaching the indexers.
Passing both raises.
gather
Name-aware torch.gather: behaves like torch.gather, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.gather for the full numerical behaviour.
index_add
Name-aware torch.index_add: behaves like torch.index_add, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_add for the full numerical behaviour.
index_copy
Name-aware torch.index_copy: behaves like torch.index_copy, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_copy for the full numerical behaviour.
index_fill
Name-aware torch.index_fill: behaves like torch.index_fill, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_fill for the full numerical behaviour.
index_select
Name-aware torch.index_select: behaves like torch.index_select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.index_select for the full numerical behaviour.
masked_select
Name-aware torch.masked_select: behaves like torch.masked_select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.masked_select for the full numerical behaviour.
nonzero
Name-aware torch.nonzero: behaves like torch.nonzero, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.nonzero for the full numerical behaviour.
scatter
Name-aware torch.scatter: behaves like torch.scatter, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.scatter for the full numerical behaviour.
scatter_add
Name-aware torch.scatter_add: behaves like torch.scatter_add, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.scatter_add for the full numerical behaviour.
take_along_dim
Name-aware torch.take_along_dim: behaves like torch.take_along_dim, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.take_along_dim for the full numerical behaviour.
where
Name-aware torch.where: behaves like torch.where, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.where for the full numerical behaviour.
chunk
Name-aware torch.chunk: behaves like torch.chunk, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.chunk for the full numerical behaviour.
flip
Name-aware torch.flip: behaves like torch.flip, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.flip for the full numerical behaviour.
narrow
Name-aware torch.narrow: behaves like torch.narrow, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.narrow for the full numerical behaviour.
roll
Name-aware torch.roll: behaves like torch.roll, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.roll for the full numerical behaviour.
select
Name-aware torch.select: behaves like torch.select, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.select for the full numerical behaviour.
split
Name-aware torch.split: behaves like torch.split, but this tensor's names (and coordinates, where applicable) propagate onto the result. See torch.split for the full numerical behaviour.