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