Skip to content

fix: preserve falsy but valid multi-index level names#11463

Open
Kropiunig wants to merge 1 commit into
pydata:mainfrom
Kropiunig:fix-multiindex-falsy-level-name
Open

fix: preserve falsy but valid multi-index level names#11463
Kropiunig wants to merge 1 commit into
pydata:mainfrom
Kropiunig:fix-multiindex-falsy-level-name

Conversation

@Kropiunig

Copy link
Copy Markdown

What happened

Building a pandas.MultiIndex-backed index whose level name is falsy but valid — most naturally an empty string "" — raises KeyError through public APIs:

import numpy as np, xarray as xr

ds = xr.Dataset(
    {"v": (("x", "y"), np.arange(6).reshape(3, 2))},
    coords={"": ("x", [10, 20, 30]), "b": ("y", [1, 2])},
)
ds.stack(z=["", "b"])        # KeyError: 'z_level_0'
# ds.set_index(...) with a "" level name fails the same way

An empty string is a perfectly valid coordinate name (ds.sel({"": 20}) works, "" in ds.coords is True), so this is a crash on legitimate input rather than rejection of a disallowed one.

Cause

PandasMultiIndex.__init__ fills in default names for unnamed levels:

name = idx.name or f"{dim}_level_{i}"

pandas represents an unnamed level as None, but or also discards legitimate falsy names ("", 0, False), swapping them for the synthetic {dim}_level_{i}. Meanwhile level_coords_dtype is still keyed by the original name, so the two fall out of sync and create_variables raises KeyError on the lookup self.level_coords_dtype[name].

Fix

Use an explicit is not None check so only truly unnamed (None) levels get the default name:

name = idx.name if idx.name is not None else f"{dim}_level_{i}"

Tests

Added a regression assertion to TestPandasMultiIndex::test_constructor covering a "" level name (fails before, passes after). The existing default-name case (None levels → x_level_0, x_level_1) is unchanged and still asserted.

PandasMultiIndex.__init__ used idx.name or f"{dim}_level_{i}" to fill
in default names for unnamed levels. pandas represents an unnamed level as
None, but or also discards legitimate falsy names such as the empty
string "" (or 0/False), replacing them with the synthetic
default name. That desyncs index.names from level_coords_dtype and
raises KeyError in create_variables when the multi-index is built via
public APIs such as Dataset.stack or Dataset.set_index.

Use an explicit is not None check so only truly unnamed (None) levels
receive the default name.
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