Operator logis untuk pengindeksan boolean di Pandas

153

Saya bekerja dengan indeks boolean di Pandas. Pertanyaannya adalah mengapa pernyataan itu:

a[(a['some_column']==some_number) & (a['some_other_column']==some_other_number)]

berfungsi dengan baik sedangkan

a[(a['some_column']==some_number) and (a['some_other_column']==some_other_number)]

keluar dengan kesalahan?

Contoh:

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()
pengguna2988577
sumber
6
Ini karena array numpy dan seri panda menggunakan operator bitwise daripada logis karena Anda membandingkan setiap elemen dalam array / seri dengan yang lain. Karena itu tidak masuk akal untuk menggunakan operator logis dalam situasi ini. lihat terkait: stackoverflow.com/questions/8632033/…
EdChum
9
Python and != &. The andoperator dalam Python tidak bisa ditimpa, sedangkan &operator ( __and__) bisa. Karenanya pilihan penggunaan &dalam numpy dan panda.
Steven Rumbalski

Jawaban:

209

Kapan kamu berkata

(a['x']==1) and (a['y']==10)

Anda secara implisit meminta Python untuk mengonversi (a['x']==1)dan (a['y']==10)ke nilai boolean.

Array NumPy (dengan panjang lebih dari 1) dan objek Pandas seperti Seri tidak memiliki nilai boolean - dengan kata lain, mereka meningkatkan

ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().

bila digunakan sebagai nilai boolean. Itu karena tidak jelas kapan harus Benar atau Salah . Beberapa pengguna mungkin menganggap mereka Benar jika panjangnya tidak nol, seperti daftar Python. Orang lain mungkin menginginkannya sebagai Benar hanya jika semua elemennya Benar. Orang lain mungkin ingin itu Benar jika salah satu elemennya Benar.

Karena ada begitu banyak harapan yang saling bertentangan, para perancang NumPy dan Pandas menolak untuk menebak, dan malah menaikkan ValueError.

Sebaliknya, Anda harus eksplisit, dengan memanggil empty(), all()atau any()metode untuk menunjukkan perilaku mana yang Anda inginkan.

Namun dalam kasus ini, sepertinya Anda tidak ingin evaluasi boolean, Anda ingin elemen- logical-and-logical. Itulah yang dilakukan oleh &operator biner:

(a['x']==1) & (a['y']==10)

mengembalikan array boolean.


By the way, seperti catatan alexpmil , tanda kurung wajib karena &memiliki prioritas operator lebih tinggi daripada ==. Tanpa tanda kurung, a['x']==1 & a['y']==10akan dievaluasi seperti a['x'] == (1 & a['y']) == 10yang pada gilirannya akan setara dengan perbandingan rantai (a['x'] == (1 & a['y'])) and ((1 & a['y']) == 10). Itu adalah ekspresi bentuk Series and Series. Penggunaan anddengan dua Seri lagi akan memicu yang sama ValueErrorseperti di atas. Itu sebabnya tanda kurung wajib.

unutbu
sumber
3
array numpy memang memiliki properti ini jika panjangnya satu. Hanya panda devs (keras kepala) yang menolak untuk menebak: p
Andy Hayden
4
Tidakkah '&' membawa kurva ambigu yang sama dengan 'dan'? Bagaimana kalau soal '&', tiba-tiba semua pengguna setuju itu harus elemen-bijaksana, sementara ketika mereka melihat 'dan', harapan mereka bervariasi?
Indominus
16
@Indominus: Bahasa Python itu sendiri mengharuskan ekspresi x and ymemicu evaluasi bool(x)dan bool(y). Python "pertama mengevaluasi x; jika xsalah, nilainya dikembalikan; jika tidak, ydievaluasi dan nilai yang dihasilkan dikembalikan." Jadi sintaksis x and ytidak dapat digunakan untuk elemen-wised logical-dan karena hanya xatau ydapat dikembalikan. Sebaliknya, x & ypemicu x.__and__(y)dan __and__metode dapat didefinisikan untuk mengembalikan apa pun yang kita suka.
unutbu
2
Penting untuk dicatat: tanda kurung di sekitar ==klausa adalah wajib . a['x']==1 & a['y']==10mengembalikan kesalahan yang sama seperti pada pertanyaan.
Alex P. Miller
1
Untuk apa "|"?
Euler_Salter
62

TLDR; Operator logis di Panda adalah &, |dan ~, dan tanda kurung (...)penting!

Operator Python and, ordan notlogis dirancang untuk bekerja dengan skalar. Jadi Pandas harus melakukan yang lebih baik dan mengesampingkan operator bitwise untuk mencapai versi fungsionalitas vektor (elemen-bijaksana).

Jadi berikut ini di python ( exp1danexp2 ekspresi yang mengevaluasi ke hasil boolean) ...

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

... akan diterjemahkan ke ...

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

untuk panda.

Jika dalam proses melakukan operasi logis Anda mendapatkan ValueError, maka Anda harus menggunakan tanda kurung untuk pengelompokan:

(exp1) op (exp2)

Sebagai contoh,

(df['col1'] == x) & (df['col2'] == y) 

Dan seterusnya.


Boolean Indexing : Operasi yang umum adalah menghitung topeng boolean melalui kondisi logis untuk menyaring data. Panda menyediakan tiga operator:&untuk logika AND,|untuk logika OR, dan~ untuk logika TIDAK.

Pertimbangkan pengaturan berikut:

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

Logis dan

Untuk df atas, katakan Anda ingin mengembalikan semua baris di mana A <5 dan B> 5. Ini dilakukan dengan menghitung masker untuk setiap kondisi secara terpisah, dan menggunakan mereka.

&Operator Bitwise Berlebihan
Sebelum melanjutkan, harap perhatikan kutipan khusus dokumen ini, yang menyatakan

Operasi umum lainnya adalah penggunaan vektor boolean untuk menyaring data. Operator adalah: |untuk or, &untuk and, dan ~untuk not. Ini harus dikelompokkan dengan menggunakan tanda kurung , karena secara default Python akan mengevaluasi ekspresi seperti df.A > 2 & df.B < 3seperti df.A > (2 & df.B) < 3, sementara pesanan evaluasi yang diinginkan adalah (df.A > 2) & (df.B < 3).

Jadi, dengan mengingat hal ini, elemen bijaksana logis DAN dapat diimplementasikan dengan operator bitwise &:

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

Dan langkah penyaringan selanjutnya adalah sederhana,

df[(df['A'] < 5) & (df['B'] > 5)]

   A  B  C
1  3  7  9
3  4  7  6

Tanda kurung digunakan untuk mengganti urutan presedensi default dari operator bitwise, yang memiliki prioritas lebih tinggi atas operator kondisional <dan >. Lihat bagian Prioritas Operator di python docs.

Jika Anda tidak menggunakan tanda kurung, ekspresi dievaluasi salah. Misalnya, jika Anda secara tidak sengaja mencoba sesuatu seperti

df['A'] < 5 & df['B'] > 5

Diurai sebagai

df['A'] < (5 & df['B']) > 5

Yang menjadi,

df['A'] < something_you_dont_want > 5

Yang menjadi (lihat dokumen python pada perbandingan operator dirantai ),

(df['A'] < something_you_dont_want) and (something_you_dont_want > 5)

Yang menjadi,

# Both operands are Series...
something_else_you_dont_want1 and something_else_you_dont_want2

Yang melempar

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Jadi, jangan membuat kesalahan itu! 1

Menghindari Pengelompokan Tanda Kurung
Perbaikan sebenarnya cukup sederhana. Sebagian besar operator memiliki metode terikat yang sesuai untuk DataFrames. Jika masing-masing topeng dibangun menggunakan fungsi alih-alih operator bersyarat, Anda tidak perlu lagi mengelompokkan menurut parens untuk menentukan urutan evaluasi:

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

Lihat bagian tentang Perbandingan Fleksibel. . Untuk meringkas, kami punya

╒════╤════════════╤════════════╕
     Operator    Function   
╞════╪════════════╪════════════╡
  0  >           gt         
├────┼────────────┼────────────┤
  1  >=          ge         
├────┼────────────┼────────────┤
  2  <           lt         
├────┼────────────┼────────────┤
  3  <=          le         
├────┼────────────┼────────────┤
  4  ==          eq         
├────┼────────────┼────────────┤
  5  !=          ne         
╘════╧════════════╧════════════╛

Pilihan lain untuk menghindari tanda kurung adalah menggunakan DataFrame.query(atau eval):

df.query('A < 5 and B > 5')

   A  B  C
1  3  7  9
3  4  7  6

Saya telah banyak mendokumentasikan querydan evaldalam Evaluasi Ekspresi Dinamis dalam panda menggunakan pd.eval () .

operator.and_
Memungkinkan Anda untuk melakukan operasi ini secara fungsional. Panggilan internal Series.__and__yang terkait dengan operator bitwise.

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

Anda biasanya tidak membutuhkan ini, tetapi berguna untuk mengetahuinya.

Generalisasi: np.logical_and(dan logical_and.reduce)
Alternatif lain adalah menggunakan np.logical_and, yang juga tidak perlu pengelompokan tanda kurung:

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_andadalah ufunc (Fungsi Universal) , dan sebagian besar ufunc memiliki reducemetode. Ini berarti lebih mudah untuk digeneralisasikan dengan logical_andjika Anda memiliki beberapa topeng ke AND. Misalnya, untuk DAN topeng m1dan m2dan m3dengan &, Anda harus melakukannya

m1 & m2 & m3

Namun, opsi yang lebih mudah adalah

np.logical_and.reduce([m1, m2, m3])

Ini sangat kuat, karena memungkinkan Anda membangun di atasnya dengan logika yang lebih kompleks (misalnya, secara dinamis menghasilkan topeng dalam pemahaman daftar dan menambahkan semuanya):

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 - Saya tahu saya mengomel tentang hal ini, tapi tolong tahan dengan saya. Ini adalah kesalahan pemula yang sangat , sangat umum, dan harus dijelaskan dengan seksama.


Logis atau

Untuk yang di dfatas, katakan Anda ingin mengembalikan semua baris tempat A == 3 atau B == 7.

Bitwise Overloaded |

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

Jika Anda belum, baca juga bagian Logical AND di atas, semua peringatan berlaku di sini.

Atau, operasi ini dapat ditentukan dengan

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_
Panggilan di Series.__or__bawah tenda.

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
Untuk dua kondisi, gunakan 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

Untuk beberapa topeng, gunakan 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

TIDAK logis

Diberi topeng, seperti

mask = pd.Series([True, True, False])

Jika Anda perlu membalikkan setiap nilai boolean (sehingga hasil akhirnya [False, False, True]), maka Anda dapat menggunakan salah satu metode di bawah ini.

Bitwise ~

~mask

0    False
1    False
2     True
dtype: bool

Sekali lagi, ekspresi perlu di kurung.

~(df['A'] == 3)

0     True
1    False
2    False
3     True
4     True
Name: A, dtype: bool

Ini panggilan internal

mask.__invert__()

0    False
1    False
2     True
dtype: bool

Tetapi jangan menggunakannya secara langsung.

operator.inv
Secara internal memanggil __invert__Seri.

operator.inv(mask)

0    False
1    False
2     True
dtype: bool

np.logical_not
Ini adalah varian yang numpy.

np.logical_not(mask)

0    False
1    False
2     True
dtype: bool

Catatan, np.logical_anddapat diganti untuk np.bitwise_and, logical_ordengan bitwise_or, dan logical_notdengan invert.

cs95
sumber
@ cs95 di TLDR, untuk elemen boolean OR, Anda menganjurkan penggunaan |, yang setara dengan numpy.bitwise_or, alih-alih numpy.logical_or. Bolehkah saya bertanya mengapa? Bukankah numpy.logical_ordirancang untuk tugas ini secara khusus? Mengapa menambahkan beban untuk melakukannya secara bitwise untuk setiap pasangan elemen?
flow2k
@ flow2k bisakah Anda mengutip teks yang relevan? Saya tidak dapat menemukan apa yang Anda maksud. FWIW Saya berpendapat bahwa logical_ * adalah fungsional yang setara dengan operator.
cs95
@ cs95 Saya merujuk pada baris pertama dari Jawaban: "TLDR; Operator Logika di Panda adalah &, | dan ~".
flow2k
@ flow2k Secara harfiah dalam dokumentasi : "Operasi umum lainnya adalah penggunaan vektor boolean untuk menyaring data. Operatornya adalah: | untuk atau, & untuk dan, dan ~ untuk tidak."
cs95
@ cs95, ok, saya baru saja membaca bagian ini, dan itu memang digunakan |untuk operasi boolean elemen-bijaksana. Tetapi bagi saya, dokumentasi itu lebih merupakan "tutorial", dan sebaliknya, saya merasa referensi API ini lebih dekat ke sumber kebenaran: numpy.bitwise_or dan numpy.logical_or - jadi saya mencoba memahami apa yang ada dijelaskan di sini.
flow2k
4

Operator logis untuk pengindeksan boolean di Pandas

Sangat penting untuk menyadari bahwa Anda tidak dapat menggunakan salah satu operator logis Python ( and, oratau not) pada pandas.Seriesatau pandas.DataFrames (sama halnya Anda tidak dapat menggunakannya pada numpy.arrays dengan lebih dari satu elemen). Alasan mengapa Anda tidak dapat menggunakan itu adalah karena mereka secara implisit memanggil booloperan mereka yang melempar Pengecualian karena struktur data ini memutuskan bahwa boolean dari sebuah array bersifat ambigu:

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

Saya memang membahas hal ini lebih luas dalam jawaban saya untuk "Nilai kebenaran dari suatu Seri adalah ambigu. Gunakan a.empty, a.bool (), a.item (), a.any () atau a.all ()" Q + A .

Fungsi logis NumPys

Namun NumPy menyediakan unsur-bijaksana setara operasi untuk operator ini sebagai fungsi yang dapat digunakan pada numpy.array, pandas.Series, pandas.DataFrame, atau lainnya (sesuai) numpy.arraysubclass:

Jadi, pada dasarnya, seseorang harus menggunakan (dengan asumsi df1dan df2adalah panda DataFrames):

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

Fungsi bitwise dan operator bitwise untuk boolean

Namun jika Anda memiliki boolean NumPy array, panda Series, atau panda DataFrames Anda juga bisa menggunakan fungsi bitwise elemen-bijaksana (untuk booleans mereka - atau setidaknya harus - tidak dapat dibedakan dari fungsi logis):

Biasanya operator digunakan. Namun ketika dikombinasikan dengan operator pembanding kita harus ingat untuk membungkus perbandingan dalam tanda kurung karena operator bitwise memiliki prioritas lebih tinggi daripada operator pembanding :

(df1 < 10) | (df2 > 10)  # instead of the wrong df1 < 10 | df2 > 10

Ini mungkin menjengkelkan karena operator logis Python memiliki prioritas lebih rendah daripada operator perbandingan sehingga Anda biasanya menulis a < 10 and b > 10(di mana adan bmisalnya bilangan bulat sederhana) dan tidak perlu tanda kurung.

Perbedaan antara operasi logis dan bitwise (pada non-boolean)

Sangat penting untuk menekankan bahwa bit dan operasi logis hanya setara untuk array NumPy boolean (dan boolean Series & DataFrames). Jika ini tidak mengandung boolean maka operasi akan memberikan hasil yang berbeda. Saya akan menyertakan contoh menggunakan array NumPy tetapi hasilnya akan serupa untuk struktur data panda:

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

Dan karena NumPy (dan juga panda) melakukan hal yang berbeda untuk array indeks boolean ( Boolean atau "mask" ) dan indeks integer ( array array ), hasil pengindeksan juga akan berbeda:

>>> 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])

Tabel ringkasan

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              |        ~

Di mana operator logis tidak bekerja untuk array NumPy , Seri panda, dan DataFrames panda. Yang lain bekerja pada struktur data ini (dan objek Python sederhana) dan elemen kerja. Namun hati-hati dengan invert bitwise pada Python bools karena bool akan ditafsirkan sebagai bilangan bulat dalam konteks ini (misalnya ~Falsepengembalian -1dan ~Truepengembalian -2).

MSeifert
sumber