Мультииндексная сортировка в Pandas

У меня есть мультииндексный DataFrame, созданный с помощью операции groupby. Я пытаюсь выполнить составную сортировку, используя несколько уровней индекса, но не могу найти функцию сортировки, которая делает то, что мне нужно.

Исходный набор данных выглядит примерно так (ежедневные продажи различных товаров):

         Date Manufacturer Product Name Product Launch Date  Sales
0  2013-01-01        Apple         iPod          2001-10-23     12
1  2013-01-01        Apple         iPad          2010-04-03     13
2  2013-01-01      Samsung       Galaxy          2009-04-27     14
3  2013-01-01      Samsung   Galaxy Tab          2010-09-02     15
4  2013-01-02        Apple         iPod          2001-10-23     22
5  2013-01-02        Apple         iPad          2010-04-03     17
6  2013-01-02      Samsung       Galaxy          2009-04-27     10
7  2013-01-02      Samsung   Galaxy Tab          2010-09-02      7

Я использую groupby для получения суммы по диапазону дат:

> grouped = df.groupby(['Manufacturer', 'Product Name', 'Product Launch Date']).sum()
                                               Sales
Manufacturer Product Name Product Launch Date       
Apple        iPad         2010-04-03              30
             iPod         2001-10-23              34
Samsung      Galaxy       2009-04-27              24
             Galaxy Tab   2010-09-02              22

Все идет нормально!

Теперь последнее, что я хочу сделать, это отсортировать продукты каждого производителя по дате запуска, но сохранить их иерархически сгруппированными по производителю - вот все, что я пытаюсь сделать:

                                               Sales
Manufacturer Product Name Product Launch Date       
Apple        iPod         2001-10-23              34
             iPad         2010-04-03              30
Samsung      Galaxy       2009-04-27              24
             Galaxy Tab   2010-09-02              22

Когда я пробую sortlevel(), я теряю красивую иерархию для каждой компании, которая у меня была раньше:

> grouped.sortlevel('Product Launch Date')
                                               Sales
Manufacturer Product Name Product Launch Date       
Apple        iPod         2001-10-23              34
Samsung      Galaxy       2009-04-27              24
Apple        iPad         2010-04-03              30
Samsung      Galaxy Tab   2010-09-02              22

sort() и sort_index() просто терпят неудачу:

grouped.sort(['Manufacturer','Product Launch Date'])
KeyError: u'no item named Manufacturer'

grouped.sort_index(by=['Manufacturer','Product Launch Date'])
KeyError: u'no item named Manufacturer'

Вроде простая операция, но не могу разобраться.

Я не привязан к использованию MultiIndex для этого, но поскольку это то, что возвращает groupby(), это то, с чем я работал.

Кстати, код для создания начального DataFrame:

data = {
  'Date': ['2013-01-01', '2013-01-01', '2013-01-01', '2013-01-01', '2013-01-02', '2013-01-02', '2013-01-02', '2013-01-02'],
  'Manufacturer' : ['Apple', 'Apple', 'Samsung', 'Samsung', 'Apple', 'Apple', 'Samsung', 'Samsung',],
  'Product Name' : ['iPod', 'iPad', 'Galaxy', 'Galaxy Tab', 'iPod', 'iPad', 'Galaxy', 'Galaxy Tab'], 
  'Product Launch Date' : ['2001-10-23', '2010-04-03', '2009-04-27', '2010-09-02','2001-10-23', '2010-04-03', '2009-04-27', '2010-09-02'],
  'Sales' : [12, 13, 14, 15, 22, 17, 10, 7]
}
df = DataFrame(data, columns=['Date', 'Manufacturer', 'Product Name', 'Product Launch Date', 'Sales'])

person Keeth    schedule 21.06.2013    source источник
comment
Данные будут лексикографически отсортированы по выбранному уровню, за которым следуют другие уровни (по порядку) (это отстой...)   -  person Andy Hayden    schedule 21.06.2013


Ответы (5)


Хаком было бы изменить порядок уровней:

In [11]: g
Out[11]:
                                               Sales
Manufacturer Product Name Product Launch Date
Apple        iPad         2010-04-03              30
             iPod         2001-10-23              34
Samsung      Galaxy       2009-04-27              24
             Galaxy Tab   2010-09-02              22

In [12]: g.index = g.index.swaplevel(1, 2)

Уровень сортировки, который (как вы обнаружили) сортирует уровни MultiIndex по порядку:

In [13]: g = g.sortlevel()

И поменять местами:

In [14]: g.index = g.index.swaplevel(1, 2)

In [15]: g
Out[15]:
                                               Sales
Manufacturer Product Name Product Launch Date
Apple        iPod         2001-10-23              34
             iPad         2010-04-03              30
Samsung      Galaxy       2009-04-27              24
             Galaxy Tab   2010-09-02              22

Я придерживаюсь мнения, что sortlevel не должен сортировать оставшиеся метки по порядку, поэтому это создаст проблему с github. :) Хотя стоит упомянуть документацию о "необходимость сортировки".

Примечание: вы можете избежать первого swaplevel, изменив порядок начальной группы:

g = df.groupby(['Manufacturer', 'Product Launch Date', 'Product Name']).sum()
person Andy Hayden    schedule 21.06.2013
comment
Это примечание к документу предполагает, что уровни нужно сортировать, хотя видимо это просто деталь реализации. Неясно, означает ли это, что они должны быть отсортированы иерархически от самого высокого до самого низкого уровня индекса. - person BrenBarn; 22.06.2013
comment
@BrenBarn Это хороший момент, я слышал, как Джефф говорил об этом раньше ... :) - person Andy Hayden; 22.06.2013
comment
Между прочим, не можете ли вы устранить дополнительную замену/сортировку в своем решении, выполнив начальную группировку в порядке замены (затем просто уровень подкачки после группы)? - person BrenBarn; 22.06.2013
comment
@BrenBarn Спасибо (упомяну об этом)! :) - person Andy Hayden; 22.06.2013

Этот лайнер работает для меня:

In [1]: grouped.sortlevel(["Manufacturer","Product Launch Date"], sort_remaining=False)

                                               Sales
Manufacturer Product Name Product Launch Date       
Apple        iPod         2001-10-23              34
             iPad         2010-04-03              30
Samsung      Galaxy       2009-04-27              24
             Galaxy Tab   2010-09-02              22

Обратите внимание, что это тоже работает:

groups.sortlevel([0,2], sort_remaining=False)

Это не сработало бы, когда вы впервые опубликовали более двух лет назад, потому что уровень сортировки по умолчанию сортируется по ВСЕМ индексам, что искажает иерархию вашей компании. В прошлом году был добавлен sort_remaining, отключающий это поведение. Вот ссылка на коммит для справки: https://github.com/pydata/pandas/commit/3ad64b11e8e4bef47e3767f1d31cc26e39593277

person Jim    schedule 09.10.2015
comment
Спасибо за публикацию обновленного ответа. У меня был трехуровневый мультииндекс, и я хотел сортировать только по первым двум. Это сработало отлично. - person Arjun Kumar; 01.12.2015

Чтобы отсортировать MultiIndex по столбцам индекса (или уровням), вам нужно использовать .sort_index() и установите его аргумент level. Если вы хотите отсортировать по нескольким уровням, аргумент должен быть установлен в список имен уровней в последовательном порядке.

Это должно дать вам необходимый DataFrame:

df.groupby(['Manufacturer',
            'Product Name', 
            'Launch Date']
          ).sum().sort_index(level=['Manufacturer','Launch Date'])
person fpersyn    schedule 31.05.2019
comment
Вы также можете установить аргумент ascending со списком логических значений, чтобы управлять направлениями для каждого уровня отдельно. например .sort_index(level=['Manufacturer','LaunchDate'], ascending=[True,False]). - person fpersyn; 31.05.2019

Если вы хотите попытаться избежать множественных свопов в очень глубоком MultiIndex, вы также можете попробовать это:

  1. Нарезка по уровню X (по списку + .loc + IndexSlice)
  2. Сортировка нужного уровня (sortlevel(2))
  3. Объединить каждую группу индексов уровня X

Здесь у вас есть код:

import pandas as pd
idx = pd.IndexSlice
g = pd.concat([grouped.loc[idx[i,:,:],:].sortlevel(2) for i in grouped.index.levels[0]])
g
person Xavi    schedule 23.01.2015

Если вы не беспокоитесь о сохранении индекса (я часто предпочитаю произвольный целочисленный индекс), вы можете просто использовать следующий однострочный код:

grouped.reset_index().sort(["Manufacturer","Product Launch Date"])
person David Hollett    schedule 05.05.2015