วิธีที่มีประสิทธิภาพในการใช้ตัวกรองหลายตัวกับแพนด้า DataFrame หรือซีรี่ส์


148

ฉันมีสถานการณ์ที่ผู้ใช้ต้องการใช้ตัวกรองหลายตัวกับวัตถุ Pandas DataFrame หรือชุดข้อมูล โดยพื้นฐานแล้วฉันต้องการโยงกลุ่มการกรอง (การดำเนินการเปรียบเทียบ) เข้าด้วยกันอย่างมีประสิทธิภาพซึ่งระบุไว้ ณ เวลาใช้งานโดยผู้ใช้

ตัวกรองควรเป็นสารเติมแต่ง (รู้จักกันว่าตัวกรองแต่ละตัวที่ใช้ควรทำให้ผลลัพธ์แคบลง)

ขณะนี้ฉันกำลังใช้reindex()แต่สิ่งนี้สร้างวัตถุใหม่ทุกครั้งและคัดลอกข้อมูลพื้นฐาน (ถ้าฉันเข้าใจเอกสารอย่างถูกต้อง) ดังนั้นสิ่งนี้อาจไม่มีประสิทธิภาพจริง ๆ เมื่อกรองชุดข้อมูลขนาดใหญ่หรือ DataFrame

ฉันคิดว่าการใช้apply(), map()หรือสิ่งที่คล้ายกันอาจจะดีกว่า ฉันค่อนข้างใหม่กับนุ่น แต่ก็ยังพยายามห่อหัวของฉันกับทุกสิ่ง

TL; DR

ฉันต้องการใช้พจนานุกรมของแบบฟอร์มต่อไปนี้และใช้การดำเนินการแต่ละอย่างกับวัตถุ Series ที่กำหนดและส่งคืนวัตถุซีรี่ส์ 'กรอง'

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

ตัวอย่างที่ยาวนาน

ฉันจะเริ่มต้นด้วยตัวอย่างของสิ่งที่ฉันมีในปัจจุบันและเพียงแค่กรองวัตถุชุดเดียว ด้านล่างเป็นฟังก์ชั่นที่ฉันใช้อยู่:

   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

ผู้ใช้จัดเตรียมพจนานุกรมที่มีการดำเนินงานที่ต้องการดำเนินการ:

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

อีกครั้ง 'ปัญหา' ด้วยวิธีการข้างต้นของฉันคือฉันคิดว่ามีจำนวนมากอาจคัดลอกข้อมูลที่ไม่จำเป็นสำหรับขั้นตอนในระหว่าง

นอกจากนี้ฉันต้องการขยายสิ่งนี้เพื่อให้พจนานุกรมที่ส่งผ่านสามารถรวมคอลัมน์ไปยังผู้ดำเนินการและกรอง DataFrame ทั้งหมดตามพจนานุกรมการป้อนข้อมูล อย่างไรก็ตามฉันสมมติว่าอะไรก็ตามที่ใช้ได้กับซีรีย์นั้นสามารถขยายไปยัง DataFrame ได้อย่างง่ายดาย


นอกจากนี้ฉันรู้อย่างเต็มที่ว่าวิธีการแก้ไขปัญหานี้อาจไม่ได้ผล ดังนั้นการทบทวนวิธีการทั้งหมดอาจเป็นประโยชน์ ฉันแค่ต้องการอนุญาตให้ผู้ใช้ระบุชุดของการดำเนินการตัวกรองที่รันไทม์และดำเนินการพวกเขา
durden2.0

ฉันสงสัยว่านุ่นสามารถทำสิ่งที่คล้ายกันเป็น data.table ใน R: df [col1 <1 ,,] [col2> = 1]
xappppp

df.queryและpd.evalดูเหมือนจะเหมาะสำหรับกรณีการใช้งานของคุณ สำหรับข้อมูลเกี่ยวกับpd.eval()ครอบครัวของฟังก์ชั่นคุณสมบัติของพวกเขาและกรณีการใช้งานกรุณาเยี่ยมแบบไดนามิกการแสดงออกในการประเมินผลโดยใช้หมีแพนด้า pd.eval ()
cs95

คำตอบ:


245

Pandas (และจำนวนมาก) อนุญาตให้ทำดัชนีบูลีนซึ่งจะมีประสิทธิภาพมากขึ้น:

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

หากคุณต้องการเขียนฟังก์ชันตัวช่วยสำหรับสิ่งนี้ให้พิจารณาบางสิ่งตามบรรทัดเหล่านี้:

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

อัปเดต: pandas 0.13 มีวิธีการสืบค้นสำหรับกรณีการใช้งานประเภทนี้โดยสมมติว่าชื่อคอลัมน์เป็นตัวระบุที่ถูกต้องในงานต่อไปนี้ (และอาจมีประสิทธิภาพมากกว่าสำหรับเฟรมขนาดใหญ่เนื่องจากใช้numexpr ที่อยู่เบื้องหลัง):

In [21]: df.query('col1 <= 1 & 1 <= col1')
Out[21]:
   col1  col2
1     1    11

1
บูลีนที่ถูกต้องของคุณมีประสิทธิภาพมากขึ้นเนื่องจากไม่ได้คัดลอกข้อมูล อย่างไรก็ตามสถานการณ์ของฉันค่อนข้างยุ่งยากกว่าตัวอย่างของคุณเล็กน้อย อินพุตที่ฉันได้รับคือพจนานุกรมที่กำหนดว่าจะใช้ตัวกรองใด df[(ge(df['col1'], 1) & le(df['col1'], 1)]ตัวอย่างของฉันสามารถทำสิ่งที่ชอบ ปัญหาสำหรับฉันจริงๆคือพจนานุกรมที่มีตัวกรองอาจมีตัวดำเนินการจำนวนมากและการผูกมัดพวกเขาไว้ด้วยกันนั้นยุ่งยาก บางทีฉันสามารถเพิ่มบูลีนระดับกลางแต่ละอันลงในอาเรย์ขนาดใหญ่แล้วใช้mapเพื่อนำandโอเปอเรเตอร์ไปใช้กับพวกมันได้ไหม
durden2.0

@ durden2.0 ฉันได้เพิ่มความคิดสำหรับฟังก์ชั่นตัวช่วยซึ่งฉันคิดว่าคล้ายกับสิ่งที่คุณกำลังมองหา :)
Andy Hayden

นั่นดูใกล้เคียงกับสิ่งที่ฉันคิดไว้! ขอบคุณสำหรับตัวอย่าง ทำไมf()ต้องใช้*bแทนที่จะเป็นเพียงแค่b? ผู้ใช้f()นี้ยังคงสามารถใช้outพารามิเตอร์เผื่อเลือกได้logical_and()หรือไม่ นี่นำไปสู่คำถามเล็ก ๆ อีกข้อหนึ่ง ประสิทธิภาพการทำงานที่ได้รับประโยชน์ / การแลกเปลี่ยนจากการผ่านในอาร์เรย์ผ่านout()เทียบกับการใช้หนึ่งกลับมาจากlogical_and()อะไร? ขอบคุณอีกครั้ง!
durden2.0

ไม่เป็นไรฉันไม่ได้ดูใกล้พอ *bเป็นสิ่งจำเป็นเพราะคุณกำลังผ่านสองอาร์เรย์b1และและคุณจำเป็นต้องแกะพวกเขาเมื่อเรียกb2 logical_andอย่างไรก็ตามคำถามอื่น ๆ ยังคงยืนอยู่ มีประโยชน์ประสิทธิภาพการทำงานในการส่งผ่านอาร์เรย์ผ่านoutพารามิเตอร์เพื่อlogical_and()vs เพียงแค่ใช้ 'คืนค่า?
durden2.0

2
@dwanderson คุณสามารถส่งรายการเงื่อนไขไปยัง np.logical_and.reduce สำหรับหลายเงื่อนไข ตัวอย่าง: np.logical_and.reduce ([df ['a'] == 3, df ['b']> 10, df ['c']. isin (1,3,5)])
Kuzenbo

39

เงื่อนไขการโยงสร้างสายยาวซึ่งไม่สนับสนุนโดย pep8 การใช้วิธีการสืบค้นคำถามบังคับให้ใช้สตริงซึ่งมีประสิทธิภาพ แต่ไม่มีเสียงไพเราะและไม่ไดนามิกมาก

เมื่อตัวกรองแต่ละตัวอยู่ในตำแหน่งหนึ่งวิธีคือ

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 ทำงานและรวดเร็ว แต่ไม่ใช้เวลามากกว่าสองข้อโต้แย้งซึ่งจัดการโดย functools.reduce

โปรดทราบว่าสิ่งนี้ยังคงมีความซ้ำซ้อนบางประการ: a) การทางลัดไม่ได้เกิดขึ้นในระดับสากล b) แต่ละเงื่อนไขทำงานบนข้อมูลเริ่มต้นทั้งหมด ถึงกระนั้นฉันก็คาดหวังว่ามันจะมีประสิทธิภาพเพียงพอสำหรับแอพพลิเคชั่นมากมายและสามารถอ่านได้มาก

นอกจากนี้คุณยังสามารถแยกความแตกต่าง (ในเงื่อนไขข้อใดข้อหนึ่งจำเป็นต้องเป็นจริง) โดยใช้np.logical_orแทน:

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

1
มีวิธีที่จะใช้สิ่งนี้สำหรับเงื่อนไขจำนวนตัวแปรหรือไม่? ฉันได้พยายามผนวกแต่ละc_1, c_2, c_3... c_nในรายการแล้วผ่านdata[conjunction(conditions_list)]แต่รับข้อผิดพลาดValueError: Item wrong length 5 instead of 37.พยายามยังdata[conjunction(*conditions_list)]แต่ฉันได้รับผลที่แตกต่างกันกว่าdata[conjunction(c_1, c_2, c_3, ... c_n )], ไม่แน่ใจว่าสิ่งที่เกิดขึ้น
user5359531

พบวิธีแก้ไขข้อผิดพลาดที่อื่น data[conjunction(*conditions_list)]ทำงานได้หลังจากการบรรจุดาต้าไฟล์ลงในรายการและเอารายการออกให้
เรียบร้อย

1
ฉันเพิ่งแสดงความคิดเห็นในคำตอบข้างต้นด้วยเวอร์ชันที่ลื่นไหลมากขึ้นจากนั้นสังเกตคำตอบของคุณ สะอาดมากฉันชอบมันมาก!
dwanderson

นี่เป็นคำตอบที่ยอดเยี่ยม!
Charlie Crown

1
ฉันใช้: df[f_2 & f_3 & f_4 & f_5 ]กับf_2 = df["a"] >= 0ฯลฯ ไม่จำเป็นต้องใช้ฟังก์ชั่นนั้น ... (ใช้ฟังก์ชั่นการสั่งซื้อที่สูงขึ้น ... )
A. Rabus

19

ง่ายที่สุดของโซลูชันทั้งหมด:

ใช้:

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

อีกตัวอย่างหนึ่งในการกรองดาต้าเฟรมสำหรับค่าที่เป็นของ Feb-2018 ให้ใช้รหัสด้านล่าง

filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]

ฉันใช้ตัวแปรแทนค่าคงที่ รับข้อผิดพลาด df [df []] [df []] แสดงข้อความเตือน แต่ให้คำตอบที่ถูกต้อง
al

8

ตั้งแต่การอัพเดตนุ่น 0.22ตัวเลือกการเปรียบเทียบมีอยู่เช่น:

  • gt (มากกว่า)
  • lt (น้อยกว่า)
  • eq (เท่ากับ)
  • ne (ไม่เท่ากับ)
  • ge (มากกว่าหรือเท่ากับ)

และอื่น ๆ อีกมากมาย. ฟังก์ชันเหล่านี้ส่งคืนอาร์เรย์บูลีน มาดูกันว่าเราจะใช้มันอย่างไร:

# 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

2

ทำไมไม่ทำเช่นนี้?

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

การสาธิต:

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

ผลลัพธ์:

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

คุณจะเห็นว่าคอลัมน์ 'a' ถูกกรองโดยที่> = 2

นี่คือเร็วกว่าเล็กน้อย (เวลาพิมพ์ไม่ได้ประสิทธิภาพ) กว่าผู้ประกอบการที่ผูกมัด แน่นอนคุณสามารถนำเข้าที่ด้านบนของไฟล์


1

e ยังสามารถเลือกแถวตามค่าของคอลัมน์ที่ไม่อยู่ในรายการหรือทำซ้ำได้ เราจะสร้างตัวแปรบูลีนเหมือนเมื่อก่อน แต่ตอนนี้เราจะลบล้างตัวแปรบูลีนด้วยการวาง ~ ไว้ด้านหน้า

ตัวอย่างเช่น

list = [1, 0]
df[df.col1.isin(list)]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.