TL; DR; Логические операторы в пандах &
, |
и ~
, и круглые скобки (...)
важны!
Python это and
, or
и not
логические операторы предназначены для работы с скаляров. Поэтому Pandas пришлось сделать что-то лучше и переопределить побитовые операторы для достижения векторизованной (поэлементной) версии этой функциональности.
Итак, следующее в python ( exp1
и exp2
это выражения, которые оценивают логический результат) ...
exp1 and exp2
exp1 or exp2
not exp1
... переведем на ...
exp1 & exp2
exp1 | exp2
~exp1
для панд.
Если в процессе выполнения логической операции вы получите a ValueError
, то для группировки нужно использовать круглые скобки:
(exp1) op (exp2)
Например,
(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
as 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
Что становится (см. Документацию python о сравнении связанных операторов ),
(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)
Что становится,
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)
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 (универсальные функции) , и у большинства 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
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)
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])
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
.