Skip to content

Commit 35b338e

Browse files
authored
BUG: .loc failing to drop first level (#42435)
1 parent 82eb380 commit 35b338e

File tree

5 files changed

+39
-15
lines changed

5 files changed

+39
-15
lines changed

pandas/core/indexes/multi.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -3030,16 +3030,20 @@ def maybe_mi_droplevels(indexer, levels):
30303030
# everything
30313031
continue
30323032
else:
3033-
raise TypeError(
3034-
f"Expected label or tuple of labels, got {key}"
3035-
)
3033+
# e.g. test_xs_IndexSlice_argument_not_implemented
3034+
k_index = np.zeros(len(self), dtype=bool)
3035+
k_index[loc_level] = True
3036+
30363037
else:
30373038
k_index = loc_level
30383039

30393040
elif com.is_null_slice(k):
30403041
# taking everything, does not affect `indexer` below
30413042
continue
3043+
30423044
else:
3045+
# FIXME: this message can be inaccurate, e.g.
3046+
# test_series_varied_multiindex_alignment
30433047
raise TypeError(f"Expected label or tuple of labels, got {key}")
30443048

30453049
if indexer is None:

pandas/core/indexing.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -882,17 +882,21 @@ def _getitem_nested_tuple(self, tup: tuple):
882882
if self.name != "loc":
883883
# This should never be reached, but lets be explicit about it
884884
raise ValueError("Too many indices")
885-
if isinstance(self.obj, ABCSeries) and any(
886-
isinstance(k, tuple) for k in tup
887-
):
888-
# GH#35349 Raise if tuple in tuple for series
889-
raise ValueError("Too many indices")
890885
if all(is_hashable(x) or com.is_null_slice(x) for x in tup):
891886
# GH#10521 Series should reduce MultiIndex dimensions instead of
892887
# DataFrame, IndexingError is not raised when slice(None,None,None)
893888
# with one row.
894889
with suppress(IndexingError):
895890
return self._handle_lowerdim_multi_index_axis0(tup)
891+
elif isinstance(self.obj, ABCSeries) and any(
892+
isinstance(k, tuple) for k in tup
893+
):
894+
# GH#35349 Raise if tuple in tuple for series
895+
# Do this after the all-hashable-or-null-slice check so that
896+
# we are only getting non-hashable tuples, in particular ones
897+
# that themselves contain a slice entry
898+
# See test_loc_series_getitem_too_many_dimensions
899+
raise ValueError("Too many indices")
896900

897901
# this is a series with a multi-index specified a tuple of
898902
# selectors
@@ -1127,6 +1131,9 @@ def _handle_lowerdim_multi_index_axis0(self, tup: tuple):
11271131
return self._get_label(tup, axis=axis)
11281132
except TypeError as err:
11291133
# slices are unhashable
1134+
# FIXME: this raises when we have a DatetimeIndex first level and a
1135+
# string for the first tup entry
1136+
# see test_partial_slicing_with_multiindex
11301137
raise IndexingError("No label returned") from err
11311138

11321139
except KeyError as ek:

pandas/tests/frame/indexing/test_xs.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,13 @@ def test_xs_IndexSlice_argument_not_implemented(self, klass):
318318
if klass is Series:
319319
obj = obj[0]
320320

321-
msg = (
322-
"Expected label or tuple of labels, got "
323-
r"\(\('foo', 'qux', 0\), slice\(None, None, None\)\)"
324-
)
325-
with pytest.raises(TypeError, match=msg):
326-
obj.xs(IndexSlice[("foo", "qux", 0), :])
321+
expected = obj.iloc[-2:].droplevel(0)
322+
323+
result = obj.xs(IndexSlice[("foo", "qux", 0), :])
324+
tm.assert_equal(result, expected)
325+
326+
result = obj.loc[IndexSlice[("foo", "qux", 0), :]]
327+
tm.assert_equal(result, expected)
327328

328329
@pytest.mark.parametrize("klass", [DataFrame, Series])
329330
def test_xs_levels_raises(self, klass):

pandas/tests/indexing/test_loc.py

+12
Original file line numberDiff line numberDiff line change
@@ -1663,6 +1663,18 @@ def test_loc_multiindex_levels_contain_values_not_in_index_anymore(self, lt_valu
16631663
with pytest.raises(KeyError, match=r"\['b'\] not in index"):
16641664
df.loc[df["a"] < lt_value, :].loc[["b"], :]
16651665

1666+
def test_loc_drops_level(self):
1667+
# Based on test_series_varied_multiindex_alignment, where
1668+
# this used to fail to drop the first level
1669+
mi = MultiIndex.from_product(
1670+
[list("ab"), list("xy"), [1, 2]], names=["ab", "xy", "num"]
1671+
)
1672+
ser = Series(range(8), index=mi)
1673+
1674+
loc_result = ser.loc["a", :, :]
1675+
expected = ser.index.droplevel(0)[:4]
1676+
tm.assert_index_equal(loc_result.index, expected)
1677+
16661678

16671679
class TestLocSetitemWithExpansion:
16681680
@pytest.mark.slow

pandas/tests/series/test_arithmetic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ def test_series_varied_multiindex_alignment():
924924
[1000 * i for i in range(1, 5)],
925925
index=pd.MultiIndex.from_product([list("xy"), [1, 2]], names=["xy", "num"]),
926926
)
927-
result = s1.loc[pd.IndexSlice["a", :, :]] + s2
927+
result = s1.loc[pd.IndexSlice[["a"], :, :]] + s2
928928
expected = Series(
929929
[1000, 2001, 3002, 4003],
930930
index=pd.MultiIndex.from_tuples(

0 commit comments

Comments
 (0)