Ответ 1
MultiIndex/Advanced Indexing
Примечание
Этот пост будет структурирован следующим образом:
- Вопросы, изложенные в ФП, будут рассмотрены один за другим
- Для каждого вопроса будет продемонстрирован один или несколько методов, применимых для решения этой проблемы и получения ожидаемого результата.
Примечание(как и это) будет включено для читателей, заинтересованных в изучении дополнительных функций, деталей реализации, и другая информация, краткая к теме под рукой. Эти заметки были составлено путем обыскивания документов и раскрытия различных темных особенности, и из моего собственного (по общему признанию ограниченного) опыта.
Все примеры кода созданы и протестированы на pandas v0.23.4, python3.7. Если что-то не понятно, или фактически неверно, или если вы этого не сделали найти решение, применимое к вашему варианту использования, пожалуйста, не стесняйтесь предложить редактирование, запросить разъяснения в комментариях или открыть новый вопрос.... если применимо.
Вот введение в некоторые распространенные идиомы (далее именуемые "Четыре идиомы"), которые мы будем часто посещать повторно
DataFrame.loc
- общее решение для выбора по метке (+pd.IndexSlice
для более сложных приложений, включающих срезы)DataFrame.xs
- Извлечение определенного поперечного сечения из Series/DataFrame.DataFrame.query
- Укажите операции нарезки и/или фильтрации динамически (т.е. как выражение, которое оценивается динамически. Более применимо к некоторым сценариям, чем к другим. Также см. этот раздел документации для запроса на мультииндексах.Булево индексирование с использованием маски, созданной с помощью
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'])
Вопрос 1b
Как мне нарезать все строки со значением "t" на уровне "два"?
col one two a t 0 b t 4 t 8 d t 12
Интуитивно понятно, что вам нужно что-то, связанное с slice()
:
df.loc[(slice(None), 't'), :]
Это просто работает! ™ Но это неуклюже. Здесь мы можем упростить более естественный синтаксис секционирования, используя API pd.IndexSlice
.
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'])]
Вопрос 2b
Как получить все значения, соответствующие "t" и "w" на уровне "два"?
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
Как я могу получить все строки, соответствующие "а" на уровне "один" или "т" на уровне "два"?
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') == 'a')
m2 = (df.index.get_level_values('two') == 't')
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
Это следующие изменения, которые вам необходимо внести в Four Idioms, чтобы они работали со столбцами.
Для нарезки с помощью
loc
используйтеdf3.loc[:, ....] # Notice how we slice across the index with ':'.
или
df3.loc[:, pd.IndexSlice[...]]
Чтобы использовать
xs
соответствующим образом, просто передайте аргументaxis=1
.Вы можете получить доступ к значениям уровня столбца напрямую, используя
df.columns.get_level_values
. Затем вам нужно будет сделать что-то вродеdf.loc[:, {condition}]
Где
{condition}
представляет некоторое условие, построенное с использованиемcolumns.get_level_values
.Чтобы использовать
query
, единственный вариант - это транспонировать, запрашивать индекс и снова транспонировать:df3.T.query(...).T
Не рекомендуется, используйте один из трех других вариантов.