Skip to content

Commit 40d19a0

Browse files
committed
BUG: raise when wrong level name is passed to "unstack"
closes #18303
1 parent 61362be commit 40d19a0

File tree

8 files changed

+34
-8
lines changed

8 files changed

+34
-8
lines changed

doc/source/whatsnew/v0.25.1.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Groupby/resample/rolling
128128
Reshaping
129129
^^^^^^^^^
130130

131-
-
131+
- A ``KeyError`` is now raised if ``.unstack()`` is called on a :class:`Series` or :class:`DataFrame` with a flat :class:`Index` passing a name which is not the correct one (:issue:`18303`)
132132
-
133133
-
134134

pandas/core/indexes/base.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,11 @@ def _validate_index_level(self, level):
15241524
"Too many levels:" " Index has only 1 level, not %d" % (level + 1)
15251525
)
15261526
elif level != self.name:
1527-
raise KeyError("Level %s must be same as name (%s)" % (level, self.name))
1527+
raise KeyError(
1528+
"Requested level ({}) does not match index name ({})".format(
1529+
level, self.name
1530+
)
1531+
)
15281532

15291533
def _get_level_number(self, level):
15301534
self._validate_index_level(level)

pandas/core/reshape/reshape.py

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ensure_platform_int,
1313
is_bool_dtype,
1414
is_extension_array_dtype,
15+
is_integer,
1516
is_integer_dtype,
1617
is_list_like,
1718
is_object_dtype,
@@ -402,6 +403,10 @@ def unstack(obj, level, fill_value=None):
402403
else:
403404
level = level[0]
404405

406+
# Prioritize integer interpretation (GH #21677):
407+
if not is_integer(level) and not level == "__placeholder__":
408+
level = obj.index._get_level_number(level)
409+
405410
if isinstance(obj, DataFrame):
406411
if isinstance(obj.index, MultiIndex):
407412
return _unstack_frame(obj, level, fill_value=fill_value)

pandas/tests/frame/test_alter_axes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ def test_reset_index_level(self):
10831083

10841084
# Missing levels - for both MultiIndex and single-level Index:
10851085
for idx_lev in ["A", "B"], ["A"]:
1086-
with pytest.raises(KeyError, match="Level E "):
1086+
with pytest.raises(KeyError, match=r"(L|l)evel \(?E\)?"):
10871087
df.set_index(idx_lev).reset_index(level=["A", "E"])
10881088
with pytest.raises(IndexError, match="Too many levels"):
10891089
df.set_index(idx_lev).reset_index(level=[0, 1, 2])

pandas/tests/indexes/test_base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2004,7 +2004,7 @@ def test_isin_level_kwarg_bad_label_raises(self, label, indices):
20042004
msg = "'Level {} not found'"
20052005
else:
20062006
index = index.rename("foo")
2007-
msg = r"'Level {} must be same as name \(foo\)'"
2007+
msg = r"Requested level \({}\) does not match index name \(foo\)"
20082008
with pytest.raises(KeyError, match=msg.format(label)):
20092009
index.isin([], level=label)
20102010

pandas/tests/indexes/test_common.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def test_droplevel(self, indices):
3535

3636
for level in "wrong", ["wrong"]:
3737
with pytest.raises(
38-
KeyError, match=re.escape("'Level wrong must be same as name (None)'")
38+
KeyError,
39+
match=r"'Requested level \(wrong\) does not match index name \(None\)'",
3940
):
4041
indices.droplevel(level)
4142

@@ -200,7 +201,7 @@ def test_unique(self, indices):
200201
with pytest.raises(IndexError, match=msg):
201202
indices.unique(level=3)
202203

203-
msg = r"Level wrong must be same as name \({}\)".format(
204+
msg = r"Requested level \(wrong\) does not match index name \({}\)".format(
204205
re.escape(indices.name.__repr__())
205206
)
206207
with pytest.raises(KeyError, match=msg):

pandas/tests/series/test_alter_axes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,9 @@ def test_reset_index_drop_errors(self):
319319

320320
# KeyError raised for series index when passed level name is missing
321321
s = Series(range(4))
322-
with pytest.raises(KeyError, match="must be same as name"):
322+
with pytest.raises(KeyError, match="does not match index name"):
323323
s.reset_index("wrong", drop=True)
324-
with pytest.raises(KeyError, match="must be same as name"):
324+
with pytest.raises(KeyError, match="does not match index name"):
325325
s.reset_index("wrong")
326326

327327
# KeyError raised for series when level to be dropped is missing

pandas/tests/test_multilevel.py

+16
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,22 @@ def test_stack_unstack_preserve_names(self):
524524
restacked = unstacked.stack()
525525
assert restacked.index.names == self.frame.index.names
526526

527+
@pytest.mark.parametrize("method", ["stack", "unstack"])
528+
def test_stack_unstack_wrong_level_name(self, method):
529+
# GH 18303 - wrong level name should raise
530+
531+
# A DataFrame with flat axes:
532+
df = self.frame.loc["foo"]
533+
534+
with pytest.raises(KeyError, match="does not match index name"):
535+
getattr(df, method)("mistake")
536+
537+
if method == "unstack":
538+
# Same on a Series:
539+
s = df.iloc[:, 0]
540+
with pytest.raises(KeyError, match="does not match index name"):
541+
getattr(s, method)("mistake")
542+
527543
def test_unstack_level_name(self):
528544
result = self.frame.unstack("second")
529545
expected = self.frame.unstack(level=1)

0 commit comments

Comments
 (0)