Cara efisien untuk menerapkan beberapa filter ke panda DataFrame atau Seri

148

Saya memiliki skenario di mana pengguna ingin menerapkan beberapa filter ke objek DataFrame atau Seri Pandas. Pada dasarnya, saya ingin rantai secara efisien sekelompok penyaringan (operasi perbandingan) bersama-sama yang ditentukan pada saat dijalankan oleh pengguna.

Filter harus aditif (alias masing-masing yang diterapkan harus mempersempit hasil).

Saat ini saya menggunakan reindex()tetapi ini menciptakan objek baru setiap kali dan menyalin data yang mendasarinya (jika saya memahami dokumentasi dengan benar). Jadi, ini bisa sangat tidak efisien saat memfilter Seri besar atau DataFrame.

Aku berpikir bahwa menggunakan apply(), map()atau sesuatu yang serupa mungkin lebih baik. Aku cukup baru untuk panda meskipun masih mencoba untuk membungkus kepalaku di sekitar segalanya.

TL; DR

Saya ingin mengambil kamus dari formulir berikut dan menerapkan setiap operasi ke objek Seri yang diberikan dan mengembalikan objek Seri yang 'difilter'.

relops = {'>=': [1], '<=': [1]}

Contoh Panjang

Saya akan mulai dengan contoh dari apa yang saya miliki saat ini dan hanya memfilter objek Seri tunggal. Di bawah ini adalah fungsi yang saya gunakan saat ini:

   def apply_relops(series, relops):
        """
        Pass dictionary of relational operators to perform on given series object
        """
        for op, vals in relops.iteritems():
            op_func = ops[op]
            for val in vals:
                filtered = op_func(series, val)
                series = series.reindex(series[filtered])
        return series

Pengguna menyediakan kamus dengan operasi yang ingin mereka lakukan:

>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
   col1  col2
0     0    10
1     1    11
2     2    12

>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1       1
2       2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1       1
Name: col1

Sekali lagi, 'masalah' dengan pendekatan saya di atas adalah bahwa saya pikir ada banyak kemungkinan penyalinan data yang tidak perlu untuk langkah-langkah di antaranya.

Juga, saya ingin memperluas ini sehingga kamus yang disahkan dapat menyertakan kolom untuk operator dan memfilter seluruh DataFrame berdasarkan pada kamus input. Namun, saya mengasumsikan apa pun yang berfungsi untuk Seri dapat dengan mudah diperluas ke DataFrame.

durden2.0
sumber
Juga, saya sepenuhnya menyadari bahwa pendekatan untuk masalah ini mungkin jauh. Jadi mungkin memikirkan kembali seluruh pendekatan akan berguna. Saya hanya ingin memperbolehkan pengguna untuk menentukan serangkaian operasi filter saat runtime dan menjalankannya.
durden2.0
Saya ingin tahu apakah panda dapat melakukan hal-hal yang sama seperti data.table di R: df [col1 <1 ,,] [col2> = 1]
xappppp
df.querydan pd.evalsepertinya cocok untuk kasus penggunaan Anda. Untuk informasi tentang rangkaian pd.eval()fungsi, fitur dan kasingnya , silakan kunjungi Evaluasi Ekspresi Dinamis di panda menggunakan pd.eval () .
cs95

Jawaban:

245

Panda (dan numpy) memungkinkan pengindeksan boolean , yang akan jauh lebih efisien:

In [11]: df.loc[df['col1'] >= 1, 'col1']
Out[11]: 
1    1
2    2
Name: col1

In [12]: df[df['col1'] >= 1]
Out[12]: 
   col1  col2
1     1    11
2     2    12

In [13]: df[(df['col1'] >= 1) & (df['col1'] <=1 )]
Out[13]: 
   col1  col2
1     1    11

Jika Anda ingin menulis fungsi pembantu untuk ini, pertimbangkan sesuatu di sepanjang baris ini:

In [14]: def b(x, col, op, n): 
             return op(x[col],n)

In [15]: def f(x, *b):
             return x[(np.logical_and(*b))]

In [16]: b1 = b(df, 'col1', ge, 1)

In [17]: b2 = b(df, 'col1', le, 1)

In [18]: f(df, b1, b2)
Out[18]: 
   col1  col2
1     1    11

Pembaruan: panda 0.13 memiliki metode kueri untuk jenis kasus penggunaan ini, dengan asumsi nama kolom adalah pengidentifikasi yang valid karya-karya berikut (dan dapat lebih efisien untuk bingkai besar karena menggunakan numexpr di belakang layar):

In [21]: df.query('col1 <= 1 & 1 <= col1')
Out[21]:
   col1  col2
1     1    11
Andy Hayden
sumber
1
Hak Anda, boolean lebih efisien karena tidak membuat salinan data. Namun, skenario saya sedikit lebih rumit daripada contoh Anda. Input yang saya terima adalah kamus yang mendefinisikan filter apa yang harus diterapkan. Contoh saya bisa melakukan sesuatu seperti df[(ge(df['col1'], 1) & le(df['col1'], 1)]. Masalah bagi saya sebenarnya adalah kamus dengan filter bisa berisi banyak operator dan merantai mereka bersama-sama rumit. Mungkin saya bisa menambahkan setiap array boolean perantara ke array besar dan kemudian gunakan saja mapuntuk menerapkan andoperator kepada mereka?
durden2.0
@ durden2.0 Saya telah menambahkan ide untuk fungsi pembantu, yang saya pikir mirip dengan apa yang Anda cari :)
Andy Hayden
Itu terlihat sangat dekat dengan apa yang saya temukan! Terima kasih untuk contohnya. Kenapa f()harus mengambil *balih-alih adil b? Apakah ini agar pengguna f()masih dapat menggunakan outparameter opsional logical_and()? Ini mengarah ke pertanyaan sampingan kecil lainnya. Apa manfaat / trade off dari passing dalam array melalui out()vs. menggunakan yang dikembalikan dari logical_and()? Terima kasih lagi!
durden2.0
Bagaimanapun, saya tidak melihat cukup dekat. Ini *bdiperlukan karena Anda melewati dua array b1dan b2dan Anda perlu membongkar mereka saat menelepon logical_and. Namun, pertanyaan lainnya masih ada. Apakah ada manfaat kinerja untuk melewatkan dalam array melalui outparameter ke logical_and()vs hanya menggunakan nilai baliknya?
durden2.0
2
@dwanderson Anda dapat memberikan daftar kondisi ke np.logical_and.reduce untuk berbagai kondisi. Contoh: np.logical_and.reduce ([df ['a'] == 3, df ['b']> 10, df ['c']. Isin (1,3,5)])
Kuzenbo
39

Kondisi rantai menciptakan garis panjang, yang tidak disarankan oleh pep8. Menggunakan metode .query memaksa untuk menggunakan string, yang kuat tetapi unpythonic dan tidak terlalu dinamis.

Setelah masing-masing filter berada di tempatnya, satu pendekatan adalah

import numpy as np
import functools
def conjunction(*conditions):
    return functools.reduce(np.logical_and, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[conjunction(c1,c2,c3)]

np.logical beroperasi dan cepat, tetapi tidak membutuhkan lebih dari dua argumen, yang ditangani oleh functools.reduce.

Perhatikan bahwa ini masih memiliki beberapa redudansi: a) pintasan tidak terjadi pada tingkat global b) Masing-masing kondisi individu berjalan pada seluruh data awal. Namun, saya berharap ini cukup efisien untuk banyak aplikasi dan sangat mudah dibaca.

Anda juga dapat membuat disjungsi (di mana hanya satu kondisi yang perlu benar) dengan menggunakan np.logical_orsebagai gantinya:

import numpy as np
import functools
def disjunction(*conditions):
    return functools.reduce(np.logical_or, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[disjunction(c1,c2,c3)]
Tokek
sumber
1
Apakah ada cara untuk menerapkan ini untuk sejumlah kondisi? Saya telah mencoba menambahkan masing-masing c_1, c_2, c_3, ... c_ndalam daftar, dan kemudian melewati data[conjunction(conditions_list)]tapi mendapatkan error ValueError: Item wrong length 5 instead of 37.Juga mencoba data[conjunction(*conditions_list)]tetapi saya mendapatkan hasil yang berbeda dari data[conjunction(c_1, c_2, c_3, ... c_n )], tidak yakin apa yang sedang terjadi.
user5359531
Menemukan solusi untuk kesalahan di tempat lain. data[conjunction(*conditions_list)]tidak berfungsi setelah mengemas dataframe ke dalam daftar, dan membongkar daftar di tempat
user5359531
1
Saya baru saja meninggalkan komentar pada jawaban di atas dengan versi yang lebih ceroboh, dan kemudian memperhatikan jawaban Anda. Sangat bersih, saya sangat menyukainya!
dwanderson
Ini jawaban yang bagus!
Charlie Crown
1
Saya telah menggunakan: df[f_2 & f_3 & f_4 & f_5 ]dengan f_2 = df["a"] >= 0dll. Tidak perlu untuk fungsi itu ... (penggunaan fungsi fungsi tingkat tinggi yang bagus ...)
A. Rabus
19

Solusi termudah:

Menggunakan:

filtered_df = df[(df['col1'] >= 1) & (df['col1'] <= 5)]

Contoh lain , Untuk memfilter bingkai data untuk nilai-nilai milik Feb-2018, gunakan kode di bawah ini

filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]
Gil Baggio
sumber
saya menggunakan variabel bukan konstan. mendapatkan kesalahan. df [df []] [df []] memberikan pesan peringatan tetapi memberikan jawaban yang benar.
Nguai al
8

Sejak panda pembaruan 0,22 , opsi perbandingan tersedia seperti:

  • gt (lebih besar dari)
  • itu (kurang dari)
  • eq (sama dengan)
  • ne (tidak sama dengan)
  • ge (lebih besar atau sama dengan)

dan masih banyak lagi. Fungsi-fungsi ini mengembalikan array boolean. Mari kita lihat bagaimana kita dapat menggunakannya:

# sample data
df = pd.DataFrame({'col1': [0, 1, 2,3,4,5], 'col2': [10, 11, 12,13,14,15]})

# get values from col1 greater than or equals to 1
df.loc[df['col1'].ge(1),'col1']

1    1
2    2
3    3
4    4
5    5

# where co11 values is better 0 and 2
df.loc[df['col1'].between(0,2)]

 col1 col2
0   0   10
1   1   11
2   2   12

# where col1 > 1
df.loc[df['col1'].gt(1)]

 col1 col2
2   2   12
3   3   13
4   4   14
5   5   15
YOLO
sumber
2

Kenapa tidak melakukan ini?

def filt_spec(df, col, val, op):
    import operator
    ops = {'eq': operator.eq, 'neq': operator.ne, 'gt': operator.gt, 'ge': operator.ge, 'lt': operator.lt, 'le': operator.le}
    return df[ops[op](df[col], val)]
pandas.DataFrame.filt_spec = filt_spec

Demo:

df = pd.DataFrame({'a': [1,2,3,4,5], 'b':[5,4,3,2,1]})
df.filt_spec('a', 2, 'ge')

Hasil:

   a  b
 1  2  4
 2  3  3
 3  4  2
 4  5  1

Anda dapat melihat bahwa kolom 'a' telah difilter di mana a> = 2.

Ini sedikit lebih cepat (waktu mengetik, bukan kinerja) daripada rantai operator. Anda tentu saja dapat menempatkan impor di bagian atas file.

Obol
sumber
1

e juga dapat memilih baris berdasarkan nilai-nilai kolom yang tidak ada dalam daftar atau iterable. Kami akan membuat variabel boolean seperti sebelumnya, tetapi sekarang kami akan meniadakan variabel boolean dengan menempatkan ~ di depan.

Sebagai contoh

list = [1, 0]
df[df.col1.isin(list)]
Ram Prajapati
sumber