Логический оператор для логического индексирования в Pandas
Я работаю с логическим индексом в Пандах. Вопрос в том, почему утверждение:
a[(a['some_column']==some_number) & (a['some_other_column']==some_other_number)]
работает нормально, тогда как
a[(a['some_column']==some_number) and (a['some_other_column']==some_other_number)]
существует с ошибкой?
Пример:
a=pd.DataFrame({'x':[1,1],'y':[10,20]})
In: a[(a['x']==1)&(a['y']==10)]
Out: x y
0 1 10
In: a[(a['x']==1) and (a['y']==10)]
Out: ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
4 ответа
Когда ты сказал
(a['x']==1) and (a['y']==10)
Вы неявно просите Python конвертировать (a['x']==1)
а также (a['y']==10)
для логических значений.
Массивы NumPy (длиной более 1) и объекты Pandas, такие как Series, не имеют логического значения - другими словами, они повышают
ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().
когда используется как логическое значение. Это потому, что неясно, когда он должен быть Истинным или Ложным. Некоторые пользователи могут предположить, что они True, если они имеют ненулевую длину, например, список Python. Другие могут желать, чтобы он был Истинным, только если все его элементы Истинны. Другие могут захотеть, чтобы оно было Истиной, если любой из его элементов Истинен.
Поскольку существует множество противоречивых ожиданий, дизайнеры NumPy и Pandas отказываются угадывать и вместо этого выдают ошибку ValueError.
Вместо этого вы должны быть явными, вызывая empty()
, all()
или же any()
метод, чтобы указать, какое поведение вы хотите.
В этом случае, однако, похоже, что вы не хотите логическую оценку, вы хотите поэлементное логическое-и. Это то, что &
бинарный оператор выполняет:
(a['x']==1) & (a['y']==10)
возвращает логический массив
Кстати, как отмечает alexpmil, скобки являются обязательными, так как &
имеет более высокий приоритет оператора, чем ==
, Без скобок, a['x']==1 & a['y']==10
будет оцениваться как a['x'] == (1 & a['y']) == 10
что в свою очередь будет эквивалентно цепочечному сравнению (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10)
, Это выражение формы Series and Series
, Использование and
с двумя сериями снова вызовет то же самое ValueError
как указано выше. Вот почему круглые скобки являются обязательными.
TLDR;
Питона and
, or
а также not
Логические операторы предназначены для работы со скалярами. Поэтому Pandas пришлось сделать лучше и переопределить побитовые операторы, чтобы получить векторизованную (поэлементную) версию этой функциональности.
Итак, следующее в Python...
x, y = True, False
x and y # Logical AND
x or y # Logical OR
not x # Logical NOT
... будет переводить на,
x = pd.Series([True, False])
y = pd.Series([False, True])
x & y # Element-wise logical AND
x | y # Element-wise logical OR
~x # Element-wise logical NOT
для панд и тд.
Если в процессе выполнения логической операции вы получаете ValueError
, тогда вам нужно использовать скобки для группировки:
(expression1) op (expression2)
Например,
(df['col1'] == x) & (df['col2'] == y)
И так далее.
Логическое индексирование
Обычной операцией является вычисление логических масок с помощью логических условий для фильтрации данных. Pandas предоставляет три оператора: &
для логического И, |
для логического ИЛИ, и ~
для логического НЕ.
Рассмотрим следующую настройку:
np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (5, 3)), columns=list('ABC'))
df
A B C
0 5 0 3
1 3 7 9
2 3 5 2
3 4 7 6
4 8 8 1
Логическое И
За df
выше, скажем, вы хотите вернуть все строки, где A < 5 и B > 5. Это делается путем вычисления масок для каждого условия в отдельности и их AND.
Перегружен побитовый &
оператор
Прежде чем продолжить, пожалуйста, обратите внимание на эту конкретную выдержку из документов, в которой говорится
Другой распространенной операцией является использование логических векторов для фильтрации данных. Операторы:
|
заor
,&
заand
, а также~
заnot
, Они должны быть сгруппированы с помощью круглых скобок, так как по умолчанию Python оценивает выражение, такое какdf.A > 2 & df.B < 3
какdf.A > (2 & df.B) < 3
в то время как желаемый порядок оценки(df.A > 2) & (df.B < 3)
,
Таким образом, с учетом этого, поэлементное логическое И может быть реализовано с помощью побитового оператора &
:
df['A'] < 5
0 False
1 True
2 True
3 True
4 False
Name: A, dtype: bool
df['B'] > 5
0 False
1 True
2 False
3 True
4 True
Name: B, dtype: bool
(df['A'] < 5) & (df['B'] > 5)
0 False
1 True
2 False
3 True
4 False
dtype: bool
И последующий этап фильтрации просто,
df[(df['A'] < 5) & (df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
Скобки используются для переопределения порядка приоритета по умолчанию для побитовых операторов, которые имеют более высокий приоритет над условными операторами <
а также >
, Смотрите раздел Приоритет оператора в документации по Python.
Если вы не используете скобки, выражение оценивается неправильно. Например, если вы случайно попытались сделать что-то
df['A'] < 5 & df['B'] > 5
Разбирается как
df['A'] < (5 & df['B']) > 5
Который становится,
df['A'] < something_you_dont_want > 5
Который становится (см. Документы по питону по сравнению цепочечных операторов),
(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)
Который становится,
# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2
Какие броски
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Так что, не делайте эту ошибку! 1
Избегание группировки скобок
Исправление на самом деле довольно просто. Большинство операторов имеют соответствующий связанный метод для DataFrames. Если отдельные маски создаются с использованием функций вместо условных операторов, вам больше не нужно группировать по пареням, чтобы указать порядок оценки:
df['A'].lt(5)
0 True
1 True
2 True
3 True
4 False
Name: A, dtype: bool
df['B'].gt(5)
0 False
1 True
2 False
3 True
4 True
Name: B, dtype: bool
df['A'].lt(5) & df['B'].gt(5)
0 False
1 True
2 False
3 True
4 False
dtype: bool
Смотрите раздел Гибкие сравнения., Подводя итог, мы имеем
╒════╤════════════╤════════════╕
│ │ Operator │ Function │
╞════╪════════════╪════════════╡
│ 0 │ > │ gt │
├────┼────────────┼────────────┤
│ 1 │ >= │ ge │
├────┼────────────┼────────────┤
│ 2 │ < │ lt │
├────┼────────────┼────────────┤
│ 3 │ <= │ le │
├────┼────────────┼────────────┤
│ 4 │ == │ eq │
├────┼────────────┼────────────┤
│ 5 │ != │ ne │
╘════╧════════════╧════════════╛
Другой способ избежать скобок заключается в использовании DataFrame.query
(или же eval
):
df.query('A < 5 and B > 5')
A B C
1 3 7 9
3 4 7 6
Я подробно документировал query
а также eval
в оценке динамических выражений в пандах с использованием pd.eval ().
operator.and_
Позволяет выполнять эту операцию функциональным образом. Внутренние звонки Series.__and__
что соответствует побитовому оператору.
import operator
operator.and_(df['A'] < 5, df['B'] > 5)
# Same as,
# (df['A'] < 5).__and__(df['B'] > 5)
0 False
1 True
2 False
3 True
4 False
dtype: bool
df[operator.and_(df['A'] < 5, df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
Обычно это вам не понадобится, но это полезно знать.
Обобщая: np.logical_and
(а также logical_and.reduce
)
Другой альтернативой является использование np.logical_and
, что также не требует группировки скобок:
np.logical_and(df['A'] < 5, df['B'] > 5)
0 False
1 True
2 False
3 True
4 False
Name: A, dtype: bool
df[np.logical_and(df['A'] < 5, df['B'] > 5)]
A B C
1 3 7 9
3 4 7 6
np.logical_and
это ufunc (универсальные функции), и большинство уфунков имеют reduce
метод. Это означает, что легче обобщать с logical_and
если у вас есть несколько масок для AND. Например, чтобы И маски m1
а также m2
а также m3
с &
, вам придется сделать
m1 & m2 & m3
Тем не менее, более простой вариант
np.logical_and.reduce([m1, m2, m3])
Это мощно, потому что позволяет вам строить поверх этого более сложную логику (например, динамически генерировать маски в понимании списка и добавлять их все):
import operator
cols = ['A', 'B']
ops = [np.less, np.greater]
values = [5, 5]
m = np.logical_and.reduce([op(df[c], v) for op, c, v in zip(ops, cols, values)])
m
# array([False, True, False, True, False])
df[m]
A B C
1 3 7 9
3 4 7 6
1 - Я знаю, что я об этом говорю, но, пожалуйста, потерпите меня. Это очень, очень распространенная ошибка новичка, и должна быть объяснена очень тщательно.
Логическое ИЛИ
Для df
выше, скажем, вы хотите вернуть все строки, где A == 3 или B == 7.
Перегружен побитовый |
df['A'] == 3
0 False
1 True
2 True
3 False
4 False
Name: A, dtype: bool
df['B'] == 7
0 False
1 True
2 False
3 True
4 False
Name: B, dtype: bool
(df['A'] == 3) | (df['B'] == 7)
0 False
1 True
2 True
3 True
4 False
dtype: bool
df[(df['A'] == 3) | (df['B'] == 7)]
A B C
1 3 7 9
2 3 5 2
3 4 7 6
Если вы еще этого не сделали, ознакомьтесь также с разделом " Логическое И" выше, здесь действуют все предостережения.
В качестве альтернативы, эта операция может быть указана с
df[df['A'].eq(3) | df['B'].eq(7)]
A B C
1 3 7 9
2 3 5 2
3 4 7 6
operator.or_
Вызовы Series.__or__
под капотом.
operator.or_(df['A'] == 3, df['B'] == 7)
# Same as,
# (df['A'] == 3).__or__(df['B'] == 7)
0 False
1 True
2 True
3 True
4 False
dtype: bool
df[operator.or_(df['A'] == 3, df['B'] == 7)]
A B C
1 3 7 9
2 3 5 2
3 4 7 6
np.logical_or
Для двух условий используйте logical_or
:
np.logical_or(df['A'] == 3, df['B'] == 7)
0 False
1 True
2 True
3 True
4 False
Name: A, dtype: bool
df[np.logical_or(df['A'] == 3, df['B'] == 7)]
A B C
1 3 7 9
2 3 5 2
3 4 7 6
Для нескольких масок используйте logical_or.reduce
:
np.logical_or.reduce([df['A'] == 3, df['B'] == 7])
# array([False, True, True, True, False])
df[np.logical_or.reduce([df['A'] == 3, df['B'] == 7])]
A B C
1 3 7 9
2 3 5 2
3 4 7 6
Логическое НЕ
Учитывая маску, такую как
mask = pd.Series([True, True, False])
Если вам нужно инвертировать каждое логическое значение (так, чтобы конечный результат [False, False, True]
), то вы можете использовать любой из методов ниже.
побитовое ~
~mask
0 False
1 False
2 True
dtype: bool
Опять же, выражения должны быть заключены в скобки.
~(df['A'] == 3)
0 True
1 False
2 False
3 True
4 True
Name: A, dtype: bool
Это внутренне называет
mask.__invert__()
0 False
1 False
2 True
dtype: bool
Но не используйте его напрямую.
operator.inv
Внутренние звонки __invert__
на серии.
operator.inv(mask)
0 False
1 False
2 True
dtype: bool
np.logical_not
Это вариант NumPy.
np.logical_not(mask)
0 False
1 False
2 True
dtype: bool
Заметка, np.logical_and
можно заменить np.bitwise_and
, logical_or
с bitwise_or
, а также logical_not
с invert
,
Логические операторы для логического индексирования в Pandas
Важно понимать, что вы не можете использовать ни один из логических операторов Python (and
, or
или же not
) на pandas.Series
или же pandas.DataFrame
s (аналогично вы не можете использовать их на numpy.array
с более чем одним элементом). Причина, по которой вы не можете использовать их, заключается в том, что они неявно вызывают bool
на их операнды, которые выдают исключение, потому что эти структуры данных решили, что логическое значение массива неоднозначно:
>>> import numpy as np
>>> import pandas as pd
>>> arr = np.array([1,2,3])
>>> s = pd.Series([1,2,3])
>>> df = pd.DataFrame([1,2,3])
>>> bool(arr)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
>>> bool(s)
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
>>> bool(df)
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Я рассказал об этом более подробно в своем ответе на "Значение Истины Серии неоднозначно. Используйте a.empty, a.bool(), a.item(), a.any() или a.all()" Q+ А
Логические функции NumPys
Однако NumPy предоставляет поэлементные рабочие эквиваленты этим операторам в качестве функций, которые можно использовать на numpy.array
, pandas.Series
, pandas.DataFrame
или любой другой (соответствующий) numpy.array
подкласс:
and
имеетnp.logical_and
or
имеетnp.logical_or
not
имеетnp.logical_not
numpy.logical_xor
который не имеет эквивалента Python, но является логической операцией "исключение или"
Так что, по сути, следует использовать (при условии df1
а также df2
Являются ли панды DataFrames):
np.logical_and(df1, df2)
np.logical_or(df1, df2)
np.logical_not(df1)
np.logical_xor(df1, df2)
Побитовые функции и побитовые операторы для логических значений
Однако, если у вас есть логический массив NumPy, pandas Series или pandas DataFrames, вы также можете использовать поэлементные побитовые функции (для логических значений они - или, по крайней мере, должны - неотличимы от логических функций):
- поразрядно и:
np.bitwise_and
или&
оператор - поразрядно или:
np.bitwise_or
или|
оператор - поразрядно нет:
np.invert
(или псевдонимnp.bitwise_not
) или~
оператор - побитовый xor:
np.bitwise_xor
или^
оператор
Обычно используются операторы. Однако при объединении с операторами сравнения необходимо помнить оборачивать сравнение в круглые скобки, поскольку побитовые операторы имеют более высокий приоритет, чем операторы сравнения:
(df1 < 10) | (df2 > 10) # instead of the wrong df1 < 10 | df2 > 10
Это может раздражать, потому что логические операторы Python имеют меньший приоритет, чем операторы сравнения, поэтому вы обычно пишете a < 10 and b > 10
(где a
а также b
например, простые целые числа) и не нуждаются в скобках.
Различия между логическими и побитовыми операциями (для не булевых)
Очень важно подчеркнуть, что битовые и логические операции эквивалентны только для логических массивов NumPy (и логических серий и фреймов данных). Если они не содержат логических значений, то операции будут давать разные результаты. Я приведу примеры использования массивов NumPy, но результаты будут аналогичны для структур данных pandas:
>>> import numpy as np
>>> a1 = np.array([0, 0, 1, 1])
>>> a2 = np.array([0, 1, 0, 1])
>>> np.logical_and(a1, a2)
array([False, False, False, True])
>>> np.bitwise_and(a1, a2)
array([0, 0, 0, 1], dtype=int32)
А поскольку NumPy (и аналогично pandas) выполняет разные функции для булевых ( булевых или "маскирующих" индексных массивов) и целочисленных ( индексных массивов) индексов, результаты индексации также будут разными:
>>> a3 = np.array([1, 2, 3, 4])
>>> a3[np.logical_and(a1, a2)]
array([4])
>>> a3[np.bitwise_and(a1, a2)]
array([1, 1, 1, 2])
Таблица результатов
Logical operator | NumPy logical function | NumPy bitwise function | Bitwise operator
-------------------------------------------------------------------------------------
and | np.logical_and | np.bitwise_and | &
-------------------------------------------------------------------------------------
or | np.logical_or | np.bitwise_or | |
-------------------------------------------------------------------------------------
| np.logical_xor | np.bitwise_xor | ^
-------------------------------------------------------------------------------------
not | np.logical_not | np.invert | ~
Где логический оператор не работает для массивов NumPy, панд Series и панд DataFrames. Другие работают над этими структурами данных (и обычными объектами Python) и работают поэлементно. Однако будьте осторожны с побитовым инвертированием на простом Python bool
s потому что bool будет интерпретироваться как целые числа в этом контексте (например, ~False
возвращается -1
а также ~True
возвращается -2
).
Обратите внимание, что вы также можете использовать*
делатьand
:
In [12]: np.all([a > 20, a < 40], axis=0)
Out[12]:
array([[False, True, False, False, True],
[False, False, False, False, False],
[ True, True, False, False, False],
[False, True, False, False, False],
[False, True, False, False, False]])
In [13]: (a > 20) * (a < 40)
Out[13]:
array([[False, True, False, False, True],
[False, False, False, False, False],
[ True, True, False, False, False],
[False, True, False, False, False],
[False, True, False, False, False]])
Я не утверждаю, что это лучше, чем использованиеnp.all
или|
. Но это работает.