Repository: pandas-dev/pandas
Pull request: BUG: Bug in xs ignored droplevel
- [x] closes #19056
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
DataFrame.xs("key", axis=1, drop_level=False) still dropping level
#### Code Sample, a copy-pastable example if possible

```python
df = pd.DataFrame([[1, 2, 3], [2, 4, 6]], columns=["type1_subtype1_subsubtype1", "type1_subtype1_subsubtype2", "type2_subtype1_subsubtype2"], index=["rowtype_rowsubtype1_rowlevel1", "row_rowlevel0_rowlevel1"])
df.columns = d.columns.str.split("_", expand=True).rename(["Sample_type", "Subtype", "SubSubtype"])
df.index = d.index.str.split("_", expand=True).rename(["Rowtype", "rowsubtype", "rowlevel"])
print("Incorrect behavior:")
print(df.xs("type1", axis=1, drop_level=False))
print("Expected output:")
print(df.transpose().xs("type1", drop_level=False).transpose())
```

#### Problem description
I checked if there was a previous issue about this in the issue tracker but my searches gave nothing similar. 

It appears that `drop_level=False` doesn't work as intended when taking a cross section along `axis=1`. Current workaround is to transpose the dataframe, then do the cross section, and then transpose it back again (as in my example above). 

#### Expected Output
```
Sample_type                         type1            
Subtype                          subtype1            
SubSubtype                    subsubtype1 subsubtype2
Rowtype rowsubtype  rowlevel                         
rowtype rowsubtype1 rowlevel1           1           2
row     rowlevel0   rowlevel1           2           4
```

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.1.final.0
python-bits: 64
OS: Linux
OS-release: 3.10.0-693.11.1.el7.x86_64
machine: x86_64
processor: 
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.20.1
pytest: 3.0.7
pip: 9.0.1
setuptools: 36.5.0.post20170921
Cython: 0.25.2
numpy: 1.12.1
scipy: 0.19.0
xarray: None
IPython: 5.3.0
sphinx: 1.5.6
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.3.0
numexpr: 2.6.2
feather: 0.3.1
matplotlib: 2.0.2
openpyxl: 2.4.7
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
lxml: 3.7.3
bs4: 4.6.0
html5lib: 0.999
sqlalchemy: 1.1.9
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
pandas_gbq: None
pandas_datareader: None
None
</details>

Hints from issue discussion:
I can confirm that for Pandas version 0.22 (Python 2.7 64-bit).
#6507 maybe related.

`drop_level=False` also ignored on `index` when fully specified tuple..

```python
import pandas as pd
df = pd.DataFrame([[1, 2, 3], [2, 4, 6]], columns=["type1_subtype1_subsubtype1", "type1_subtype1_subsubtype2", "type2_subtype1_subsubtype2"], index=["rowtype_rowsubtype1_rowlevel1", "row_rowlevel0_rowlevel1"])
df.columns = df.columns.str.split("_", expand=True).rename(["Sample_type", "Subtype", "SubSubtype"])
df.index = df.index.str.split("_", expand=True).rename(["Rowtype", "rowsubtype", "rowlevel"])
df.xs(('row','rowlevel0','rowlevel1'), drop_level=False)
```

```
Sample_type  Subtype   SubSubtype 
type1        subtype1  subsubtype1    2
                       subsubtype2    4
type2        subtype1  subsubtype2    6
Name: (row, rowlevel0, rowlevel1), dtype: int64
```
I am having the same issue for Pandas version 0.23.0 (Python v 3.6.5 64-bit)

Commit message: Move whatsnew

Patch to review:
diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst
index f751a91cecf19..b7378524c77bc 100644
--- a/doc/source/whatsnew/v1.2.0.rst
+++ b/doc/source/whatsnew/v1.2.0.rst
@@ -470,6 +470,7 @@ Indexing
 - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`)
 - 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 :meth:`DataFrame.xs` ignored ``droplevel=False`` for columnsm (:issue:`19056`)
 
 Missing
 ^^^^^^^
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index 24c1ae971686e..d41fc12ceb62e 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -3708,7 +3708,9 @@ class   animal   locomotion
             return result
 
         if axis == 1:
-            return self[key]
+            if drop_level:
+                return self[key]
+            return self[[key]]
 
         index = self.index
         if isinstance(index, MultiIndex):
diff --git a/pandas/tests/frame/indexing/test_xs.py b/pandas/tests/frame/indexing/test_xs.py
index 11e076f313540..410b183d80062 100644
--- a/pandas/tests/frame/indexing/test_xs.py
+++ b/pandas/tests/frame/indexing/test_xs.py
@@ -297,3 +297,18 @@ 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_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)


Code review comments:
diff_hunk: @@ -470,6 +470,7 @@ Indexing
 - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`)
 - 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 :meth:`DataFrame.xs` ignored ``droplevel=False`` for columnsm (:issue:`19056`)
path: doc/source/whatsnew/v1.2.0.rst
text: @user1:
typo columns

@author:
Thx, fixed

diff_hunk: @@ -3708,7 +3708,9 @@ class   animal   locomotion
             return result
 
         if axis == 1:
-            return self[key]
+            if drop_level:
+                return self[key]
+            return self[[key]]
path: pandas/core/generic.py
text: @user1:
PITA but this causes us to make a copy instead of a view.  can we re-use any of the code in the rest of the method to keep this consistent?

@author:
Would make this pretty complex probably, but will have a look tomorrow

@author:
This should work, but got a bit more complex. Using iloc now to select the data if ``droplevel=False``