Логический оператор для логического индексирования в 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или|. Но это работает.

Другие вопросы по тегам