Примечание
Этот пост будет структурирован следующим образом:
- Вопросы, изложенные в ФП, будут рассмотрены один за другим
- Для каждого вопроса будет продемонстрирован один или несколько методов, применимых для решения этой проблемы и получения ожидаемого результата.
Примечание s (очень похоже на это) будет включено для читателей, заинтересованных в изучении дополнительных функциональных возможностей, деталей реализации и другой информации, кратко описывающей данную тему.Эти заметки были скомпилированы в процессе изучения документов и раскрытия различных неясных функций, а также из моего собственного (общеизвестно ограниченного) опыта.
Все примеры кода созданы и протестированы на pandas v0.23.4, python3.7.Если что-то неясно, или фактически неверно, или если вы не нашли решения, применимого к вашему варианту использования, пожалуйста, не стесняйтесь предлагать редактирование, запрашивать разъяснения в комментариях или открывать новый вопрос, .... в зависимости от обстоятельств.
Вот введение в некоторые распространенные идиомы (далее именуемые Четыреми идиомами), которые мы будем часто посещать
DataFrame.loc
- общее решение для выбора по метке (+ pd.IndexSlice
для более сложных приложений, включающих срезы)
DataFrame.xs
- Извлечение определенного поперечного сечения из серии / кадра данных.
DataFrame.query
- Укажите операции среза и / или фильтрации динамически (т. Е. Как выражение, которое оценивается динамически. Более применимо к некоторым сценариям, чем к другим. Также см. этот раздел документации для запросов в мультииндексах.
Booleanиндексирование с использованием маски, созданной с использованием MultiIndex.get_level_values
(часто в сочетании с Index.isin
, особенно при фильтрации с несколькими значениями).Это также весьма полезно в некоторых обстоятельствах.
Будет полезно взглянуть на различные проблемы нарезки и фильтрации в терминах Четырех идиом, чтобы лучше понять, что можно применить кданная ситуация.Очень важно понимать, что не все идиомы будут работать одинаково хорошо (если вообще) при любых обстоятельствах.Если идиома не была указана в качестве потенциального решения проблемы ниже, это означает, что идиома не может быть эффективно применена к этой проблеме.
Вопрос 1
КакВыбрать строки, имеющие «а» на уровне «один»?
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
Как получить все строки, соответствующие "a" на уровне "one" или "t" на уровне "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
Это следующие изменения, которые вам необходимо внести в Четыре Идиомы, чтобы они работали со столбцами.
Чтобы нарезать с помощью 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
Не рекомендуется, используйте один из трех других вариантов.