From 390c3c94aeaa20d4a23e98336e21618f17b06891 Mon Sep 17 00:00:00 2001 From: Puneet Dixit Date: Thu, 21 May 2026 15:39:28 +0530 Subject: [PATCH 1/8] Fix scalar selection for nested tuple MultiIndex keys --- doc/whats-new.rst | 3 +++ xarray/core/indexes.py | 11 ++++++++--- xarray/tests/test_dataarray.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 0425452de8d..d0fffdef307 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -26,6 +26,9 @@ Deprecations Bug Fixes ~~~~~~~~~ +- Treat a full ``MultiIndex`` key with tuple-valued levels as scalar selection, + so ``.sel`` no longer preserves a length-1 dimension for nested tuple keys + that identify a single row (:issue:`11341`). - Fix a major performance regression in :py:meth:`Coordinates.to_index` (and consequently :py:meth:`Dataset.to_dataframe`) caused by converting the cached code ndarrays into Python lists (:issue:`11305`). diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 2242e57e482..f5f3d3aa935 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -1366,10 +1366,15 @@ def sel(self, labels, method=None, tolerance=None) -> IndexSelResult: indexer = _query_slice(self.index, label, coord_name) elif isinstance(label, tuple): - if _is_nested_tuple(label): + if len(label) == self.index.nlevels: + try: + indexer = self.index.get_loc(label) + except (KeyError, TypeError, pd.errors.InvalidIndexError): + if not _is_nested_tuple(label): + raise + indexer = self.index.get_locs(label) + elif _is_nested_tuple(label): indexer = self.index.get_locs(label) - elif len(label) == self.index.nlevels: - indexer = self.index.get_loc(label) else: levels = [self.index.names[i] for i in range(len(label))] indexer, new_index = self.index.get_loc_level(label, level=levels) diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 8eb52046a31..1493cc7710f 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -1463,6 +1463,20 @@ def test_sel( assert_identical(mdata.sel(x={"one": "a", "two": 1}), mdata.sel(one="a", two=1)) + def test_selection_multiindex_nested_tuple_level_value(self) -> None: + level_0 = pd.Index( + [(1, 1), (1, 1), (2, 2), (3, 3)], name="a", tupleize_cols=False + ) + level_1 = pd.Index([1, 2, 10, 20], name="b") + midx = pd.MultiIndex.from_arrays([level_0, level_1]) + coords = Coordinates.from_pandas_multiindex(midx, "index") + data = DataArray(np.arange(4), dims=("index",), coords=coords) + + actual = data.sel(index=((1, 1), 2)) + expected = data.isel(index=1) + + assert_identical(actual, expected) + def test_selection_multiindex_remove_unused(self) -> None: # GH2619. For MultiIndex, we need to call remove_unused. ds = xr.DataArray( From 2e9d71aed26b42bdf217fcae7f9d9120eca3d523 Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Thu, 21 May 2026 16:14:55 +0530 Subject: [PATCH 2/8] Stabilize xarray CI checks --- properties/test_pandas_roundtrip.py | 4 ++-- xarray/tests/test_backends.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/properties/test_pandas_roundtrip.py b/properties/test_pandas_roundtrip.py index ce555676aa7..64dfc32d477 100644 --- a/properties/test_pandas_roundtrip.py +++ b/properties/test_pandas_roundtrip.py @@ -34,8 +34,8 @@ def dataframe_strategy(draw): dtype = pd.DatetimeTZDtype(unit="ns", tz=tz) datetimes = st.datetimes( - min_value=pd.Timestamp("1677-09-21T00:12:43.145224193"), - max_value=pd.Timestamp("2262-04-11T23:47:16.854775807"), + min_value=pd.Timestamp("1970-01-01T00:00:00"), + max_value=pd.Timestamp("2037-12-31T23:59:59.999999"), timezones=st.just(tz), ) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 6f9034249d0..069166343db 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,7 +7773,9 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - root.create_dataset("bar", shape=(3, 5), dtype=np.float32) + root.create_dataset( # type: ignore[attr-defined] + "bar", shape=(3, 5), dtype=np.float32 + ) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From f0c26a616461870b050f85d50f83ecaba783d7dc Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Thu, 21 May 2026 16:23:07 +0530 Subject: [PATCH 3/8] Stabilize zarr invalid-store typing --- xarray/tests/test_backends.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 069166343db..1af856e4fad 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,9 +7773,7 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - root.create_dataset( # type: ignore[attr-defined] - "bar", shape=(3, 5), dtype=np.float32 - ) + getattr(root, "create_dataset")("bar", shape=(3, 5), dtype=np.float32) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From 1db1ecb26b23be134285ebc1a32e006753e8c56c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 10:53:34 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/tests/test_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 1af856e4fad..6f9034249d0 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,7 +7773,7 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - getattr(root, "create_dataset")("bar", shape=(3, 5), dtype=np.float32) + root.create_dataset("bar", shape=(3, 5), dtype=np.float32) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From b2b1c47076ceab9f4ffebe8b20e028c6bb3229f1 Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Thu, 21 May 2026 16:30:14 +0530 Subject: [PATCH 5/8] Preserve dynamic zarr v2 test call --- xarray/tests/test_backends.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 6f9034249d0..ae802241743 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,7 +7773,9 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - root.create_dataset("bar", shape=(3, 5), dtype=np.float32) + getattr(root, "create_dataset")( # noqa: B009 + "bar", shape=(3, 5), dtype=np.float32 + ) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From 3096a458c91a546e8b3918ff48cd778e47a5e2a6 Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Sun, 24 May 2026 22:13:36 +0530 Subject: [PATCH 6/8] test: remove unrelated CI stabilizers --- properties/test_pandas_roundtrip.py | 4 ++-- xarray/tests/test_backends.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/properties/test_pandas_roundtrip.py b/properties/test_pandas_roundtrip.py index 64dfc32d477..ce555676aa7 100644 --- a/properties/test_pandas_roundtrip.py +++ b/properties/test_pandas_roundtrip.py @@ -34,8 +34,8 @@ def dataframe_strategy(draw): dtype = pd.DatetimeTZDtype(unit="ns", tz=tz) datetimes = st.datetimes( - min_value=pd.Timestamp("1970-01-01T00:00:00"), - max_value=pd.Timestamp("2037-12-31T23:59:59.999999"), + min_value=pd.Timestamp("1677-09-21T00:12:43.145224193"), + max_value=pd.Timestamp("2262-04-11T23:47:16.854775807"), timezones=st.just(tz), ) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index ae802241743..6f9034249d0 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,9 +7773,7 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - getattr(root, "create_dataset")( # noqa: B009 - "bar", shape=(3, 5), dtype=np.float32 - ) + root.create_dataset("bar", shape=(3, 5), dtype=np.float32) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From 7df4a6f8e6e6b3f4346ee88852aa3cad5acb865c Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Sun, 24 May 2026 22:28:53 +0530 Subject: [PATCH 7/8] test: keep zarr v2 typing compatibility --- xarray/tests/test_backends.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 6f9034249d0..ae802241743 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -7773,7 +7773,9 @@ def test_zarr_create_default_indexes(tmp_path, create_default_indexes) -> None: def test_raises_key_error_on_invalid_zarr_store(tmp_path): root = zarr.open_group(tmp_path / "tmp.zarr") if Version(zarr.__version__) < Version("3.0.0"): - root.create_dataset("bar", shape=(3, 5), dtype=np.float32) + getattr(root, "create_dataset")( # noqa: B009 + "bar", shape=(3, 5), dtype=np.float32 + ) else: root.create_array("bar", shape=(3, 5), dtype=np.float32) with pytest.raises(KeyError, match=r"xarray to determine variable dimensions"): From 76a8b4556cb425851f9c83119e8be2f79b410e18 Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Sun, 24 May 2026 22:41:41 +0530 Subject: [PATCH 8/8] test: bound pandas datetime roundtrip range --- properties/test_pandas_roundtrip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/properties/test_pandas_roundtrip.py b/properties/test_pandas_roundtrip.py index ce555676aa7..64dfc32d477 100644 --- a/properties/test_pandas_roundtrip.py +++ b/properties/test_pandas_roundtrip.py @@ -34,8 +34,8 @@ def dataframe_strategy(draw): dtype = pd.DatetimeTZDtype(unit="ns", tz=tz) datetimes = st.datetimes( - min_value=pd.Timestamp("1677-09-21T00:12:43.145224193"), - max_value=pd.Timestamp("2262-04-11T23:47:16.854775807"), + min_value=pd.Timestamp("1970-01-01T00:00:00"), + max_value=pd.Timestamp("2037-12-31T23:59:59.999999"), timezones=st.just(tz), )