Как вы обновляете уровни pandas MultiIndex после резки его DataFrame?

У меня есть Dataframe с pandas MultiIndex:

In [1]: import pandas as pd
In [2]: multi_index = pd.MultiIndex.from_product([['CAN','USA'],['total']],names=['country','sex'])
In [3]: df = pd.DataFrame({'pop':[35,318]},index=multi_index)
In [4]: df
Out[4]:
               pop
country sex
CAN     total   35
USA     total  318

Затем я удаляю некоторые строки из этого DataFrame:

In [5]: df = df.query('pop > 100')

In [6]: df
Out[6]:
               pop
country sex
USA     total  318

Но когда я консультирую MutliIndex, у него все еще есть обе страны на своих уровнях.

In [7]: df.index.levels[0]
Out[7]: Index([u'CAN', u'USA'], dtype='object')

Я могу исправить это сам довольно странным образом:

In [8]: idx_names = df.index.names

In [9]: df = df.reset_index(drop=False)

In [10]: df = df.set_index(idx_names)

In [11]: df
Out[11]:
               pop
country sex
USA     total  318

In [12]: df.index.levels[0]
Out[12]: Index([u'USA'], dtype='object')

Но это кажется довольно грязным. Есть ли лучший способ, которым я не хватает?

Ответ 1

Это то, что укусило меня раньше. Удаление столбцов или строк НЕ изменяет базовый MultiIndex по производительности и философским соображениям, и это официально не считается ошибкой (читайте подробнее здесь), Короткий ответ заключается в том, что разработчики говорят, что "это не то, для чего используется MultiIndex". Если вам понадобится список содержимого уровня MultiIndex после модификации, например, для итерации или для проверки того, включено ли что-либо, вы можете использовать:

df.index.get_level_values(<levelname>)

Это возвращает текущие активные значения в пределах этого уровня индекса.

Итак, я думаю, что "трюк" здесь заключается в том, что для этого API-интерфейса это использовать get_level_values, а не только .index или .columns

Ответ 2

Из версии 0.20.0 использовать MultiIndex.remove_unused_levels

print (df.index)
MultiIndex(levels=[['CAN', 'USA'], ['total']],
           labels=[[1], [0]],
           names=['country', 'sex'])

df.index = df.index.remove_unused_levels()

print (df.index)
MultiIndex(levels=[['USA'], ['total']],
           labels=[[0], [0]],
           names=['country', 'sex'])

Ответ 3

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

In [165]: df.index
Out[165]:
MultiIndex(levels=[[u'CAN', u'USA'], [u'total']],
           labels=[[0, 1], [0, 0]],
           names=[u'country', u'sex'])

In [166]: df = df.query('pop > 100')

In [167]: df.index
Out[167]:
MultiIndex(levels=[[u'CAN', u'USA'], [u'total']],
           labels=[[1], [0]],
           names=[u'country', u'sex'])

вы можете видеть, что метки - индексы в значениях уровня - обновлены, но не значения уровня. Это может быть несовершенная аналогия, но мне кажется, что значения уровня аналогичны перечисляемому столбцу в таблице базы данных, тогда как метки аналогичны фактическим значениям строк в таблице. Если вы удалите все строки таблицы со значением "CAN" , это не изменит того факта, что "CAN" по-прежнему является допустимым выбором, основанным на определении столбца. Чтобы удалить "CAN" из перечисления, вам нужно изменить определение столбца; это эквивалент переиндексации фрейма данных в pandas.