From 4eeba15b3857405aef7218ee10907d070a42d148 Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Fri, 26 Jun 2026 09:26:34 +0800 Subject: [PATCH] Fix MultiIndex sel with tuple-valued levels (GH#11341) Problem: When a MultiIndex level contains tuple-valued entries (e.g., (1,1)), selecting with a nested tuple key like ((1,1), 2) incorrectly preserved the dimension instead of collapsing it to a scalar result. Root cause: _is_nested_tuple() was checking for 'tuple' in addition to 'list' and 'slice', which caused it to misidentify tuple-valued keys as nested selection tuples. Fix: Remove 'tuple' from the isinstance check in _is_nested_tuple() so that only 'list' and 'slice' are treated as indicators of nested selections. Tuple- valued keys in MultiIndex levels are now correctly handled as scalar key values. Added regression test for selecting with nested tuple keys on MultiIndex with tuple-valued levels. --- xarray/core/indexes.py | 2 +- xarray/tests/test_indexes.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 2242e57e482..123bc75a21b 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -610,7 +610,7 @@ def _asarray_tuplesafe(values): def _is_nested_tuple(possible_tuple): return isinstance(possible_tuple, tuple) and any( - isinstance(value, tuple | list | slice) for value in possible_tuple + isinstance(value, list | slice) for value in possible_tuple ) diff --git a/xarray/tests/test_indexes.py b/xarray/tests/test_indexes.py index 94adcc3b935..0dbdaba5043 100644 --- a/xarray/tests/test_indexes.py +++ b/xarray/tests/test_indexes.py @@ -509,6 +509,28 @@ def test_sel(self) -> None: with pytest.raises(IndexError): index.sel({"x": (slice(None), 1, "no_level")}) + def test_sel_nested_tuple_key(self) -> None: + """Test that tuple-valued MultiIndex levels can be selected with a single key. + + Regression test for GH#11341: when a MultiIndex level contains tuples, + selecting with a nested tuple key ((1, 1), 2) should collapse the dimension + just like selecting with a non-nested tuple key (1, 2). + """ + # Create a MultiIndex where the first level contains tuples + nested_level_0 = pd.Index( + [(1, 1), (1, 1), (2, 2), (3, 3)], name="a", tupleize_cols=False + ) + nested_level_1 = pd.Index([1, 2, 10, 20], name="b") + nested_mi = pd.MultiIndex.from_arrays([nested_level_0, nested_level_1]) + + index = PandasMultiIndex(nested_mi, "index") + + # Select with a nested tuple key - should return scalar indexer + actual = index.sel({"index": ((1, 1), 2)}) + # pandas.get_loc returns an integer for exact match + expected_dim_indexers = {"index": 1} + assert actual.dim_indexers == expected_dim_indexers + def test_join(self): midx = pd.MultiIndex.from_product([["a", "aa"], [1, 2]], names=("one", "two")) level_coords_dtype = {"one": "=U2", "two": "i"}