pandas: กรองแถวของ DataFrame ด้วยการโยงผู้ปฏิบัติงาน


329

การดำเนินงานมากที่สุดในpandasสามารถทำได้กับผู้ประกอบการผูกมัด ( groupby, aggregate, applyฯลฯ ) แต่วิธีเดียวที่ฉันได้พบกับแถวที่กรองผ่านการจัดทำดัชนีวงเล็บปกติ

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

สิ่งนี้ไม่น่าสนใจเนื่องจากฉันต้องกำหนดให้dfกับตัวแปรก่อนที่จะสามารถกรองค่าได้ มีอะไรมากกว่านี้อีกไหม?

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

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

คำตอบ:


384

ฉันไม่แน่ใจว่าสิ่งที่คุณต้องการอย่างแท้จริงและบรรทัดสุดท้ายของรหัสก็ไม่ได้ช่วยอะไร แต่:

การกรอง "ถูกผูกมัด" จะกระทำโดย "การผูกมัด" เกณฑ์ในดัชนีบูลีน

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

หากคุณต้องการวิธีการเชื่อมโยงคุณสามารถเพิ่มวิธีการมาสก์ของคุณเองและใช้วิธีการนั้น

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
คำตอบที่ดี! ดังนั้นใน(df.A == 1) & (df.D == 6)"&" โอเปอเรเตอร์ที่โอเวอร์โหลดใน Pandas คืออะไร
Shawn


นั่นเป็นทางออกที่ดีจริงๆ - ฉันไม่รู้ด้วยซ้ำว่าคุณสามารถใช้วิธีการตัดสินแบบลูกขุนได้ ฟังก์ชั่นแบบนี้จะดีมากที่มีใน Pandas
naught101

pandas.ปัญหาเดียวที่ฉันมีกับเรื่องนี้คือการใช้ import pandas as pdคุณควร
ไดสุเกะอารามากิ

3
แท้จริงแล้วimport pandas as pdคือการปฏิบัติทั่วไปในขณะนี้ ฉันสงสัยว่าเมื่อฉันตอบคำถาม
Wouter Overmeire

108

ตัวกรองสามารถถูกผูกมัดโดยใช้แบบสอบถาม Pandas :

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

ตัวกรองสามารถรวมกันในการสืบค้นเดียว:

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

3
หากคุณต้องการอ้างถึงตัวแปรหลามในแบบสอบถามของคุณเอกสารกล่าวว่า "คุณสามารถอ้างถึงตัวแปรในสภาพแวดล้อมได้โดยนำหน้าด้วยอักขระ '@' เช่น @a + b" df.query('a in list([1,2])')โปรดทราบว่าต่อไปนี้จะถูกต้อง: s = set([1,2]); df.query('a in @s'),
user3780389

2
ในทางกลับกันดูเหมือนว่าการประเมินแบบสอบถามจะล้มเหลวหากชื่อคอลัมน์ของคุณมีอักขระพิเศษบางอย่างเช่น "Place.Name"
user3780389

2
การผูกมัดคือแบบสอบถามที่ถูกออกแบบมาเพื่ออะไร
piRSquared

66

คำตอบจาก @lodagro นั้นยอดเยี่ยม ฉันจะขยายมันโดย generalizing ฟังก์ชั่นหน้ากากเป็น:

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

จากนั้นคุณสามารถทำสิ่งต่าง ๆ เช่น:

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

8
ลักษณะทั่วไปที่มีประโยชน์! ฉันหวังว่ามันจะรวมเข้ากับDataFrames โดยตรงแล้ว!
duckworthd

24

ตั้งแต่รุ่น 0.18.1.locวิธีการรับ callable สำหรับการเลือก เมื่อใช้ร่วมกับฟังก์ชั่นแลมบ์ดาคุณสามารถสร้างตัวกรองเชนที่ยืดหยุ่นได้มาก:

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]

.locหากสิ่งที่คุณกำลังทำคือการกรองคุณยังสามารถละเว้น


16

ฉันเสนอสิ่งนี้เพื่อเป็นตัวอย่างเพิ่มเติม นี่เป็นคำตอบเดียวกับhttps://stackoverflow.com/a/28159296/

ฉันจะเพิ่มการแก้ไขอื่น ๆ เพื่อให้โพสต์นี้มีประโยชน์มากขึ้น

pandas.DataFrame.query
queryถูกสร้างขึ้นเพื่อวัตถุประสงค์นี้ พิจารณา DataFramedf

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

ลองใช้queryเพื่อกรองแถวทั้งหมดที่D > 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

ซึ่งเราห่วงโซ่

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

นี่เป็นคำตอบเดียวกับstackoverflow.com/a/28159296 หรือ ไม่มีคำตอบใดที่หายไปจากที่คุณคิดว่าควรชี้แจง
bscan

9

ฉันมีคำถามเดียวกันยกเว้นว่าฉันต้องการรวมเกณฑ์เข้ากับเงื่อนไข OR รูปแบบที่กำหนดโดย Wouter Overmeire รวมเกณฑ์เข้ากับเงื่อนไขและเงื่อนไขที่ทั้งสองจะต้องมีความพึงพอใจ:

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

แต่ฉันพบว่าหากคุณหุ้มแต่ละเงื่อนไข(... == True)เข้ากับเกณฑ์ด้วยไพพ์เกณฑ์จะรวมกันในเงื่อนไข OR พึงพอใจทุกครั้งที่เงื่อนไขเหล่านั้นเป็นจริง

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

12
จะไม่df[(df.A==1) | (df.D==6)]เพียงพอสำหรับสิ่งที่คุณพยายามทำ
eenblam

ไม่ไม่ได้เพราะจะให้ผลลัพธ์ที่น่าเชื่อถือ (True vs False) แทนที่จะเป็นเพราะอยู่ด้านบนซึ่งกรองข้อมูลทั้งหมดที่ตรงตามเงื่อนไข หวังว่าฉันจะทำให้มันชัดเจน
MGB.py

8

แพนด้ามีสองทางเลือกสำหรับคำตอบของ Wouter Overmeire ซึ่งไม่ต้องการการเอาชนะใด ๆ หนึ่งอยู่.loc[.]กับ callable เช่นใน

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

อื่น ๆ.pipe()เช่นใน

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

7

คำตอบของฉันคล้ายกับคนอื่น ๆ หากคุณไม่ต้องการสร้างฟังก์ชั่นใหม่คุณสามารถใช้สิ่งที่แพนด้ากำหนดไว้ให้คุณแล้ว ใช้วิธีการไพพ์

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

นี่คือสิ่งที่คุณต้องการหากคุณต้องการเชื่อมโยงคำสั่งต่าง ๆ เช่นa.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
displayname

4

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

pd.DataFrame = apply_masks()

การใช้งาน:

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

มันค่อนข้างแฮ็คเล็กน้อย แต่มันสามารถทำให้สิ่งต่าง ๆ สะอาดขึ้นได้ถ้าคุณกำลังตัดและเปลี่ยนชุดข้อมูลอย่างต่อเนื่องตามตัวกรอง นอกจากนี้ยังมีตัวกรองวัตถุประสงค์ทั่วไปที่ดัดแปลงจาก Daniel Velkov ด้านบนในฟังก์ชั่น gen_mask ซึ่งคุณสามารถใช้กับฟังก์ชั่นแลมบ์ดาหรืออย่างอื่นได้หากต้องการ

ไฟล์ที่จะบันทึก (ฉันใช้ 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

โซลูชันนี้มีการแฮ็กมากกว่าในแง่ของการใช้งาน แต่ฉันคิดว่ามันสะอาดกว่าในแง่ของการใช้งานและแน่นอนกว่าวิธีอื่น ๆ ที่เสนอโดยทั่วไป

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

คุณไม่จำเป็นต้องดาวน์โหลด repo ทั้งหมด: บันทึกไฟล์และทำ

from where import where as W

ควรจะพอเพียง จากนั้นคุณใช้มันเช่นนี้:

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

ตัวอย่างการใช้งานที่โง่น้อยลงเล็กน้อย:

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

โดยวิธีการ: แม้ในกรณีที่คุณใช้บูลีนบูล

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

สามารถมีประสิทธิภาพมากกว่า

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

เพราะมันจะประเมินcond2เฉพาะในกรณีที่cond1เป็นTrueเป็น

การปฏิเสธความรับผิด: ฉันให้คำตอบแรกนี้ที่อื่นเพราะฉันไม่ได้เห็นสิ่งนี้


2

เพียงแค่ต้องการเพิ่มการสาธิตโดยใช้ locในการกรองไม่เพียงแค่แถว แต่ยังตามคอลัมน์และข้อดีบางอย่างในการดำเนินการที่ถูกผูกมัด

รหัสด้านล่างสามารถกรองแถวตามค่า

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

ด้วยการปรับเปลี่ยนเล็กน้อยคุณสามารถกรองคอลัมน์ได้เช่นกัน

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

แล้วทำไมเราถึงต้องการวิธีที่ถูกล่ามโซ่? คำตอบคือมันง่ายต่อการอ่านถ้าคุณมีการดำเนินการหลายอย่าง ตัวอย่างเช่น,

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

2

สิ่งนี้ไม่น่าสนใจเนื่องจากฉันต้องกำหนดให้dfกับตัวแปรก่อนที่จะสามารถกรองค่าได้

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

ดูเหมือนว่าจะทำงาน: คุณสามารถซ้อนตัว[]ดำเนินการได้เช่นกัน บางทีพวกเขาเพิ่มมันตั้งแต่คุณถามคำถาม


1
สิ่งนี้มีเหตุผลเล็กน้อยในห่วงโซ่เพราะdfตอนนี้ไม่จำเป็นต้องอ้างอิงผลลัพธ์ของส่วนก่อนหน้าของห่วงโซ่เต
Daan Luttik

@DananLuttik: ตกลงมันไม่ได้ผูกมัด แต่ทำรัง ดีกว่าสำหรับคุณ?
serv-inc

1

หากคุณตั้งค่าคอลัมน์ของคุณเพื่อค้นหาเป็นดัชนีคุณสามารถใช้DataFrame.xs()เพื่อข้ามส่วนได้ นี่ไม่ใช่queryคำตอบที่อเนกประสงค์แต่มันอาจมีประโยชน์ในบางสถานการณ์

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

คุณยังสามารถใช้ประโยชน์จากไลบรารีnumpyสำหรับการดำเนินการทางตรรกะ มันค่อนข้างเร็ว

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