Логические операторы для логической индексации в Pandas

167

Я работаю с логическим индексом в 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()
user2988577
источник
6
Это связано с тем, что массивы numpy и серии pandas используют побитовые операторы, а не логические, поскольку вы сравниваете каждый элемент в массиве / серии с другим. Следовательно, в этой ситуации нет смысла использовать логический оператор. см. по теме: stackoverflow.com/questions/8632033/…
EdChum
9
В Python and != &. andОператор в Python не может быть переопределен, в то время как &оператор ( __and__) может. Отсюда и выбор использования &в numpy и pandas.
Стивен Румбальский

Ответы:

227

Когда ты говоришь

(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().

при использовании в качестве логического значения. Это потому, что неясно, когда это должно быть Истина или Ложь . Некоторые пользователи могут предположить, что они истинны, если у них ненулевая длина, например список Python. Другие могут пожелать, чтобы он был Истинным, только если все его элементы Истинны. Другие могут захотеть, чтобы он был True, если какой-либо из его элементов имеет значение True.

Поскольку существует так много противоречивых ожиданий, разработчики 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что и выше. Поэтому круглые скобки обязательны.

Unutbu
источник
3
массивы numpy имеют это свойство, если они имеют длину один. Только панды УБС (упрямо) отбросы догадаться: р
Энди Хэйден
4
Разве «&» не имеет той же неоднозначной кривой, что и «и»? Почему, когда дело доходит до «И», все пользователи внезапно соглашаются, что он должен быть поэлементным, а когда они видят «И», их ожидания разнятся?
Индоминус
16
@Indominus: Сам язык Python требует , чтобы выражение x and yзапускало оценку bool(x)и bool(y). Python «сначала выполняет оценку x; если xложно, возвращается его значение; в противном случае yоценивается и возвращается результирующее значение». Таким образом, синтаксис x and yнельзя использовать для элементарного логического и, поскольку может быть возвращено только xили y. Напротив, x & yтриггеры x.__and__(y)и __and__метод могут быть определены так, чтобы возвращать все, что нам нравится.
unutbu
2
Важно отметить: круглые скобки вокруг ==предложения являются обязательными . a['x']==1 & a['y']==10возвращает ту же ошибку, что и в вопросе.
Alex P. Miller
1
Для чего нужен "|"?
Euler_Salter
82

TL; DR; Логические операторы в пандах &, |и ~, и круглые скобки (...)важны!

Python это and, orи notлогические операторы предназначены для работы с скаляров. Поэтому Pandas пришлось сделать что-то лучше и переопределить побитовые операторы для достижения векторизованной (поэлементной) версии этой функциональности.

Итак, следующее в python ( exp1и exp2это выражения, которые оценивают логический результат) ...

exp1 and exp2              # Logical AND
exp1 or exp2               # Logical OR
not exp1                   # Logical NOT

... переведем на ...

exp1 & exp2                # Element-wise logical AND
exp1 | exp2                # Element-wise logical OR
~exp1                      # Element-wise logical NOT

для панд.

Если в процессе выполнения логической операции вы получите 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 < 3as 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)

Что становится,

# 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_andlogical_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 
# 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.

cs95
источник
@ cs95 в TL; DR, для поэлементного логического ИЛИ вы выступаете за использование |, что эквивалентно numpy.bitwise_or, вместо numpy.logical_or. Могу я спросить, почему? Не numpy.logical_orпредназначен специально для этой задачи? Зачем добавлять бремя побитовой обработки для каждой пары элементов?
flow2k
@flow2k, не могли бы вы процитировать соответствующий текст? Я не могу найти то, о чем вы говорите. FWIW Я утверждаю, что logical_ * - это правильный функциональный эквивалент операторов.
cs95
@ cs95 Я имею в виду первую строку ответа: «TL; DR; логические операторы в Pandas - это &, | и ~».
flow2k
@flow2k В документации буквально написано : «Другой распространенной операцией является использование логических векторов для фильтрации данных. Операторы: | for or, & for and, and ~ for not.»
cs95
@ cs95, хорошо, я только что прочитал этот раздел, и он действительно используется |для поэлементных логических операций. Но для меня эта документация больше похожа на «учебник», и, напротив, я чувствую, что эти ссылки API ближе к источнику истины: numpy.bitwise_or и numpy.logical_or - поэтому я пытаюсь понять, что такое описано здесь.
flow2k
5

Логические операторы для логической индексации в Pandas

Важно понимать, что вы не можете использовать какие-либо логические операторы Python ( and, orили not) для pandas.Seriesor pandas.DataFrames (аналогично вы не можете использовать их для numpy.arrays с более чем одним элементом). Причина, по которой вы не можете их использовать, заключается в том, что они неявно вызывают 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подкласса:

Так, по существу, следует использовать (при условии , df1и df2являются панды DataFrames):

np.logical_and(df1, df2)
np.logical_or(df1, df2)
np.logical_not(df1)
np.logical_xor(df1, df2)

Побитовые функции и побитовые операторы для логических значений

Однако, если у вас есть логический массив NumPy, серия pandas или pandas DataFrames, вы также можете использовать поэлементные побитовые функции (для логических значений они - или, по крайней мере, должны быть - неотличимы от логических функций):

  • побитовое и: np.bitwise_andили &оператор
  • побитовое ИЛИ: np.bitwise_orили |оператор
  • побитовое not: 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 (а также логических серий и DataFrames). Если они не содержат логических значений, тогда операции будут давать разные результаты. Я включу примеры с использованием массивов 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 , серий pandas и DataFrames pandas. Остальные работают с этими структурами данных (и простыми объектами Python) и работают поэлементно. Однако будьте осторожны с побитовым инвертированием на простом Python, boolпотому что bool будет интерпретироваться как целые числа в этом контексте (например, ~Falsereturn -1и ~Truereturn -2).

MSeifert
источник