From 1b8bb72dfa4d06d2df84a1027cbfa7b432f56433 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Fri, 4 Jun 2021 02:20:05 +0200 Subject: [PATCH] Backport PR #41789: Bug in xs raising KeyError for MultiIndex columns with droplevel False and list indexe --- doc/source/whatsnew/v1.2.5.rst | 7 +++++++ pandas/core/generic.py | 9 +++++++++ pandas/tests/frame/indexing/test_xs.py | 17 +++++++++++++++-- .../tests/indexing/multiindex/test_partial.py | 3 ++- pandas/tests/series/indexing/test_xs.py | 10 ++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.2.5.rst b/doc/source/whatsnew/v1.2.5.rst index 500030e1304c6..42c71acdee7e0 100644 --- a/doc/source/whatsnew/v1.2.5.rst +++ b/doc/source/whatsnew/v1.2.5.rst @@ -21,6 +21,13 @@ Fixed regressions .. --------------------------------------------------------------------------- +.. _whatsnew_125.deprecations: + +Deprecations +~~~~~~~~~~~~ + +- Deprecated passing lists as ``key`` to :meth:`DataFrame.xs` and :meth:`Series.xs` (:issue:`41760`) + .. _whatsnew_125.bug_fixes: Bug fixes diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 4350cd60c99d7..b37c97012b74d 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3705,6 +3705,15 @@ class animal locomotion """ axis = self._get_axis_number(axis) labels = self._get_axis(axis) + + if isinstance(key, list): + warnings.warn( + "Passing lists as key for xs is deprecated and will be removed in a " + "future version. Pass key as a tuple instead.", + FutureWarning, + stacklevel=2, + ) + if level is not None: if not isinstance(labels, MultiIndex): raise TypeError("Index must be a MultiIndex") diff --git a/pandas/tests/frame/indexing/test_xs.py b/pandas/tests/frame/indexing/test_xs.py index 3be3ce15622b4..ad85be3f9f8be 100644 --- a/pandas/tests/frame/indexing/test_xs.py +++ b/pandas/tests/frame/indexing/test_xs.py @@ -99,7 +99,8 @@ def test_xs_keep_level(self): expected = df[:1] tm.assert_frame_equal(result, expected) - result = df.xs([2008, "sat"], level=["year", "day"], drop_level=False) + with tm.assert_produces_warning(FutureWarning): + result = df.xs([2008, "sat"], level=["year", "day"], drop_level=False) tm.assert_frame_equal(result, expected) def test_xs_view(self): @@ -172,7 +173,11 @@ def test_xs_with_duplicates(self, key, level, multiindex_dataframe_random_data): assert df.index.is_unique is False expected = concat([frame.xs("one", level="second")] * 2) - result = df.xs(key, level=level) + if isinstance(key, list): + with tm.assert_produces_warning(FutureWarning): + result = df.xs(key, level=level) + else: + result = df.xs(key, level=level) tm.assert_frame_equal(result, expected) def test_xs_missing_values_in_index(self): @@ -327,3 +332,11 @@ def test_xs_droplevel_false_view(self): df.values[0, 0] = 2 expected = DataFrame({"a": [2]}) tm.assert_frame_equal(result, expected) + + def test_xs_list_indexer_droplevel_false(self): + # GH#41760 + mi = MultiIndex.from_tuples([("x", "m", "a"), ("x", "n", "b"), ("y", "o", "c")]) + df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=mi) + with tm.assert_produces_warning(FutureWarning): + with pytest.raises(KeyError, match="y"): + df.xs(["x", "y"], drop_level=False, axis=1) diff --git a/pandas/tests/indexing/multiindex/test_partial.py b/pandas/tests/indexing/multiindex/test_partial.py index 9c356b81b85db..a3341faaf397d 100644 --- a/pandas/tests/indexing/multiindex/test_partial.py +++ b/pandas/tests/indexing/multiindex/test_partial.py @@ -67,7 +67,8 @@ def test_xs_partial( ) df = DataFrame(np.random.randn(8, 4), index=index, columns=list("abcd")) - result = df.xs(["foo", "one"]) + with tm.assert_produces_warning(FutureWarning): + result = df.xs(["foo", "one"]) expected = df.loc["foo", "one"] tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/series/indexing/test_xs.py b/pandas/tests/series/indexing/test_xs.py index 83cc6d4670423..fc84ce88b36c7 100644 --- a/pandas/tests/series/indexing/test_xs.py +++ b/pandas/tests/series/indexing/test_xs.py @@ -65,3 +65,13 @@ def test_series_xs_droplevel_false(self): ), ) tm.assert_series_equal(result, expected) + + def test_xs_key_as_list(self): + # GH#41760 + mi = MultiIndex.from_tuples([("a", "x")], names=["level1", "level2"]) + ser = Series([1], index=mi) + with tm.assert_produces_warning(FutureWarning): + ser.xs(["a", "x"], axis=0, drop_level=False) + + with tm.assert_produces_warning(FutureWarning): + ser.xs(["a"], axis=0, drop_level=False)