Выделение строк в пандах MultiIndex DataFrame

Цель и мотивация

MultiIndex API набирает популярность на протяжении многих лет, однако не все в нем полностью понимается с точки зрения структуры, работы и связанных операций.

Одна важная операция - фильтрация. Фильтрация является распространенным требованием, но варианты использования разнообразны. Соответственно, определенные методы и функции будут более применимы к некоторым случаям использования, чем другие.

Таким образом, цель этого поста - затронуть некоторые распространенные проблемы фильтрации и варианты использования, продемонстрировать различные методы для решения этих проблем и обсудить их применимость. Некоторые из вопросов высокого уровня, на которые этот пост стремится ответить:

  • Нарезка на основе одного значения / метки
  • Нарезка на основе нескольких меток одного или нескольких уровней
  • Фильтрация по логическим условиям и выражениям
  • Какие методы применимы при каких обстоятельствах

Эти проблемы были разбиты на 6 конкретных вопросов, перечисленных ниже. Для простоты пример DataFrames в приведенной ниже настройке имеет только два уровня и не имеет дублирующих индексных ключей. Большинство представленных решений проблем могут обобщаться до N уровней.

В этом посте не будет рассказано, как создавать мультииндексы, как выполнять над ними операции присваивания или какие-либо обсуждения, связанные с производительностью (это отдельные темы для другого времени).


Вопросы

Вопрос 1-6 будет задан в контексте установки ниже.

mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15

Вопрос 1: Выбор одного предмета
Как выбрать строки, имеющие "a" на уровне "one"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

Кроме того, как я могу снизить уровень "один" в выводе?

     col
two     
t      0
u      1
v      2
w      3

Вопрос 1б
Как мне нарезать все строки со значением "t" на уровне "два"?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

Вопрос 2: Выбор нескольких значений на уровне
Как выбрать строки, соответствующие элементам "b" и "d" на уровне "один"?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

Вопрос 2б
Как бы я получить все значения, соответствующие "т" и "ш" на уровне "два"?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

Вопрос 3: Нарезка одного поперечного сечения (x, y)
Как получить сечение, то есть одну строку, имеющую определенные значения для индекса из df? В частности, как я могу получить сечение ('c', 'u'), данный

         col
one two     
c   u      9

Вопрос 4: нарезка нескольких поперечных сечений [(a, b), (c, d), ...]
Как выбрать две строки, соответствующие ('c', 'u'), а также ('a', 'w')?

         col
one two     
c   u      9
a   w      3

Вопрос 5: Один предмет, нарезанный за уровень
Как я могу получить все строки, соответствующие "a" на уровне "one" и "u" на уровне "two"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

Вопрос 6: Произвольная нарезка
Как я могу нарезать определенные сечения? Для "a" и "b" я хотел бы выбрать все строки с подуровнями "u" и "v", а для "d" я хотел бы выбрать строки с подуровнем "w".

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

Вопрос 7 будет использовать уникальную настройку, состоящую из числового уровня:

np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15

Вопрос 6: Неравенство фильтрации на числовых уровнях
Как получить все строки, где значения на уровне "два" больше 5?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

7 ответов

Решение

MultiIndex / Advanced Indexing

Заметка
Этот пост будет структурирован следующим образом:

  1. Вопросы, изложенные в ФП, будут рассмотрены один за другим.
  2. Для каждого вопроса будет продемонстрирован один или несколько методов, применимых для решения этой проблемы и получения ожидаемого результата.

Заметки (очень похожие на эту) будут включены для читателей, заинтересованных в изучении дополнительных функций, деталей реализации и другой информации, кратко изложенной в данной теме. Эти заметки были составлены путем изучения документов и раскрытия различных неясных особенностей, а также из моего собственного (по общему признанию ограниченного) опыта.

Все примеры кода созданы и протестированы на pandas v0.23.4, python3.7. Если что-то неясно, или фактически неверно, или если вы не нашли решения, применимого к вашему варианту использования, пожалуйста, не стесняйтесь предлагать редактирование, запрашивать разъяснения в комментариях или открывать новый вопрос,.... в зависимости от обстоятельств,

Вот введение в некоторые распространенные идиомы (далее именуемые "Четыре идиомы"), которые мы будем часто посещать

  1. DataFrame.loc - общее решение для выбора по метке (+ pd.IndexSlice для более сложных приложений, включающих срезы)

  2. DataFrame.xs - Извлечение определенного поперечного сечения из Series / DataFrame.

  3. DataFrame.query - Укажите операции среза и / или фильтрации динамически (т. Е. Как выражение, которое оценивается динамически. Более применимо к некоторым сценариям, чем к другим. Также см. Этот раздел документации для запросов по мультииндексам.

  4. Булево индексирование с помощью маски, созданной с использованием MultiIndex.get_level_values (часто в сочетании с Index.isin особенно при фильтрации по нескольким значениям). Это также весьма полезно в некоторых обстоятельствах.

Будет полезно взглянуть на различные проблемы нарезки и фильтрации в терминах четырех идиом, чтобы лучше понять, что можно применить к данной ситуации. Очень важно понимать, что не все идиомы будут работать одинаково хорошо (если вообще) при любых обстоятельствах. Если идиома не была указана в качестве потенциального решения проблемы ниже, это означает, что идиома не может быть эффективно применена к этой проблеме.


Вопрос 1

Как выбрать строки, имеющие "a" на уровне "one"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

Ты можешь использовать loc как решение общего назначения, применимое к большинству ситуаций:

df.loc[['a']]

На данный момент, если вы получаете

TypeError: Expected tuple, got str

Это означает, что вы используете старую версию панд. Подумайте об обновлении! В противном случае используйте df.loc[('a', slice(None)), :],

Кроме того, вы можете использовать xs здесь, так как мы извлекаем одно поперечное сечение. Обратите внимание levels а также axis аргументы (разумные значения по умолчанию могут быть приняты здесь).

df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

Здесь drop_level=False аргумент нужен, чтобы предотвратить xs от падения уровня "один" в результате (уровень, на который мы нарезали).

Еще один вариант здесь использует query:

df.query("one == 'a'")

Если у индекса нет имени, вам нужно изменить строку запроса на "ilevel_0 == 'a'",

Наконец, используя get_level_values:

df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']

Кроме того, как я могу снизить уровень "один" в выводе?

     col
two     
t      0
u      1
v      2
w      3

Это можно легко сделать, используя либо

df.loc['a'] # Notice the single string argument instead the list.

Или же,

df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')

Обратите внимание, что мы можем опустить drop_level аргумент (предполагается, что True по умолчанию).

Заметка
Вы можете заметить, что отфильтрованный DataFrame может иметь все уровни, даже если они не отображаются при распечатке DataFrame. Например,

v = df.loc[['a']]
print(v)
         col
one two     
a   t      0
    u      1
    v      2
    w      3

print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

Вы можете избавиться от этих уровней, используя MultiIndex.remove_unused_levels:

v.index = v.index.remove_unused_levels()

print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

Вопрос 1б

Как мне нарезать все строки со значением "t" на уровне "два"?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

Интуитивно, вы хотели бы что-то вовлекающее slice():

df.loc[(slice(None), 't'), :]

Это просто работает!™ Но это неуклюже. Мы можем облегчить более естественный синтаксис секционирования, используя pd.IndexSlice API здесь.

idx = pd.IndexSlice
df.loc[idx[:, 't'], :]

Это намного, намного чище.

Заметка
Почему концевой срез : через столбцы требуется? Это потому что, loc может использоваться для выбора и нарезки вдоль обеих осей (axis=0 или же axis=1). Без явного указания на то, по какой оси следует выполнять нарезку, операция становится неоднозначной. Смотрите большую красную коробку в документации по нарезке.

Если вы хотите удалить любой оттенок двусмысленности, loc принимает axis параметр:

df.loc(axis=0)[pd.IndexSlice[:, 't']]

Без axis параметр (то есть, просто делая df.loc[pd.IndexSlice[:, 't']]), предполагается нарезка на столбцы, а KeyError будет поднят в этом случае.

Это задокументировано в слайсерах. Однако для целей этого поста мы явно укажем все оси.

С xs, это

df.xs('t', axis=0, level=1, drop_level=False)

С query, это

df.query("two == 't'")
# Or, if the first level has no name, 
# df.query("ilevel_1 == 't'") 

И наконец, с get_level_values, вы можете сделать

df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']

Все к тому же эффекту.


вопрос 2

Как выбрать строки, соответствующие элементам "b" и "d" на уровне "один"?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

Используя loc, это делается аналогичным образом, указав список.

df.loc[['b', 'd']]

Чтобы решить вышеуказанную проблему выбора "b" и "d", вы также можете использовать query:

items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')

Заметка
Да, парсер по умолчанию 'pandas', но важно подчеркнуть, что этот синтаксис не является обычно Python. Парсер Pandas генерирует немного другое дерево разбора из выражения. Это сделано для того, чтобы сделать некоторые операции более понятными для указания. Для получения дополнительной информации, пожалуйста, прочитайте мой пост об оценке динамических выражений в пандах с использованием pd.eval ().

И с get_level_values + Index.isin:

df[df.index.get_level_values("one").isin(['b', 'd'])]

Вопрос 2б

Как бы я получить все значения, соответствующие "т" и "ш" на уровне "два"?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

С loc это возможно только в сочетании с pd.IndexSlice,

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

Первый двоеточие : в pd.IndexSlice[:, ['t', 'w']] означает разрезать через первый уровень. По мере увеличения глубины запрашиваемого уровня вам нужно будет указывать больше срезов, по одному на каждый уровень. Однако вам не нужно указывать больше уровней, чем нарезанный.

С query, это

items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas') 
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')

С get_level_values а также Index.isin (похоже на выше):

df[df.index.get_level_values('two').isin(['t', 'w'])]

Вопрос 3

Как получить сечение, то есть одну строку, имеющую определенные значения для индекса из df? В частности, как я могу получить сечение ('c', 'u'), данный

         col
one two     
c   u      9

использование loc указав кортеж ключей:

df.loc[('c', 'u'), :]

Или же,

df.loc[pd.IndexSlice[('c', 'u')]]

Заметка
В этот момент вы можете столкнуться с PerformanceWarning это выглядит так:

PerformanceWarning: indexing past lexsort depth may impact performance.

Это просто означает, что ваш индекс не отсортирован. pandas зависит от сортируемого индекса (в данном случае лексикографически, поскольку мы имеем дело со строковыми значениями) для оптимального поиска и извлечения. Быстрое решение состоит в том, чтобы отсортировать ваш DataFrame заранее, используя DataFrame.sort_index, Это особенно желательно с точки зрения производительности, если вы планируете выполнять несколько таких запросов в тандеме:

df_sort = df.sort_index()
df_sort.loc[('c', 'u')]

Вы также можете использовать MultiIndex.is_lexsorted() проверить, отсортирован ли индекс или нет. Эта функция возвращает True или же False соответственно. Вы можете вызвать эту функцию, чтобы определить, требуется ли дополнительный шаг сортировки или нет.

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

df.xs(('c', 'u'))

С query, вещи становятся немного неуклюжими:

df.query("one == 'c' and two == 'u'")

Теперь вы можете видеть, что это будет относительно сложно обобщить. Но все еще в порядке для этой конкретной проблемы.

С доступом, охватывающим несколько уровней, get_level_values все еще может использоваться, но не рекомендуется:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]

Вопрос 4

Как выбрать две строки, соответствующие ('c', 'u'), а также ('a', 'w')?

         col
one two     
c   u      9
a   w      3

С loc, это все так же просто, как:

df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

С query вам нужно будет динамически генерировать строку запроса, перебирая поперечные сечения и уровни:

cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses) 

query = '(' + ') or ('.join([
    ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) 
    for cs in cses
]) + ')'

print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))

df.query(query)

100% НЕ РЕКОМЕНДУЕМ! Но это возможно.


Вопрос 5

Как я могу получить все строки, соответствующие "a" на уровне "one" и "u" на уровне "two"?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

Это на самом деле очень сложно сделать с loc гарантируя правильность и сохраняя ясность кода. df.loc[pd.IndexSlice['a', 't']] неверно, это интерпретируется как df.loc[pd.IndexSlice[('a', 't')]] (т. е. выбор поперечного сечения). Вы можете придумать решение с pd.concat обрабатывать каждую этикетку отдельно:

pd.concat([
    df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])

         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0   # Does this look right to you? No, it isn't!
b   t      4
    t      8
d   t     12

Но вы заметите, что один из рядов дублируется. Это потому, что этот ряд удовлетворял обоим условиям нарезки, и поэтому появился дважды. Вам вместо этого нужно будет сделать

v = pd.concat([
        df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]

Но если ваш DataFrame по своей сути содержит дублирующиеся индексы (которые вы хотите), то это не сохранит их. Используйте с особой осторожностью.

С query это тупо просто

df.query("one == 'a' or two == 't'")

С get_level_values, это все еще просто, но не так элегантно:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 | m2]

Вопрос 6

Как я могу нарезать определенные сечения? Для "a" и "b" я хотел бы выбрать все строки с подуровнями "u" и "v", а для "d" я хотел бы выбрать строки с подуровнем "w".

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

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

Как правило, для решения подобных задач требуется явная передача списка ключей loc, Один из способов сделать это с:

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

Если вы хотите сохранить некоторую типизацию, вы поймете, что есть шаблон для разбиения "a", "b" и его подуровней, поэтому мы можем разделить задачу нарезки на две части и concat результат:

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

Спецификация нарезки для "a" и "b" немного чище (('a', 'b'), ('u', 'v')) потому что одни и те же подуровни индексируются одинаково для каждого уровня.


Вопрос 7

Как получить все строки, где значения на уровне "два" больше 5?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

Это можно сделать с помощью query,

df2.query("two > 5")

А также get_level_values,

df2[df2.index.get_level_values('two') > 5]

Заметка
Подобно этому примеру, мы можем фильтровать на основе любого произвольного условия, используя эти конструкции. В общем, полезно помнить, что loc а также xs специально для индексирования на основе меток, в то время как query а также get_level_values полезны для построения общих условных масок для фильтрации.


Бонусный вопрос

Что делать, если мне нужно нарезать MultiIndex колонна?

На самом деле, большинство решений здесь применимо и к столбцам с небольшими изменениями. Рассматривать:

np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
        list('ABCD'), list('efgh')
], names=['one','two'])

df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)

one  A           B           C           D         
two  e  f  g  h  e  f  g  h  e  f  g  h  e  f  g  h
0    5  0  3  3  7  9  3  5  2  4  7  6  8  8  1  6
1    7  7  8  1  5  9  8  9  4  3  0  3  5  0  2  3
2    8  1  3  3  3  7  0  1  9  9  0  4  7  3  2  7

Это следующие изменения, которые вам нужно внести в Четыре Идиомы, чтобы они работали со столбцами.

  1. Нарезать с loc использовать

    df3.loc[:, ....] # Notice how we slice across the index with `:`. 
    

    Или же,

    df3.loc[:, pd.IndexSlice[...]]
    
  2. Использовать xs в зависимости от обстоятельств, просто передайте аргумент axis=1,

  3. Вы можете получить доступ к значениям уровня столбца напрямую с df.columns.get_level_values, Затем вам нужно будет сделать что-то вроде

    df.loc[:, {condition}] 
    

    куда {condition} представляет собой некоторое условие, построенное с использованием columns.get_level_values,

  4. Использовать query, ваш единственный вариант - это транспонировать, запрашивать индекс и снова транспонировать:

    df3.T.query(...).T
    

    Не рекомендуется, используйте один из 3 других вариантов.

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

Я далек от эксперта, но я наткнулся на решение, которое не было указано в исчерпывающих ответах выше. Я не гарантирую, что решения в любом случае оптимальны.

Это другой способ получить результат, немного отличный от вопроса № 6 выше. (и, вероятно, другие вопросы)

В частности, я искал:

  1. Способ выбора двух + значений из одного уровня индекса и одного значения из другого уровня индекса, и
  2. Способ оставить значения индекса из предыдущей операции в выводе фрейма данных.

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

  1. Индексы были безымянными.

На фрейме данных игрушки ниже:

    index = pd.MultiIndex.from_product([['a','b'],
                               ['stock1','stock2','stock3'],
                               ['price','volume','velocity']])

    df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
                      10,11,12,13,14,15,16,17,18], 
                       index)

                        0
    a stock1 price      1
             volume     2
             velocity   3
      stock2 price      4
             volume     5
             velocity   6
      stock3 price      7
             volume     8
             velocity   9
    b stock1 price     10
             volume    11
             velocity  12
      stock2 price     13
             volume    14
             velocity  15
      stock3 price     16
             volume    17
             velocity  18

Конечно, с помощью приведенных ниже работ:

    df.xs(('stock1', 'velocity'), level=(1,2))

        0
    a   3
    b  12

Но мне нужен был другой результат, поэтому мой метод получения этого результата был следующим:

   df.iloc[df.index.isin(['stock1'], level=1) & 
           df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
    b stock1 velocity  12

И если бы я хотел два + значения с одного уровня и одно (или 2+) значение с другого уровня:

    df.iloc[df.index.isin(['stock1','stock3'], level=1) & 
            df.index.isin(['velocity'], level=2)] 

                        0
    a stock1 velocity   3
      stock3 velocity   9
    b stock1 velocity  12
      stock3 velocity  18

Вышеупомянутый метод, вероятно, немного неуклюж, но я обнаружил, что он удовлетворяет мои потребности и, как бонус, мне было легче понять и прочитать.

Похоже, отличный случай для dfsql

      df.sql(<SQL select statement>)

https://github.com/mindsdb/dfsql

Полная статья об этом здесь:

https://medium.com/riselab/why-every-data-scientist-using-pandas-needs-modin-bringing-sql-to-dataframes-3b216b29a7c0

Я давно использую и ценю этот вопрос и ответ user4909087, который очень тщательен и обрабатывает все случаи. Подобно , я тоже хотел найти способ работы с несколькими индексами, содержащими несколько уровней.

Наконец-то я нашел способ получить произвольное количество срезов с заданным уровнем или именованным индексом, который может решить несколько вопросов, предложенных выше. Основное улучшение здесь заключается в том, что не нужно разбирать slice(None)или :с pd.IndexSliceдля нескольких индексов или срезов.

      import pandas as pd

def slice_df_by(df_, slice_by=["Oman", "Nairobi",], slice_idx='country'):
    idxn = df_.index.names.index(slice_idx)
    return df_.loc[tuple([slice(None)]*idxn +[slice_by] ), :]

gender = tuple(["male", "female"]*6)
thrown = tuple(["rock", "scissors", "paper"]*4) 
country = tuple(["Nairobi", "Oman", "Djibouti", "Belize"]*3) 
names = tuple(["Chris", "Pat", "Michele", "Thomy", "Musa", "Casey"]*2)

tuples = list(zip(gender, thrown, country, names))

idx = pd.MultiIndex.from_tuples(tuples, 
                                names=["gender", "thrown", "country", "name"])

df = pd.DataFrame({'Count A': [12., 70., 30., 20.]*3, 
                   'Count B': [12., 70., 30., 20.]*3}, index=idx)

Преимущество здесь в том, что вы можете добавить любую комбинацию этих вызовов в функцию slice_df_byчтобы получить более сложные срезы, используя только имя индекса и список значений.

      print(slice_df_by(df))

                                 Count A  Count B
gender thrown   country name                     
female scissors Oman    Pat         70.0     70.0
       paper    Oman    Casey       70.0     70.0
       rock     Oman    Thomy       70.0     70.0
male   rock     Nairobi Chris       12.0     12.0
       scissors Nairobi Musa        12.0     12.0
       paper    Nairobi Michele     12.0     12.0

Подвох, как указал user5489642Существует множество способов удовлетворить это, используя описанный здесь подход, например df.index.names = ["names", "for", "the", "indices"]или какой-то такой метод:

      idxz = lambda ixln=4: [chr(i) for i in np.arange(ixln)+65]
df.index.names = idxz(len(df.index.names))
print(idxz())
Out[132]: ['A', 'B', 'C', 'D']

Принятый ответ превосходен, но.loc(axis=0)обеспечивает более краткое решение при разрезании только на 2-м уровне, например вопросы 1b и 2b. Это устраняет необходимость вget_level_valuesиpd.IndexSlice.


Вопрос 1б

Как разрезать все строки со значением «t» на втором уровне?

                col
one two     
a   t      0
b   t      4
    t      8
d   t     12

df.loc(axis=0)[:,'t']это все, что тебе нужно.

Вопрос 2б

Как мне получить все значения, соответствующие «t» и «w» на втором уровне?

                col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

df.loc(axis=0)[:,['t', 'w']]это еще раз все, что вам нужно.

axis=0эквивалентноaxis='rows'.

Аналогично, чтобы разрезать столбцы MultiIndex, используйте.loc(axis=1)или.loc(axis='columns')

Работать с мультииндексами pandas обычно довольно сложно, поэтому я создал небольшую вспомогательную библиотеку pandas-indexing , чтобы упростить работу с ними. Она содержит набор селекторов для элегантного запроса в мультииндекс:

      from pandas_indexing import isin

Вопрос 1

Как выбрать строки, имеющие «а» на уровне «один»?

                col
one two     
a   t      0
    u      1
    v      2
    w      3
      df.loc[isin(one="a")]

Кроме того, как я могу отбросить уровень «один» в выводе?

            col
two     
t      0
u      1
v      2
w      3

Ну и брось тогда.

      df.loc[isin(one="a")].droplevel("one")

Вопрос 1б

Как разрезать все строки со значением «t» на втором уровне?

                col
one two     
a   t      0
b   t      4
    t      8
d   t     12
      df.loc[isin(two="t")]

Это намного, намного чище.


вопрос 2

Как выбрать строки, соответствующие пунктам «b» и «d» на уровне «один»?

                col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

Используя loc, это делается аналогичным образом путем указания списка.

      df.loc[isin(one=["b", "d"])]

Вопрос 2б

Как мне получить все значения, соответствующие «t» и «w» на втором уровне?

                col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15
      df.loc[isin(two=["t", "w"])] 

Вопрос 3

Как получить поперечное сечение, т. е. одну строку с определенными значениями индекса изdf? В частности, как мне получить поперечное сечение, заданное формулой

                col
one two     
c   u      9
      df.loc[isin(one="c", two="u")]

Вопрос 4

Как выбрать две строки, соответствующие('c', 'u'), и('a', 'w')?

                col
one two     
c   u      9
a   w      3

Сisinпредполагает и между разными уровнями и или между разными значениями, интуитивное решение:

      df.loc[isin(one=["c", "a"], two=["u", "w"])]

тоже бы выбрал("c", "w")что вам, вероятно, не нужно, но вы можете легко комбинировать селекторы:

      df.loc[isin(one="c", two="u") | isin(one="a", two="w")]

или если у вас есть их список, используйтеsemijoin

      from pandas_indexing import semijoin
semijoin(df, pd.MultiIndex.from_tuples([("c", "u"), ("a", "w")], names=["one", "two"]))

Вопрос 5

Как я могу получить все строки, соответствующие «a» на уровне «один» или «t» на уровне «два»?

                col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12
      df.loc[isin(one="a") | isin(two="t")]

Вопрос 6

Как я могу нарезать определенные сечения? Для «a» и «b» я хотел бы выбрать все строки с подуровнями «u» и «v», а для «d» я хотел бы выбрать строки с подуровнем «w».

                col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15
      df.loc[isin(one=["a", "b"], two=["u", "v"]) | isin(one="d", two="w")]

Вопрос 7

Как получить все строки, в которых значения на втором уровне больше 5?

                col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

Из версии 0.2.6:

      df2.loc[isin(two=lambda s: s > 5)]

Один из вариантов — select_rows из pyjanitor:

      # pip install pyjanitor
import pandas as pd
import janitor

Вопрос 1

Как выбрать строки, имеющие «а» на уровне «один»?

      df.select_rows('a') 
         col
one two     
a   t      0
    u      1
    v      2
    w      3

Кроме того, как я могу понизить уровень «один» на выходе?

      df.select_rows('a').droplevel('one') 
     col
two     
t      0
u      1
v      2
w      3

Вопрос 1б

Как разрезать все строки со значением «t» на уровне «два»?

                col
one two     
a   t      0
b   t      4
    t      8
d   t     12

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

      df.select_rows({'two':'t'})
         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

вопрос 2

Как я могу выбрать строки, соответствующие элементам «b» и «d» на уровне «один»?

                col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

Поскольку выбор находится на одном уровне, передайте список меток:

      df.select_rows(['b','d'])
         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

Вопрос 2б

Как мне получить все значения, соответствующие «t» и «w» на уровне «два»?

                col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

Используй словарь:

      df.select_rows({'two':['t','w']})
         col
one two     
a   t      0
b   t      4
    t      8
d   t     12
a   w      3
b   w      7
d   w     11
    w     15

Вопрос 3

Как получить поперечное сечение, т. е. одну строку с определенными значениями индекса изdf? В частности, как мне получить поперечное сечение , заданное

                col
one two     
c   u      9

Идём по уровням (по горизонтали, а не по вертикали), требуется кортеж:

      # sort required to avoid lexsort performance warning
df.sort_index().select_rows(('c','u'))
         col
one two     
c   u      9

Вопрос 4

Как выбрать две строки, соответствующие('c', 'u'), и('a', 'w')?

                col
one two     
c   u      9
a   w      3

select_rowsпринимает несколько переменных аргументов:

      df.sort_index().select_rows(('c','u'), ('a','w'))
         col
one two     
c   u      9
a   w      3

Вопрос 5

Как я могу получить все строки, соответствующие «а» на уровне «один» или «т» на уровне «два»?

                col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12
      df.select_rows('a', {'two':'t'})
         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0
b   t      4
    t      8
d   t     12

Вопрос 6

Как я могу разрезать определенные поперечные сечения? Для «a» и «b» я хотел бы выбрать все строки с подуровнями «u» и «v», а для «d» я хотел бы выбрать строки с подуровнем «w».

                col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15
      df.sort_index().select_rows({'one':['a','b'], 'two':['u','v']}, ('d','w'))
         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

Вопрос 7

Как получить все строки, в которых значения на уровне «два» больше 5?

                col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

Со словарем вы можете передать функцию, если она может быть оценена в объекте Index:

      df2.select_rows({'two': lambda df: df > 5})
         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

Вы можете выбрать столбцы с помощью функции select_columns . Существует также общая функция выбора для выбора как строк, так и столбцов.

Функции расширяемы: давайте посмотрим, как это работает с ответом @double0darbo:

      df.select_rows({'country':['Oman', 'Nairobi']})
                                 Count A  Count B
gender thrown   country name                     
female scissors Oman    Pat         70.0     70.0
       paper    Oman    Casey       70.0     70.0
       rock     Oman    Thomy       70.0     70.0
male   rock     Nairobi Chris       12.0     12.0
       scissors Nairobi Musa        12.0     12.0
       paper    Nairobi Michele     12.0     12.0

Попытка ответа @ra:

      df.select_rows({1:'stock1', 2:'velocity'})
                    0
a stock1 velocity   3
b stock1 velocity  12

df.select_rows({1:['stock1','stock3'], 2:'velocity'})
                    0
a stock1 velocity   3
b stock1 velocity  12
a stock3 velocity   9
b stock3 velocity  18

df.select_rows({0:slice('a',None), 1:['stock1','stock3'], 2:'velocity'})
                    0
a stock1 velocity   3
  stock3 velocity   9
b stock1 velocity  12
  stock3 velocity  18

Другие вопросы по тегам