Skip to content

Respect drop=True for scalar coordinates an index keeps - #11617

Open
matthiasschabel wants to merge 6 commits into
pydata:mainfrom
matthiasschabel:pr/drop-scalar-index-coords
Open

matthiasschabel wants to merge 6 commits into
pydata:mainfrom
matthiasschabel:pr/drop-scalar-index-coords

Conversation

@matthiasschabel

@matthiasschabel matthiasschabel commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Extracting bare pixel samples from a raster with drop=True does not drop the scalar coordinates when the raster's index keeps them:

raster.isel(x=i, y=j, drop=True).coords     # still has 0-d 'x' and 'y'
  • The documented contract of isel(..., drop=True) and sel(..., drop=True) is that scalar coordinates produced by the selection are dropped.
  • An index whose isel rebuilds the selected position as a 0-d coordinate returns it in index_variables, and the isel paths copy every index variable into the result before drop is considered.
  • The built-in indexes return None from isel for a scalar selection, so they never hit this and are unchanged.

The fix adds drop_scalar_index_coords, called from DataArray.isel, Dataset.isel and Dataset._isel_fancy when drop=True. It drops the index-owned scalar coordinates the way drop_vars would and raises the existing corrupt-index error when only some of an index's coordinates would go (selecting one axis of the raster with drop=True). Indexes the selection does not touch are left alone; sel(drop=True) delegates to isel.

On the new error: the previous result broke the drop=True contract, and drop_vars already raises the same error for a partial drop. It only affects custom indexes that keep scalar coordinates, so it is listed under Bug Fixes.

Reproducer with a scalar-keeping RasterIndex

The guide's RasterIndex plus an isel that keeps the selected position as a 0-d coordinate.

import numpy as np, xarray as xr
from xarray import Index, Variable
from xarray.core.indexes import PandasIndex

class RasterIndex(Index):
    def __init__(self, xy_indexes, scalars=None):
        self._xy_indexes = dict(xy_indexes); self._scalars = dict(scalars or {})
    @classmethod
    def from_variables(cls, variables, *, options):
        return cls({k: PandasIndex.from_variables({k: v}, options=options) for k, v in variables.items()})
    def create_variables(self, variables=None):
        out = dict(self._scalars)
        for index in self._xy_indexes.values():
            out.update(index.create_variables(variables))
        return out
    def isel(self, indexers):
        axes, scalars = {}, dict(self._scalars)
        for k, idx in self._xy_indexes.items():
            if idx.dim in indexers:
                new = idx.isel({idx.dim: indexers[idx.dim]})
                if new is None: scalars[k] = Variable((), idx.index[indexers[idx.dim]])
                else: axes[k] = new
            else: axes[k] = idx
        return RasterIndex(axes, scalars)

da = xr.DataArray(np.zeros((100, 50)), coords={"x": np.arange(50), "y": np.arange(100)}, dims=("y", "x"))
raster = da.set_xindex(["x", "y"], RasterIndex)
print(raster.isel(x=1, y=0, drop=True).coords)   # main: x and y kept; this PR: empty

Checklist

  • Tests added
  • User visible changes (including notable bug fixes) are documented in whats-new.rst

AI Disclosure

  • This PR contains AI-generated content.
    • I have tested any AI-generated content in my PR.
    • I take responsibility for any AI-generated content in my PR.
      Tools: Codex (gpt-6-sol) drafted the change and tests at my direction; Claude (Opus 5.5) and Codex (gpt-6-astra) reviewed. I have read and validated every line.

isel and sel with drop=True promise to drop the scalar coordinates a
selection produces. When the selected coordinate belongs to an index
whose isel rebuilds the 0-d coordinate itself, isel_indexes returns it
among the index variables and the coordinate was silently kept.

Drop such coordinates the way drop_vars would, raising the existing
corrupt-index error when only some of an index's coordinates would go.
The default PandasIndex returns no index for a scalar selection, so its
behaviour is unchanged.
@matthiasschabel
matthiasschabel force-pushed the pr/drop-scalar-index-coords branch from 2454f7b to 6a630ad Compare September 26, 2026 20:23
@matthiasschabel
matthiasschabel marked this pull request as ready for review September 27, 2026 16:27
@dcherian
dcherian requested a review from benbovy September 28, 2026 13:36
matthiasschabel and others added 4 commits September 28, 2026 12:03
The Bug Fixes entry added with the fix was dropped while resolving a
merge from main around the v2026.09.0 release. Re-add it under the
unreleased section, naming DataArray as well as Dataset, since both
isel paths changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-By: Codex (gpt-6-astra, reasoning effort high)
…coords

# Conflicts:
#	xarray/tests/test_dataset.py

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant