Repository: pandas-dev/pandas

Issue:
BUG: Series.xs() inconsistent with DataFrame.xs() with MultiIndex
_Edited to clarify the bug_

`Series.xs` slice fails with string index labels and MultiIndex:

``` python
In [1]: idx = pd.MultiIndex.from_tuples([('a', 'one'), ('a', 'two'), ('b', 'one'), ('b', 'two')])

In [2]: df = pd.Series(np.random.randn(4), index=idx)

In [4]: df.index.set_names(['L1', 'L2'], inplace=True)

In [5]: df
Out[5]: 
L1  L2 
a   one   -0.136418
    two   -0.346941
b   one   -1.468534
    two    1.217693
dtype: float64

In [6]: df.xs('one', level='L2')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-6-52601adf5184> in <module>()
----> 1 df.xs('one', level='L2')

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in _xs(self, key, axis, level, copy)
    437 
    438     def _xs(self, key, axis=0, level=None, copy=True):
--> 439         return self.__getitem__(key)
    440 
    441     xs = _xs

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in __getitem__(self, key)
    482     def __getitem__(self, key):
    483         try:
--> 484             return self.index.get_value(self, key)
    485         except InvalidIndexError:
    486             pass

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/index.pyc in get_value(self, series, key)
   2294                     raise InvalidIndexError(key)
   2295                 else:
-> 2296                     raise e1
   2297             except Exception:  # pragma: no cover
   2298                 raise e1

KeyError: 'one'
```

The same slice works on a DataFrame.

Previous post below:

``` python
In [12]: idx = pd.MultiIndex.from_tuples([('a', 0), ('a', 1), ('b', 0), ('b', 1)])

In [13]: df = pd.Series(np.random.randn(4), index=idx)

In [14]: df
Out[14]: 
a  0    0.876121
   1    0.638050
b  0    0.965934
   1    1.061716
dtype: float64

In [15]: df.xs(0, level=1)   # returns scaler
Out[15]: 0.87612104445620753

In [16]: df.index.names = ['L1', 'L2']

In [27]: df.xs(0, level='L2')   # returns scaler
Out[27]: -0.98585685847339011

In [28]: df.xs(0, level='L1')   # No key error
Out[28]: -0.98585685847339011
```

Works for DataFrames:

``` python
In [30]: df.xs(0, level='L2')
Out[30]: 
           0
L1          
a  -0.985857
b   0.648114

[2 rows x 1 columns]
```

`Series.xs` also seems to fail on string index labels?

``` python
In [50]: idx = pd.MultiIndex.from_tuples([('a', 'one'), ('a', 'two'), ('b', 'one'), ('b', 'two')])
In [53]: df = pd.Series(np.random.randn(4), index=idx)
In [56]: df.xs('one', level='L2')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-56-52601adf5184> in <module>()
----> 1 df.xs('one', level='L2')

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in _xs(self, key, axis, level, copy)
    437 
    438     def _xs(self, key, axis=0, level=None, copy=True):
--> 439         return self.__getitem__(key)
    440 
    441     xs = _xs

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in __getitem__(self, key)
    482     def __getitem__(self, key):
    483         try:
--> 484             return self.index.get_value(self, key)
    485         except InvalidIndexError:
    486             pass

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/index.pyc in get_value(self, series, key)
   2294                     raise InvalidIndexError(key)
   2295                 else:
-> 2296                     raise e1
   2297             except Exception:  # pragma: no cover
   2298                 raise e1

KeyError: 'one'
```

So I guess this is about 3 errors on `Series.xs` (possibly related?):
1. Returning scalers when it should return a Series when the label is an integer
2. Not raising key errors when the label is an integer
3. Failing on slices for `level>1` when the label is a string.

EDIT: Oh, and I know that `.loc` / `.ix` will work for these. I was just surprised by the results.



Discussion:
`xs` has some of the `ix` like behavior where it will interpret an integer as a positional argument if it doesn't find the label. So showing your examples:

```
In [1]: idx = pd.MultiIndex.from_tuples([('a', 0), ('a', 1), ('b', 0), ('b', 1)])

In [2]: s = pd.Series(np.random.randn(4), index=idx)

In [3]: s
Out[3]: 
a  0    0.789393
   1   -0.554426
b  0    1.986913
   1    0.501087
dtype: float64
```

This is correct (though prob not what you want)

```
In [4]: s.xs(0,level=1)
Out[4]: 0.78939286728150548
```

You should set names like this, FYI

```
In [7]: s.index.set_names(['L1','L2'],inplace=True)

In [8]: s
Out[8]: 
L1  L2
a   0     0.789393
    1    -0.554426
b   0     1.986913
    1     0.501087
dtype: float64
```

(9) is correct

```
In [9]: s.xs(0,level='L2')
Out[9]: 0.78939286728150548
```

ix like behavior

```
In [10]: s.xs(0,level='L1')
Out[10]: 0.78939286728150548
```

correct

```
In [11]: s.xs('a',level='L1')
Out[11]: 
L2
0     0.789393
1    -0.554426
dtype: float64
```

Your last example
``df.xs('one',level='L2')` doesn't have the level L2 defined?

For the last one, I missed a copy paste:

``` python
In [65]: df
Out[65]: 
L1  L2 
a   one    1.041627
    two   -0.553980
b   one   -1.491640
    two   -0.026511
dtype: float64

In [66]: df.xs('one', level='L2')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-66-52601adf5184> in <module>()
----> 1 df.xs('one', level='L2')

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in _xs(self, key, axis, level, copy)
    437 
    438     def _xs(self, key, axis=0, level=None, copy=True):
--> 439         return self.__getitem__(key)
    440 
    441     xs = _xs

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/series.pyc in __getitem__(self, key)
    482     def __getitem__(self, key):
    483         try:
--> 484             return self.index.get_value(self, key)
    485         except InvalidIndexError:
    486             pass

/Users/tom/Envs/pandas-dev/lib/python2.7/site-packages/pandas-0.13.0rc1_27_g4d5ca5c-py2.7-macosx-10.8-x86_64.egg/pandas/core/index.pyc in get_value(self, series, key)
   2294                     raise InvalidIndexError(key)
   2295                 else:
-> 2296                     raise e1
   2297             except Exception:  # pragma: no cover
   2298                 raise e1

KeyError: 'one'
```

And I guess the most surprising thing here is the inconsistency across DataFrames and Series:

``` python
In [69]: df = pd.DataFrame(df)

In [70]: df.xs('one', level='L2')
Out[70]: 
           0
L1          
a   1.041627
b  -1.491640

[2 rows x 1 columns]
```

Sorry this is such a mess of an issue.  I think the last one is the only real _bug_ then? I'll edit my post/title to reflect that, if it's actually a bug.

yep...last one is a bug then. `xs` is implemented separately for Series/DataFrame so I am not suprised (they really should be integrated....)

@TomAugspurger maybe just edit the Top section to reflect the bug


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -1262,21 +1262,17 @@ def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
             if not copy and not isinstance(loc, slice):
                 raise ValueError('Cannot retrieve view (copy=False)')
 
-            # level = 0
-            loc_is_slice = isinstance(loc, slice)
-            if not loc_is_slice:
-                indexer = [slice(None)] * self.ndim
-                indexer[axis] = loc
-                indexer = tuple(indexer)
-            else:
-                indexer = loc
+            # convert to a label indexer if needed
+            if isinstance(loc, slice):
                 lev_num = labels._get_level_number(level)
                 if labels.levels[lev_num].inferred_type == 'integer':
-                    indexer = self.index[loc]
+                    loc = labels[loc]
+
+            # create the tuple of the indexer
+            indexer = [slice(None)] * self.ndim
+            indexer[axis] = loc
+            indexer = tuple(indexer)
 
-            # select on the correct axis
-            if axis == 1 and loc_is_slice:
-                indexer = slice(None), indexer
             result = self.ix[indexer]
             setattr(result, result._get_axis_name(axis), new_ax)
             return result
diff --git a/pandas/core/index.py b/pandas/core/index.py
--- a/pandas/core/index.py
+++ b/pandas/core/index.py
@@ -3236,7 +3236,7 @@ def _get_level_indexer(self, key, level=0):
         labels = self.labels[level]
 
         if level > 0 or self.lexsort_depth == 0:
-            return labels == loc
+            return np.array(labels == loc,dtype=bool)
         else:
             # sorted, so can return slice object -> view
             i = labels.searchsorted(loc, side='left')


Test patch:
diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py
--- a/pandas/tests/test_indexing.py
+++ b/pandas/tests/test_indexing.py
@@ -951,6 +951,24 @@ def test_series_getitem_multiindex(self):
         expected = Series([2,3],index=[1,2])
         assert_series_equal(result,expected)
 
+        # GH6258
+        s = Series([1,3,4,1,3,4],
+                   index=MultiIndex.from_product([list('AB'),
+                                                  list(date_range('20130903',periods=3))]))
+        result = s.xs('20130903',level=1)
+        expected = Series([1,1],index=list('AB'))
+        assert_series_equal(result,expected)
+
+        # GH5684
+        idx = MultiIndex.from_tuples([('a', 'one'), ('a', 'two'),
+                                      ('b', 'one'), ('b', 'two')])
+        s = Series([1, 2, 3, 4], index=idx)
+        s.index.set_names(['L1', 'L2'], inplace=True)
+        result = s.xs('one', level='L2')
+        expected = Series([1, 3], index=['a', 'b'])
+        expected.index.set_names(['L1'], inplace=True)
+        assert_series_equal(result, expected)
+
     def test_ix_general(self):
 
         # ix general issues