From cb9cbba17bd3f3862d55a30ba34b41cf615ce419 Mon Sep 17 00:00:00 2001 From: Alex Lubbock Date: Sat, 15 Jul 2017 00:36:32 -0500 Subject: [PATCH 1/2] BUG: MultiIndex sort with ascending as list MultiIndex sorting with `sort_index` would fail when the `ascending` argument was specified as a list but not all levels of the index were specified in the `level` argument, or the levels were specified in a different order to the MultiIndex. This PR rectifies the issue and introduces a unit test based on #16934 Fixes: #16934 --- doc/source/whatsnew/v0.21.0.txt | 1 + pandas/core/indexes/multi.py | 3 ++- pandas/tests/test_multilevel.py | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index bd19d71182762..0b2f86f7b6322 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -156,6 +156,7 @@ Indexing - When called on an unsorted ``MultiIndex``, the ``loc`` indexer now will raise ``UnsortedIndexError`` only if proper slicing is used on non-sorted levels (:issue:`16734`). - Fixes regression in 0.20.3 when indexing with a string on a ``TimedeltaIndex`` (:issue:`16896`). - Fixed ``TimedeltaIndex.get_loc`` handling of ``np.timedelta64`` inputs (:issue:`16909`). +- Fix MultiIndex ``sort_index`` ordering when ``ascending`` argument is a list but not all levels are specified, or are in a different order (:issue:`16934`). I/O ^^^ diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 81eac0ac0684f..ed7ca079a07b5 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -1697,7 +1697,8 @@ def sortlevel(self, level=0, ascending=True, sort_remaining=True): raise ValueError("level must have same length as ascending") from pandas.core.sorting import lexsort_indexer - indexer = lexsort_indexer(self.labels, orders=ascending) + indexer = lexsort_indexer([self.labels[lev] for lev in level], + orders=ascending) # level ordering else: diff --git a/pandas/tests/test_multilevel.py b/pandas/tests/test_multilevel.py index c8c210c42eac2..1b575d62c2193 100644 --- a/pandas/tests/test_multilevel.py +++ b/pandas/tests/test_multilevel.py @@ -2781,3 +2781,20 @@ def test_sort_index_nan(self): result = s.sort_index(na_position='first') expected = s.iloc[[1, 2, 3, 0]] tm.assert_series_equal(result, expected) + + def test_sort_ascending_list(self): + # GH: 16934 + + # Set up a Series with a three level MultiIndex + arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], + ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], + [4, 3, 2, 1, 4, 3, 2, 1]] + tuples = list(zip(*arrays)) + index = pd.MultiIndex.from_tuples(tuples, + names=['first', 'second', 'third']) + s = pd.Series(range(8), index=index) + + result = s.sort_index(level=['third', 'first'], + ascending=[False, True]) + + assert np.array_equal(result, [0, 4, 1, 5, 2, 6, 3, 7]) From 79359fd11235199d54f75785ddcc363cdfeb4351 Mon Sep 17 00:00:00 2001 From: Alex Lubbock Date: Sat, 15 Jul 2017 10:00:34 -0500 Subject: [PATCH 2/2] Add extra test and tweak whatsnew message --- doc/source/whatsnew/v0.21.0.txt | 2 +- pandas/tests/test_multilevel.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index 0b2f86f7b6322..6ddf6029b99bb 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -156,7 +156,7 @@ Indexing - When called on an unsorted ``MultiIndex``, the ``loc`` indexer now will raise ``UnsortedIndexError`` only if proper slicing is used on non-sorted levels (:issue:`16734`). - Fixes regression in 0.20.3 when indexing with a string on a ``TimedeltaIndex`` (:issue:`16896`). - Fixed ``TimedeltaIndex.get_loc`` handling of ``np.timedelta64`` inputs (:issue:`16909`). -- Fix MultiIndex ``sort_index`` ordering when ``ascending`` argument is a list but not all levels are specified, or are in a different order (:issue:`16934`). +- Fix :meth:`MultiIndex.sort_index` ordering when ``ascending`` argument is a list, but not all levels are specified, or are in a different order (:issue:`16934`). I/O ^^^ diff --git a/pandas/tests/test_multilevel.py b/pandas/tests/test_multilevel.py index 1b575d62c2193..a56ff0fc2d158 100644 --- a/pandas/tests/test_multilevel.py +++ b/pandas/tests/test_multilevel.py @@ -2794,7 +2794,13 @@ def test_sort_ascending_list(self): names=['first', 'second', 'third']) s = pd.Series(range(8), index=index) + # Sort with boolean ascending + result = s.sort_index(level=['third', 'first'], ascending=False) + expected = s.iloc[[4, 0, 5, 1, 6, 2, 7, 3]] + tm.assert_series_equal(result, expected) + + # Sort with list of boolean ascending result = s.sort_index(level=['third', 'first'], ascending=[False, True]) - - assert np.array_equal(result, [0, 4, 1, 5, 2, 6, 3, 7]) + expected = s.iloc[[0, 4, 1, 5, 2, 6, 3, 7]] + tm.assert_series_equal(result, expected)