Repository: pandas-dev/pandas

Issue:
sort_index with sort_remaining=False does not work with int level
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
mi = pd.MultiIndex.from_tuples([[2,2],[2,1],[1,2],[1,1]], names=list('AB'))
a = pd.Series([1,2,3,4], index=mi)
a.sort_index(level=0, sort_remaining=False)
```
returns:
```
A  B
1  1    4
   2    3
2  1    2
   2    1
dtype: int64
```

#### Problem description

Besides A (level 0), also B (level 1) is sorted. This, despite the fact that sort_remaining is set to False.

#### Expected Output

By comparison, the correct result is obtained when specifying the level by its name:

```python
a.sort_index(level='A', sort_remaining=False)
```
with
```
A  B
1  2    3
   1    4
2  2    1
   1    2
dtype: int64
```
Exactly the same result should be expected when using `level=0` instead of `level='A'`.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 2.7.13.final.0
python-bits: 32
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None
pandas: 0.22.0
pytest: None
pip: 9.0.3
setuptools: 36.4.0
Cython: None
numpy: 1.14.1
scipy: 1.0.0
pyarrow: None
xarray: 0.10.0
IPython: 5.3.0
sphinx: 1.6.3
patsy: 0.5.0
dateutil: 2.6.1
pytz: 2018.3
blosc: None
bottleneck: 1.2.1
tables: None
numexpr: None
feather: None
matplotlib: 2.1.2
openpyxl: 2.5.0
xlrd: 1.0.0
xlwt: 1.3.0
xlsxwriter: 1.0.2
lxml: 4.1.1
bs4: 4.6.0
html5lib: 0.999999999
sqlalchemy: 1.2.3
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:

Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -4454,7 +4454,10 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
         axis = self._get_axis_number(axis)
         labels = self._get_axis(axis)
 
-        if level:
+        # make sure that the axis is lexsorted to start
+        # if not we need to reconstruct to get the correct indexer
+        labels = labels._sort_levels_monotonic()
+        if level is not None:
 
             new_axis, indexer = labels.sortlevel(level, ascending=ascending,
                                                  sort_remaining=sort_remaining)
@@ -4462,9 +4465,6 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
         elif isinstance(labels, MultiIndex):
             from pandas.core.sorting import lexsort_indexer
 
-            # make sure that the axis is lexsorted to start
-            # if not we need to reconstruct to get the correct indexer
-            labels = labels._sort_levels_monotonic()
             indexer = lexsort_indexer(labels._get_labels_for_sorting(),
                                       orders=ascending,
                                       na_position=na_position)
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -2616,7 +2616,7 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
         axis = self._get_axis_number(axis)
         index = self.index
 
-        if level:
+        if level is not None:
             new_index, indexer = index.sortlevel(level, ascending=ascending,
                                                  sort_remaining=sort_remaining)
         elif isinstance(index, MultiIndex):


Test patch:
diff --git a/pandas/tests/frame/test_reshape.py b/pandas/tests/frame/test_reshape.py
--- a/pandas/tests/frame/test_reshape.py
+++ b/pandas/tests/frame/test_reshape.py
@@ -861,6 +861,23 @@ def test_stack_preserve_categorical_dtype(self):
 
                 tm.assert_series_equal(result, expected)
 
+    @pytest.mark.parametrize("level", [0, 'baz'])
+    def test_unstack_swaplevel_sortlevel(self, level):
+        # GH 20994
+        mi = pd.MultiIndex.from_product([[0], ['d', 'c']],
+                                        names=['bar', 'baz'])
+        df = pd.DataFrame([[0, 2], [1, 3]], index=mi, columns=['B', 'A'])
+        df.columns.name = 'foo'
+
+        expected = pd.DataFrame([
+            [3, 1, 2, 0]], columns=pd.MultiIndex.from_tuples([
+                ('c', 'A'), ('c', 'B'), ('d', 'A'), ('d', 'B')], names=[
+                    'baz', 'foo']))
+        expected.index.name = 'bar'
+
+        result = df.unstack().swaplevel(axis=1).sort_index(axis=1, level=level)
+        tm.assert_frame_equal(result, expected)
+
 
 def test_unstack_fill_frame_object():
     # GH12815 Test unstacking with object.
diff --git a/pandas/tests/frame/test_sorting.py b/pandas/tests/frame/test_sorting.py
--- a/pandas/tests/frame/test_sorting.py
+++ b/pandas/tests/frame/test_sorting.py
@@ -550,18 +550,36 @@ def test_sort_index(self):
         expected = frame.iloc[:, ::-1]
         assert_frame_equal(result, expected)
 
-    def test_sort_index_multiindex(self):
+    @pytest.mark.parametrize("level", ['A', 0])  # GH 21052
+    def test_sort_index_multiindex(self, level):
         # GH13496
 
         # sort rows by specified level of multi-index
-        mi = MultiIndex.from_tuples([[2, 1, 3], [1, 1, 1]], names=list('ABC'))
-        df = DataFrame([[1, 2], [3, 4]], mi)
+        mi = MultiIndex.from_tuples([
+            [2, 1, 3], [2, 1, 2], [1, 1, 1]], names=list('ABC'))
+        df = DataFrame([[1, 2], [3, 4], [5, 6]], index=mi)
+
+        expected_mi = MultiIndex.from_tuples([
+            [1, 1, 1],
+            [2, 1, 2],
+            [2, 1, 3]], names=list('ABC'))
+        expected = pd.DataFrame([
+            [5, 6],
+            [3, 4],
+            [1, 2]], index=expected_mi)
+        result = df.sort_index(level=level)
+        assert_frame_equal(result, expected)
 
-        # MI sort, but no level: sort_level has no effect
-        mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list('ABC'))
-        df = DataFrame([[1, 2], [3, 4]], mi)
-        result = df.sort_index(sort_remaining=False)
-        expected = df.sort_index()
+        # sort_remaining=False
+        expected_mi = MultiIndex.from_tuples([
+            [1, 1, 1],
+            [2, 1, 3],
+            [2, 1, 2]], names=list('ABC'))
+        expected = pd.DataFrame([
+            [5, 6],
+            [1, 2],
+            [3, 4]], index=expected_mi)
+        result = df.sort_index(level=level, sort_remaining=False)
         assert_frame_equal(result, expected)
 
     def test_sort_index_intervalindex(self):
diff --git a/pandas/tests/series/test_sorting.py b/pandas/tests/series/test_sorting.py
--- a/pandas/tests/series/test_sorting.py
+++ b/pandas/tests/series/test_sorting.py
@@ -141,19 +141,20 @@ def test_sort_index_inplace(self):
         assert result is None
         tm.assert_series_equal(random_order, self.ts)
 
-    def test_sort_index_multiindex(self):
+    @pytest.mark.parametrize("level", ['A', 0])  # GH 21052
+    def test_sort_index_multiindex(self, level):
 
         mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list('ABC'))
         s = Series([1, 2], mi)
         backwards = s.iloc[[1, 0]]
 
         # implicit sort_remaining=True
-        res = s.sort_index(level='A')
+        res = s.sort_index(level=level)
         assert_series_equal(backwards, res)
 
         # GH13496
-        # rows share same level='A': sort has no effect without remaining lvls
-        res = s.sort_index(level='A', sort_remaining=False)
+        # sort has no effect without remaining lvls
+        res = s.sort_index(level=level, sort_remaining=False)
         assert_series_equal(s, res)
 
     def test_sort_index_kind(self):