gấu trúc: lọc các hàng của DataFrame với chuỗi toán tử


329

Hầu hết các hoạt động trong pandasthể được thực hiện với nhà điều hành chaining ( groupby, aggregate, apply, vv), nhưng cách duy nhất tôi đã tìm thấy các hàng lọc là thông qua chỉ mục khung bình thường

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

Điều này không hấp dẫn vì nó yêu cầu tôi gán dfcho một biến trước khi có thể lọc các giá trị của nó. Có một cái gì đó giống như sau đây?

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

df.querypd.evalcó vẻ như phù hợp với trường hợp sử dụng này. Để biết thông tin về pd.eval()họ các hàm, tính năng và trường hợp sử dụng của chúng, vui lòng truy cập Đánh giá biểu thức động trong gấu trúc bằng pd.eval () .
cs95

Câu trả lời:


384

Tôi không hoàn toàn chắc chắn về những gì bạn muốn và dòng mã cuối cùng của bạn cũng không giúp được gì, nhưng dù sao đi nữa:

Lọc "Chained" được thực hiện bằng cách "xâu chuỗi" các tiêu chí trong chỉ mục 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

Nếu bạn muốn xâu chuỗi các phương thức, bạn có thể thêm phương thức mặt nạ của riêng bạn và sử dụng phương thức đó.

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
Câu trả lời chính xác! Vì vậy (df.A == 1) & (df.D == 6), là "&" một nhà điều hành quá tải ở Pandas?
Shawn


Đó là một giải pháp thực sự tốt - tôi thậm chí còn không biết rằng bạn có thể đánh lừa các phương pháp như thế trong trăn. Một chức năng như thế này sẽ thực sự tốt đẹp khi có trong chính Pandas.
ness101 ngày

Vấn đề duy nhất tôi có với điều này là việc sử dụng pandas.. Bạn nên import pandas as pd.
Daisuke Aramaki

3
Thực sự import pandas as pdlà thực tế phổ biến bây giờ. Tôi nghi ngờ đó là khi tôi trả lời câu hỏi.
Wouter Overmeire

108

Các bộ lọc có thể được kết nối bằng truy vấn Pandas :

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

Các bộ lọc cũng có thể được kết hợp trong một truy vấn duy nhất:

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

3
Nếu bạn cần tham khảo các biến python trong truy vấn của mình, tài liệu nói: "Bạn có thể tham khảo các biến trong môi trường bằng cách thêm tiền tố vào chúng với ký tự '@' như @a + b". Lưu ý rằng sau đây là hợp lệ: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
dùng3780389

2
Mặt khác, có vẻ như việc đánh giá truy vấn sẽ thất bại nếu tên cột của bạn có một số ký tự đặc biệt: ví dụ: "Place.Name".
dùng3780389

2
Chaining là những gì truy vấn được thiết kế cho.
piRSquared

66

Câu trả lời từ @lodagro là tuyệt vời. Tôi sẽ mở rộng nó bằng cách khái quát hóa chức năng mặt nạ như:

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

Sau đó, bạn có thể làm những thứ như:

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

8
Một khái quát hữu ích! Tôi ước nó được tích hợp trực tiếp vào DataFrames rồi!
vịt xứng đáng

24

Kể từ phiên bản 0.18.1 , .locphương thức chấp nhận một cuộc gọi để lựa chọn. Cùng với các chức năng lambda, bạn có thể tạo các bộ lọc có thể tạo chuỗi rất linh hoạt:

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]

Nếu tất cả những gì bạn đang làm là lọc, bạn cũng có thể bỏ qua .loc.


16

Tôi cung cấp điều này cho các ví dụ bổ sung. Đây là câu trả lời tương tự như https://stackoverflow.com/a/28159296/

Tôi sẽ thêm các chỉnh sửa khác để làm cho bài đăng này hữu ích hơn.

pandas.DataFrame.query
queryđã được thực hiện cho chính xác mục đích này. Hãy xem xét khung dữ liệudf

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

Chúng ta hãy sử dụng queryđể lọc tất cả các hàng trong đó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

Mà chúng tôi chuỗi

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

Đây không phải là câu trả lời cơ bản giống như stackoverflow.com/a/28159296 Có thiếu điều gì từ câu trả lời mà bạn nghĩ cần được làm rõ không?
bscan

9

Tôi đã có cùng một câu hỏi ngoại trừ việc tôi muốn kết hợp các tiêu chí thành một điều kiện OR. Định dạng được đưa ra bởi Wouter Overmeire kết hợp các tiêu chí thành một điều kiện AND sao cho cả hai phải được thỏa mãn:

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

Nhưng tôi thấy rằng, nếu bạn bọc từng điều kiện (... == True)và tham gia các tiêu chí bằng một đường ống, các tiêu chí được kết hợp trong điều kiện OR, thỏa mãn bất cứ khi nào một trong hai điều kiện là đúng:

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

12
Sẽ không df[(df.A==1) | (df.D==6)]đủ cho những gì bạn đang cố gắng thực hiện?
eenblam

Không, sẽ không vì nó cho kết quả bollean (Đúng so với Sai) thay vì ở trên mà bộ lọc tất cả dữ liệu thỏa mãn điều kiện. Hy vọng rằng tôi đã làm cho nó rõ ràng.
MGB.py

8

gấu trúc cung cấp hai lựa chọn thay thế cho câu trả lời của Wouter Overmeire mà không yêu cầu ghi đè. Một là .loc[.]với một cuộc gọi, như trong

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

khác là .pipe(), như trong

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

7

Câu trả lời của tôi tương tự như những người khác. Nếu bạn không muốn tạo một chức năng mới, bạn có thể sử dụng những gì gấu trúc đã xác định cho bạn. Sử dụng phương pháp đường ống.

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

ĐÂY là những gì bạn muốn nếu bạn muốn xâu chuỗi các lệnh nhưa.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
displayname

4

Nếu bạn muốn áp dụng tất cả các mặt nạ boolean phổ biến cũng như mặt nạ cho mục đích chung, bạn có thể tặc lưỡi sau đây trong một tệp và sau đó chỉ cần gán tất cả chúng như sau:

pd.DataFrame = apply_masks()

Sử dụng:

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

Hơi khó khăn một chút nhưng nó có thể khiến mọi thứ sạch sẽ hơn một chút nếu bạn liên tục cắt và thay đổi bộ dữ liệu theo các bộ lọc. Ngoài ra còn có một bộ lọc mục đích chung được điều chỉnh từ Daniel Velkov ở trên trong hàm gen_mask mà bạn có thể sử dụng với các hàm lambda hoặc nếu không muốn.

Tập tin sẽ được lưu (Tôi sử dụng mask.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

Giải pháp này có nhiều hack về mặt thực hiện, nhưng tôi thấy nó sạch sẽ hơn nhiều về mặt sử dụng, và nó chắc chắn là tổng quát hơn so với những người khác đề xuất.

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

Bạn không cần tải xuống toàn bộ repo: lưu tệp và thực hiện

from where import where as W

nên đủ. Sau đó, bạn sử dụng nó như thế này:

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

Một ví dụ sử dụng hơi ngu ngốc:

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

Nhân tiện: ngay cả trong trường hợp bạn chỉ đang sử dụng cols boolean,

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

có thể hiệu quả hơn nhiều

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

bởi vì nó đánh giá cond2duy nhất mà cond1True.

TUYÊN BỐ TỪ CHỐI: Lần đầu tiên tôi đưa ra câu trả lời này ở nơi khác vì tôi chưa thấy điều này.


2

Chỉ muốn thêm một cuộc biểu tình bằng cách sử dụng loc để lọc không chỉ theo hàng mà còn theo cột và một số giá trị cho hoạt động được xâu chuỗi.

Mã dưới đây có thể lọc các hàng theo giá trị.

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

Bằng cách sửa đổi nó một chút, bạn cũng có thể lọc các cột.

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

Vậy tại sao chúng ta muốn một phương pháp xích? Câu trả lời là nó rất đơn giản để đọc nếu bạn có nhiều thao tác. Ví dụ,

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

2

Điều này không hấp dẫn vì nó yêu cầu tôi gán dfcho một biến trước khi có thể lọc các giá trị của nó.

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

dường như làm việc: bạn có thể lồng các []toán tử là tốt. Có lẽ họ đã thêm nó kể từ khi bạn đặt câu hỏi.


1
Điều này rất ít có ý nghĩa trong một chuỗi bởi vì dfbây giờ không nhất thiết phải tham chiếu đầu ra của phần trước của chuỗi te.
Daan Luttik

@DaanLuttik: đồng ý, không phải là xích, mà là làm tổ. Tốt hơn cho bạn?
phục vụ

1

Nếu bạn đặt các cột của mình để tìm kiếm dưới dạng chỉ mục, thì bạn có thể sử dụng DataFrame.xs()để có một mặt cắt ngang. Điều này không linh hoạt như querycâu trả lời, nhưng nó có thể hữu ích trong một số tình huống.

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

Bạn cũng có thể tận dụng thư viện numpy cho các hoạt động hợp lý. Nó khá nhanh.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.