panda: filter baris DataFrame dengan operator chaining


329

Kebanyakan operasi di pandasdapat dicapai dengan operator chaining ( groupby, aggregate, apply, dll), tetapi satu-satunya cara saya telah menemukan untuk baris filter melalui braket pengindeksan yang normal

df_filtered = df[df['column'] == value]

Ini tidak menarik karena mengharuskan saya menugaskan dfke variabel sebelum dapat menyaring nilai-nilainya. Apakah ada yang lebih seperti yang berikut ini?

df_filtered = df.mask(lambda x: x['column'] == value)

df.querydan pd.evalsepertinya cocok untuk kasus penggunaan ini. Untuk informasi tentang rangkaian pd.eval()fungsi, fitur dan kasingnya , silakan kunjungi Evaluasi Ekspresi Dinamis di panda menggunakan pd.eval () .
cs95

Jawaban:


384

Saya tidak sepenuhnya yakin apa yang Anda inginkan, dan baris kode terakhir Anda juga tidak membantu, tetapi:

Penyaringan "dirantai" dilakukan dengan "merantai" kriteria dalam indeks boolean.

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Jika Anda ingin metode berantai, Anda dapat menambahkan metode topeng Anda sendiri dan menggunakannya.

In [90]: def mask(df, key, value):
   ....:     return df[df[key] == value]
   ....:

In [92]: pandas.DataFrame.mask = mask

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))

In [95]: df.ix['d','A'] = df.ix['a', 'A']

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [97]: df.mask('A', 1)
Out[97]:
   A  B  C  D
a  1  4  9  1
d  1  3  9  6

In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
   A  B  C  D
d  1  3  9  6

2
Jawaban bagus! Jadi (df.A == 1) & (df.D == 6), apakah "&" operator kelebihan beban di Pandas?
Shawn


Itu adalah solusi yang sangat bagus - saya bahkan tidak menyadari bahwa Anda dapat menggunakan metode juri-rig seperti itu dengan python. Fungsi seperti ini akan sangat bagus untuk dimiliki di Panda itu sendiri.
naught101

Satu-satunya masalah yang saya miliki dengan ini adalah penggunaan pandas.. Kamu harus import pandas as pd.
Daisuke Aramaki

3
Memang import pandas as pdsudah biasa dilakukan sekarang. Saya ragu ketika saya menjawab pertanyaan itu.
Wouter Overmeire

108

Filter dapat dirantai menggunakan kueri Pandas :

df = pd.DataFrame(np.random.randn(30, 3), columns=['a','b','c'])
df_filtered = df.query('a > 0').query('0 < b < 2')

Filter juga dapat digabungkan dalam satu permintaan:

df_filtered = df.query('a > 0 and 0 < b < 2')

3
Jika Anda perlu merujuk ke variabel python dalam kueri Anda, dokumentasi mengatakan, "Anda bisa merujuk ke variabel di lingkungan dengan mengawali mereka dengan karakter '@' seperti @a + b". Perhatikan bahwa yang berikut ini valid: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
user3780389

2
Di sisi lain, sepertinya evaluasi kueri akan gagal jika nama kolom Anda memiliki karakter khusus tertentu: mis. "Place.Name".
user3780389

2
Chaining adalah tujuan permintaan.
piRSquared

66

Jawaban dari @lodagro sangat bagus. Saya akan memperluasnya dengan menggeneralisasi fungsi mask sebagai:

def mask(df, f):
  return df[f(df)]

Maka Anda dapat melakukan hal-hal seperti:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)

8
Generalisasi yang bermanfaat! Saya berharap sudah terintegrasi langsung ke DataFrames!
duckworthd

24

Sejak versi 0.18.1 yang .locmetode menerima callable untuk seleksi. Bersama dengan fungsi lambda Anda dapat membuat filter rantai yang sangat fleksibel:

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80]  # equivalent to df[df.A == 80] but chainable

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]

Jika semua yang Anda lakukan adalah memfilter, Anda juga dapat menghilangkan .loc.


16

Saya menawarkan ini untuk contoh tambahan. Ini adalah jawaban yang sama dengan https://stackoverflow.com/a/28159296/

Saya akan menambahkan suntingan lain untuk menjadikan tulisan ini lebih bermanfaat.

pandas.DataFrame.query
querydibuat untuk tujuan ini. Pertimbangkan kerangka datadf

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)),
    columns=list('ABCDE')
)

df

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
6  8  7  6  4  7
7  6  2  6  6  5
8  2  8  7  5  8
9  4  7  6  1  5

Mari kita gunakan queryuntuk memfilter semua baris di manaD > B

df.query('D > B')

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Yang kami rantai

df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Bukankah ini pada dasarnya jawaban yang sama dengan stackoverflow.com/a/28159296 Apakah ada sesuatu yang hilang dari jawaban itu yang menurut Anda harus diklarifikasi?
bscan

9

Saya memiliki pertanyaan yang sama kecuali bahwa saya ingin menggabungkan kriteria menjadi kondisi ATAU. Format yang diberikan oleh Wouter Overmeire menggabungkan kriteria ke dalam kondisi AND sehingga keduanya harus dipenuhi:

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Tetapi saya menemukan bahwa, jika Anda membungkus setiap kondisi (... == True)dan bergabung dengan kriteria dengan pipa, kriteria tersebut digabungkan dalam kondisi OR, terpuaskan setiap kali salah satu dari itu benar:

df[((df.A==1) == True) | ((df.D==6) == True)]

12
Tidakkah df[(df.A==1) | (df.D==6)]cukup untuk apa yang ingin Anda capai?
eenblam

Tidak, itu tidak akan karena memberikan hasil bersih (Benar vs Salah) alih-alih seperti di atas yang memfilter semua data yang memenuhi syarat. Harapan saya jelaskan.
MGB.py

8

panda memberikan dua alternatif jawaban Wouter Overmeire yang tidak memerlukan penggantian. Satu .loc[.]dengan callable, seperti pada

df_filtered = df.loc[lambda x: x['column'] == value]

yang lainnya adalah .pipe(), seperti dalam

df_filtered = df.pipe(lambda x: x['column'] == value)

7

Jawaban saya mirip dengan yang lain. Jika Anda tidak ingin membuat fungsi baru, Anda dapat menggunakan apa yang telah ditetapkan panda untuk Anda. Gunakan metode pipa.

df.pipe(lambda d: d[d['column'] == value])

INI adalah apa yang Anda inginkan jika Anda ingin mengaitkan perintah sepertia.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
displayname

4

Jika Anda ingin menerapkan semua topeng boolean umum serta topeng tujuan umum, Anda dapat membuang yang berikut dalam file dan kemudian menetapkan semuanya sebagai berikut:

pd.DataFrame = apply_masks()

Pemakaian:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary

Agak sedikit macet tetapi bisa membuat hal-hal sedikit lebih bersih jika Anda terus-menerus memotong dan mengubah kumpulan data sesuai dengan filter. Ada juga filter tujuan umum yang diadaptasi dari Daniel Velkov di atas dalam fungsi gen_mask yang dapat Anda gunakan dengan fungsi lambda atau sebaliknya jika diinginkan.

File yang akan disimpan (saya menggunakan masks.py):

import pandas as pd

def eq_mask(df, key, value):
    return df[df[key] == value]

def ge_mask(df, key, value):
    return df[df[key] >= value]

def gt_mask(df, key, value):
    return df[df[key] > value]

def le_mask(df, key, value):
    return df[df[key] <= value]

def lt_mask(df, key, value):
    return df[df[key] < value]

def ne_mask(df, key, value):
    return df[df[key] != value]

def gen_mask(df, f):
    return df[f(df)]

def apply_masks():

    pd.DataFrame.eq_mask = eq_mask
    pd.DataFrame.ge_mask = ge_mask
    pd.DataFrame.gt_mask = gt_mask
    pd.DataFrame.le_mask = le_mask
    pd.DataFrame.lt_mask = lt_mask
    pd.DataFrame.ne_mask = ne_mask
    pd.DataFrame.gen_mask = gen_mask

    return pd.DataFrame

if __name__ == '__main__':
    pass

3

Solusi ini lebih bersifat meretas dalam hal implementasi, tetapi saya merasa jauh lebih bersih dalam hal penggunaan, dan tentu saja lebih umum daripada yang lain yang diusulkan.

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

Anda tidak perlu mengunduh seluruh repo: menyimpan file dan melakukan

from where import where as W

harus cukup. Maka Anda menggunakannya seperti ini:

df = pd.DataFrame([[1, 2, True],
                   [3, 4, False], 
                   [5, 7, True]],
                  index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])

Contoh penggunaan yang sedikit kurang bodoh:

data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]

Omong-omong: bahkan dalam kasus di mana Anda hanya menggunakan boolean cols,

df.loc[W['cond1']].loc[W['cond2']]

dapat jauh lebih efisien daripada

df.loc[W['cond1'] & W['cond2']]

karena mengevaluasi cond2hanya mana cond1adalah True.

DISCLAIMER: Saya pertama kali memberikan jawaban ini di tempat lain karena saya belum melihat ini.


2

Hanya ingin menambahkan demonstrasi menggunakan loc untuk menyaring tidak hanya oleh baris tetapi juga oleh kolom dan beberapa manfaat untuk operasi berantai.

Kode di bawah ini dapat memfilter baris berdasarkan nilai.

df_filtered = df.loc[df['column'] == value]

Dengan memodifikasinya sedikit, Anda dapat memfilter kolom juga.

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

Jadi mengapa kita menginginkan metode yang dirantai? Jawabannya adalah mudah dibaca jika Anda memiliki banyak operasi. Sebagai contoh,

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)

2

Ini tidak menarik karena mengharuskan saya menugaskan dfke variabel sebelum dapat menyaring nilai-nilainya.

df[df["column_name"] != 5].groupby("other_column_name")

tampaknya berfungsi: Anda dapat membuat sarang []operator juga. Mungkin mereka menambahkannya sejak Anda mengajukan pertanyaan.


1
Ini tidak masuk akal dalam suatu rantai karena dfsekarang tidak perlu merujuk output dari bagian rantai sebelumnya.
Daan Luttik

@DaanLuttik: setuju, itu bukan chaining, tapi bersarang. Lebih baik untukmu?
serv-inc

1

Jika Anda mengatur kolom Anda untuk mencari sebagai indeks, maka Anda dapat menggunakan DataFrame.xs()untuk mengambil bagian silang. Ini tidak serbaguna seperti queryjawabannya, tetapi mungkin berguna dalam beberapa situasi.

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)),
    columns=list('ABCDE')
)

df
# Out[55]: 
#    A  B  C  D  E
# 0  0  2  2  2  2
# 1  1  1  2  0  2
# 2  0  2  0  0  2
# 3  0  2  2  0  1
# 4  0  1  1  2  0
# 5  0  0  0  1  2
# 6  1  0  1  1  1
# 7  0  0  2  0  2
# 8  2  2  2  2  2
# 9  1  2  0  2  1

df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]: 
#    A  D  B  C  E
# 0  0  2  2  2  2
# 1  0  2  1  1  0

1

Anda juga dapat memanfaatkan perpustakaan numpy untuk operasi logis. Cukup cepat.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.