Skip to content

Commit

Permalink
Fix Inconsistent MultiIndex Sorting (#21043)
Browse files Browse the repository at this point in the history
  • Loading branch information
WillAyd authored and jreback committed May 19, 2018
1 parent af2b609 commit bc37ea2
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 17 deletions.
8 changes: 4 additions & 4 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -4454,17 +4454,17 @@ 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)

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)
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -2617,7 +2617,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):
Expand Down
17 changes: 17 additions & 0 deletions pandas/tests/frame/test_reshape.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 26 additions & 8 deletions pandas/tests/frame/test_sorting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
9 changes: 5 additions & 4 deletions pandas/tests/series/test_sorting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit bc37ea2

Please sign in to comment.