Repository: pandas-dev/pandas
Pull request: TST: Add tests for single level indexing with loc(axis=1)
- [x] closes #13655
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`


Problem statement:
DataFrame.loc(axis=1)[..] not working for single-level indexer
When doing `.loc(axis=1)[...]`, this only works when indexing into multiple levels. Here an example with a MultiIndex column:

```
In [23]: df = pd.DataFrame(np.arange(27).reshape(3, 9), columns=pd.MultiIndex.from_product([['a1', 'a2', 'a3'], ['b1', 'b2', 'b3']]))

In [24]: df
Out[24]:
   a1          a2          a3
   b1  b2  b3  b1  b2  b3  b1  b2  b3
0   0   1   2   3   4   5   6   7   8
1   9  10  11  12  13  14  15  16  17
2  18  19  20  21  22  23  24  25  26

In [25]: df.loc(axis=1)['a1':'a2', ['b1', 'b2']]
Out[25]:
   a1      a2
   b1  b2  b1  b2
0   0   1   3   4
1   9  10  12  13
2  18  19  21  22
```

Given the above, I would expect leaving out the `['b1', 'b2']` to work as well, but in this case it looks like the `axis=1` is ignored, which is a bit surprising IMO:

```
In [26]: df.loc(axis=1)['a1':'a2']
Out[26]:
Empty DataFrame
Columns: [(a1, b1), (a1, b2), (a1, b3), (a2, b1), (a2, b2), (a2, b3), (a3, b1),
(a3, b2), (a3, b3)]
Index: []

In [27]: df.loc(axis=1)['a1']
...
KeyError: 'the label [a1] is not in the [index]'
```

Also when having a simple frame, the same observation applies:

```
In [28]: df2 = pd.DataFrame(np.arange(9).reshape(3,3), columns=['a', 'b', 'c'])

In [29]: df2
Out[29]:
   a  b  c
0  0  1  2
1  3  4  5
2  6  7  8

In [30]: df2.loc(axis=1)['a']
...
KeyError: 'the label [a] is not in the [index]'
```

Hints from issue discussion:
This looks to be fixed on master. Could use a test.

```
In [93]: df.loc(axis=1)['a1':'a2']
Out[93]:
   a1          a2
   b1  b2  b3  b1  b2  b3
0   0   1   2   3   4   5
1   9  10  11  12  13  14
2  18  19  20  21  22  23

In [94]: df.loc(axis=1)['a1':'a2']
Out[94]:
   a1          a2
   b1  b2  b3  b1  b2  b3
0   0   1   2   3   4   5
1   9  10  11  12  13  14
2  18  19  20  21  22  23

In [95]: In [27]: df.loc(axis=1)['a1']
    ...:
Out[95]:
   b1  b2  b3
0   0   1   2
1   9  10  11
2  18  19  20

In [96]: In [28]: df2 = pd.DataFrame(np.arange(9).reshape(3,3), columns=['a', 'b', 'c'])
    ...:

In [97]: In [30]: df2.loc(axis=1)['a']
    ...:
Out[97]:
0    0
1    3
2    6
Name: a, dtype: int64

In [98]: pd.__version__
Out[98]: '0.26.0.dev0+652.g30362ed82'
```
Ill have a go at this.

Commit message: remove dtype check to pass CI

Patch to review:
diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py
index f1f11285696f9..44b6db7cf9f57 100644
--- a/pandas/tests/indexing/multiindex/test_slice.py
+++ b/pandas/tests/indexing/multiindex/test_slice.py
@@ -492,6 +492,29 @@ def test_loc_axis_arguments(self):
         with pytest.raises(ValueError):
             df.loc(axis="foo")[:, :, ["C1", "C3"]]
 
+    def test_loc_axis_single_level_indexer(self):
+
+        # test single level indexing on Multindex column dataframe
+        df1 = pd.DataFrame(
+            np.arange(27).reshape(3, 9),
+            columns=pd.MultiIndex.from_product(
+                [["a1", "a2", "a3"], ["b1", "b2", "b3"]]
+            ),
+        )
+        result = df1.loc(axis=1)["a1":"a2"]
+        expected = df1.iloc[:, :-3]
+        tm.assert_frame_equal(result, expected)
+        result = df1.loc(axis=1)["a1"]
+        expected = df1.iloc[:, :3]
+        expected.columns = ["b1", "b2", "b3"]
+        tm.assert_frame_equal(result, expected)
+
+        # test single level indexing on single index column data frame
+        df2 = pd.DataFrame(np.arange(9).reshape(3, 3), columns=["a", "b", "c"])
+        result = df2.loc(axis=1)["a"]
+        expected = pd.Series([0, 3, 6], name="a")
+        tm.assert_series_equal(result, expected, check_dtype=False)
+
     def test_per_axis_per_level_setitem(self):
 
         # test index maker


Code review comments:
diff_hunk: @@ -492,6 +492,29 @@ def test_loc_axis_arguments(self):
         with pytest.raises(ValueError):
             df.loc(axis="foo")[:, :, ["C1", "C3"]]
 
+    def test_loc_axis_single_level_indexer(self):
+
+        # test single level indexing on Multindex column dataframe
+        df1 = pd.DataFrame(
+            np.arange(27).reshape(3, 9),
+            columns=pd.MultiIndex.from_product(
+                [["a1", "a2", "a3"], ["b1", "b2", "b3"]]
+            ),
+        )
+        result = df1.loc(axis=1)["a1":"a2"]
+        expected = df1.iloc[:, :-3]
+        tm.assert_frame_equal(result, expected)
+        result = df1.loc(axis=1)["a1"]
+        expected = df1.iloc[:, :3]
+        expected.columns = ["b1", "b2", "b3"]
+        tm.assert_frame_equal(result, expected)
+
+        # test single level indexing on single index column data frame
+        df2 = pd.DataFrame(np.arange(9).reshape(3, 3), columns=["a", "b", "c"])
+        result = df2.loc(axis=1)["a"]
+        expected = pd.Series([0, 3, 6], name="a")
+        tm.assert_series_equal(result, expected, check_dtype=False)
path: pandas/tests/indexing/multiindex/test_slice.py
text: @user1:
Why is `check_dtype=False` set?

@author:
@user1  Since CI was failing for some of the 32 bit OS builds because of int32 vs int64 dtype comparison - im having a similar issue in #29522. Don't know if there is any other work around.

```
================================== FAILURES ===================================
__________ TestMultiIndexSlicers.test_loc_axis_single_level_indexer ___________
[gw0] win32 -- Python 3.6.7 C:\Miniconda\envs\pandas-dev\python.exe

self = <pandas.tests.indexing.multiindex.test_slice.TestMultiIndexSlicers object at 0x00000156C517C898>

    def test_loc_axis_single_level_indexer(self):
    
        # test single level indexing on Multindex column dataframe
        df1 = pd.DataFrame(
            np.arange(27).reshape(3, 9),
            columns=pd.MultiIndex.from_product(
                [["a1", "a2", "a3"], ["b1", "b2", "b3"]]
            ),
        )
        result = df1.loc(axis=1)["a1":"a2"]
        expected = df1.iloc[:, :-3]
        tm.assert_frame_equal(result, expected)
        result = df1.loc(axis=1)["a1"]
        expected = df1.iloc[:, :3]
        expected.columns = ["b1", "b2", "b3"]
        tm.assert_frame_equal(result, expected)
    
        # test single level indexing on single index column data frame
        df2 = pd.DataFrame(np.arange(9).reshape(3, 3), columns=["a", "b", "c"])
        result = df2.loc(axis=1)["a"]
        expected = pd.Series([0, 3, 6], dtype="int64", name="a")
>       tm.assert_series_equal(result, expected)
E       AssertionError: Attributes of Series are different
E       
E       Attribute "dtype" are different
E       [left]:  int32
E       [right]: int64

pandas\tests\indexing\multiindex\test_slice.py:516: AssertionError


```

@user1:
I see you're not assigning `dtype="int64"` anymore. Could you remove `check_dtype=False` now?

diff_hunk: @@ -492,6 +492,29 @@ def test_loc_axis_arguments(self):
         with pytest.raises(ValueError):
             df.loc(axis="foo")[:, :, ["C1", "C3"]]
 
+    def test_loc_axis_single_level_indexer(self):
+
+        # test single level indexing on Multindex column dataframe
+        df1 = pd.DataFrame(
+            np.arange(27).reshape(3, 9),
+            columns=pd.MultiIndex.from_product(
+                [["a1", "a2", "a3"], ["b1", "b2", "b3"]]
+            ),
+        )
+        result = df1.loc(axis=1)["a1":"a2"]
+        expected = df1.iloc[:, :-3]
+        tm.assert_frame_equal(result, expected)
+        result = df1.loc(axis=1)["a1"]
+        expected = df1.iloc[:, :3]
+        expected.columns = ["b1", "b2", "b3"]
+        tm.assert_frame_equal(result, expected)
path: pandas/tests/indexing/multiindex/test_slice.py
text: @user1:
Could you make 3 different functions for these tests?

@author:
@user1 Is it ok if I split it into 2 functions - one for Multindex column df and one for simple df --- and for the Multiindex bit I can comment on the two different tests - i.e one is slicing on multi columns on single level and other is slicing on single col. Otherwise, having concise names for the functions is tricky.

Something like this:
```
        # GH29519
        # test with single level multiple columns slice
        df1 = pd.DataFrame(
            np.arange(27).reshape(3, 9),
            columns=pd.MultiIndex.from_product(
                [["a1", "a2", "a3"], ["b1", "b2", "b3"]]
            ),
        )
        result = df1.loc(axis=1)["a1":"a2"]
        expected = df1.iloc[:, :-3]

        tm.assert_frame_equal(result, expected)

        # GH29519
        # test with single level single column slice
        result = df1.loc(axis=1)["a1"]
        expected = df1.iloc[:, :3]
        expected.columns = ["b1", "b2", "b3"]

        tm.assert_frame_equal(result, expected)


```  

@user1:
Verbose function names are fine. Helps isolate the failing test as separate methods.

@author:
Ok just pushed with changes as you originally suggested