diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 22a0fb7a45318..f31c4d38b2bd6 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -475,6 +475,7 @@ Indexing - Bug in :meth:`Series.loc` and :meth:`DataFrame.loc` raises when numeric label was given for object :class:`Index` although label was in :class:`Index` (:issue:`26491`) - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :class:`MultiIndex` (:issue:`27104`) - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`) +- Bug in :meth:`DataFrame.xs` ignored ``droplevel=False`` for columns (:issue:`19056`) Missing ^^^^^^^ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 24c1ae971686e..72978dd842918 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3708,18 +3708,21 @@ class animal locomotion return result if axis == 1: - return self[key] + if drop_level: + return self[key] + index = self.columns + else: + index = self.index - index = self.index if isinstance(index, MultiIndex): try: - loc, new_index = self.index._get_loc_level( + loc, new_index = index._get_loc_level( key, level=0, drop_level=drop_level ) except TypeError as e: raise TypeError(f"Expected label or tuple of labels, got {key}") from e else: - loc = self.index.get_loc(key) + loc = index.get_loc(key) if isinstance(loc, np.ndarray): if loc.dtype == np.bool_: @@ -3729,9 +3732,9 @@ class animal locomotion return self._take_with_is_copy(loc, axis=axis) if not is_scalar(loc): - new_index = self.index[loc] + new_index = index[loc] - if is_scalar(loc): + if is_scalar(loc) and axis == 0: # In this case loc should be an integer if self.ndim == 1: # if we encounter an array-like and we only have 1 dim @@ -3747,7 +3750,10 @@ class animal locomotion name=self.index[loc], dtype=new_values.dtype, ) - + elif is_scalar(loc): + result = self.iloc[:, [loc]] + elif axis == 1: + result = self.iloc[:, loc] else: result = self.iloc[loc] result.index = new_index diff --git a/pandas/tests/frame/indexing/test_xs.py b/pandas/tests/frame/indexing/test_xs.py index 11e076f313540..a90141e9fad60 100644 --- a/pandas/tests/frame/indexing/test_xs.py +++ b/pandas/tests/frame/indexing/test_xs.py @@ -297,3 +297,25 @@ def test_xs_levels_raises(self, klass): msg = "Index must be a MultiIndex" with pytest.raises(TypeError, match=msg): obj.xs(0, level="as") + + def test_xs_multiindex_droplevel_false(self): + # GH#19056 + mi = MultiIndex.from_tuples( + [("a", "x"), ("a", "y"), ("b", "x")], names=["level1", "level2"] + ) + df = DataFrame([[1, 2, 3]], columns=mi) + result = df.xs("a", axis=1, drop_level=False) + expected = DataFrame( + [[1, 2]], + columns=MultiIndex.from_tuples( + [("a", "x"), ("a", "y")], names=["level1", "level2"] + ), + ) + tm.assert_frame_equal(result, expected) + + def test_xs_droplevel_false(self): + # GH#19056 + df = DataFrame([[1, 2, 3]], columns=Index(["a", "b", "c"])) + result = df.xs("a", axis=1, drop_level=False) + expected = DataFrame({"a": [1]}) + 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 1a23b09bde816..ca7ed50ab8875 100644 --- a/pandas/tests/series/indexing/test_xs.py +++ b/pandas/tests/series/indexing/test_xs.py @@ -50,3 +50,18 @@ def test_series_getitem_multiindex_xs(xs): result = ser.xs("20130903", level=1) tm.assert_series_equal(result, expected) + + def test_series_xs_droplevel_false(self): + # GH: 19056 + mi = MultiIndex.from_tuples( + [("a", "x"), ("a", "y"), ("b", "x")], names=["level1", "level2"] + ) + df = Series([1, 1, 1], index=mi) + result = df.xs("a", axis=0, drop_level=False) + expected = Series( + [1, 1], + index=MultiIndex.from_tuples( + [("a", "x"), ("a", "y")], names=["level1", "level2"] + ), + ) + tm.assert_series_equal(result, expected)