Логічні оператори для булевої індексації в Pandas


154

Я працюю з булевим індексом у 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()

6
Це тому, що numpy масиви та серії pandas використовують побітові оператори, а не логічні, оскільки ви порівнюєте кожен елемент масиву / серії з іншим. Тому не має сенсу використовувати логічного оператора в цій ситуації. см , пов'язані: stackoverflow.com/questions/8632033 / ...
EdChum

9
У Python and != &. andОператор в Python не може бути перевизначений, в той час як &оператор ( __and__) може. Звідси вибір використання &в нуме і пандах.
Стівен Румбальський

Відповіді:


209

Коли ти кажеш

(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. Інші можуть бажати, щоб це було Істинним, лише якщо всі його елементи є Істинними. Інші, можливо, хочуть, щоб це було істиною, якщо будь-який з його елементів є істинним.

Оскільки існує стільки суперечливих очікувань, дизайнери 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, що вище. Ось чому дужки є обов’язковими.


3
numpy масиви мають це властивість, якщо вони є довжиною одиницею . Лише панди (вперто) відмовляються здогадуватися: p
Енді Хайден

4
Чи "" не має таку ж неоднозначну криву, як "і"? Як прийти, коли мова йде про "&", раптом всі користувачі погоджуються, що це має бути елементарно, а коли вони бачать "і", їхні очікування змінюються?
Indominus

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повертає ту саму помилку, що й у питанні.
Алекс П. Міллер

1
Для чого "|"?
Euler_Salter

62

TLDR; Логічні оператори в Pandas є &, |і ~, і круглі дужки (...)є важливими!

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 пропонує три оператори:&для логічного AND,|для логічного АБО та~для логічного NOT.

Розглянемо наступне налаштування:

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вище, скажімо , ви хочете , щоб повернути всі рядки , де А <5 і B> 5. Це робиться шляхом обчислення маски для кожного стану окремо, і Андінг їх.

Перевантажений &оператор бітового
режиму Перш ніж продовжувати, будь ласка, зверніть увагу на цей конкретний уривок документів, які містять стан

Ще одна поширена операція - використання булевих векторів для фільтрації даних. Оператори: |для or, &для andі ~для not. Вони повинні бути згруповані за допомогою круглих дужок , оскільки за замовчуванням Python оцінить такий вираз, як, df.A > 2 & df.B < 3в df.A > (2 & df.B) < 3той час, як бажаний порядок оцінки (df.A > 2) & (df.B < 3).

Отже, маючи це на увазі, елемент розумний логічний AND може бути реалізований за допомогою розрядного оператора &:

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. Наприклад, до масок 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
Це нудний варіант.

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 в TLDR, для елементарного булевого АБО, ви рекомендуєте використовувати |, що еквівалентно numpy.bitwise_orзамість numpy.logical_or. Чи можу я запитати, чому? Не numpy.logical_orрозроблено спеціально для цього завдання? Навіщо додавати тягар, як це робити побіжно для кожної пари елементів?
flow2k

@ flow2k чи можете ви цитувати відповідний текст будь-ласка? Я не можу знайти те, про що ти маєш на увазі. FWIW Я стверджую, що логічний_ * є правильним функціональним еквівалентом операторів.
cs95

@ cs95 Я маю на увазі перший рядок відповіді: "TLDR; Логічні оператори в пандах є &, | і ~".
flow2k

@ flow2k Буквально в документації : "Інша поширена операція - це використання булевих векторів для фільтрації даних. Операторами є: | для або, & для і, і ~ для не".
cs95

@ cs95, добре, я просто прочитав цей розділ, і він використовує |для елементарних булевих операцій. Але для мене ця документація більше "підручник", і навпаки, я вважаю, що ці посилання API ближче до джерела істини: numpy.bitwise_or та numpy.logical_or - тому я намагаюся зрозуміти, що таке описані тут.
flow2k

4

Логічні оператори для булевої індексації в Pandas

Важливо розуміти , що ви не можете використовувати будь-який з Python логічних операторів ( and, orабо not) на pandas.Seriesабо pandas.DataFrameз ( так само ви не можете використовувати їх на 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підкласу:

Отже, по суті, слід використовувати (якщо припустити df1і df2є пандами DataFrames):

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

Побітові функції та побітові оператори для булів

Однак у випадку, якщо у вас є логічний масив NumPy, серії pandas або panda 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 (та булевих Series & DataFrames). Якщо вони не містять булевих, операції дадуть різні результати. Я включу приклади, що використовують масиви NumPy, але результати будуть аналогічні структурам даних панди:

>>> 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 (і аналогічно панди) робить різні речі для булевих ( булевих або "маскових" індексних масивів ) та цілих ( індексних масивів ), результати індексації також будуть різними:

>>> 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 pandas і панд DataFrames. Інші працюють над цими структурами даних (і звичайними об’єктами Python) і працюють впорядковано. Однак будьте обережні з побітною інверсією на звичайному Python bools, оскільки bool буде інтерпретуватися як цілі числа в цьому контексті (наприклад, ~Falseповернення -1та ~Trueповернення -2).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.