Cách hiệu quả để áp dụng nhiều bộ lọc cho gấu trúc DataFrame hoặc Sê-ri


148

Tôi có một kịch bản trong đó người dùng muốn áp dụng một số bộ lọc cho đối tượng Pandas DataFrame hoặc Series. Về cơ bản, tôi muốn kết nối một cách hiệu quả một loạt các bộ lọc (hoạt động so sánh) với nhau được chỉ định tại thời điểm chạy bởi người dùng.

Các bộ lọc nên là phụ gia (còn gọi là mỗi bộ lọc được áp dụng sẽ thu hẹp kết quả).

Tôi hiện đang sử dụng reindex()nhưng điều này tạo ra một đối tượng mới mỗi lần và sao chép dữ liệu cơ bản (nếu tôi hiểu tài liệu chính xác). Vì vậy, điều này có thể thực sự không hiệu quả khi lọc một Series hoặc DataFrame lớn.

Tôi đang nghĩ rằng việc sử dụng apply(), map()hoặc một cái gì đó tương tự có thể được tốt hơn. Tôi khá mới mẻ với Pandas mặc dù vậy vẫn cố gắng quấn đầu mọi thứ.

TL; DR

Tôi muốn lấy một từ điển có dạng sau và áp dụng từng thao tác cho một đối tượng Sê-ri nhất định và trả về một đối tượng Sê-ri 'được lọc'.

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

Ví dụ dài

Tôi sẽ bắt đầu với một ví dụ về những gì tôi hiện có và chỉ lọc một đối tượng Sê-ri duy nhất. Dưới đây là chức năng tôi hiện đang sử dụng:

   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

Người dùng cung cấp một từ điển với các hoạt động mà họ muốn thực hiện:

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

Một lần nữa, "vấn đề" với cách tiếp cận trên của tôi là tôi nghĩ rằng có rất nhiều sự sao chép dữ liệu có thể không cần thiết cho các bước ở giữa.

Ngoài ra, tôi muốn mở rộng điều này để từ điển được truyền vào có thể bao gồm các cột cho toán tử trên và lọc toàn bộ DataFrame dựa trên từ điển đầu vào. Tuy nhiên, tôi cho rằng mọi thứ hoạt động cho Sê-ri đều có thể dễ dàng mở rộng thành Khung dữ liệu.


Ngoài ra, tôi hoàn toàn biết rằng cách tiếp cận vấn đề này có thể được giải quyết. Vì vậy, có thể suy nghĩ lại toàn bộ cách tiếp cận sẽ hữu ích. Tôi chỉ muốn cho phép người dùng chỉ định một tập hợp các hoạt động của bộ lọc trong thời gian chạy và thực thi chúng.
durden2.0

Tôi tự hỏi liệu gấu trúc có thể làm những điều tương tự như data.table trong R: df [col1 <1 ,,] [col2> = 1]
xappppp

df.querypd.evalcó vẻ như phù hợp với trường hợp sử dụng của bạn. Để 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:


244

Gấu trúc (và numpy) cho phép lập chỉ mục boolean , sẽ hiệu quả hơn nhiều:

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

Nếu bạn muốn viết các hàm trợ giúp cho việc này, hãy xem xét một số thứ dọc theo các dòng sau:

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

Cập nhật: pandas 0.13 có phương thức truy vấn cho các loại trường hợp sử dụng này, giả sử tên cột là định danh hợp lệ cho các tác phẩm sau (và có thể hiệu quả hơn cho các khung lớn vì nó sử dụng numexpr đằng sau hậu trường):

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

1
Quyền của bạn, boolean hiệu quả hơn vì nó không tạo ra một bản sao của dữ liệu. Tuy nhiên, kịch bản của tôi khó hơn một chút so với ví dụ của bạn. Đầu vào tôi nhận được là một từ điển xác định những bộ lọc sẽ áp dụng. Ví dụ của tôi có thể làm một cái gì đó như df[(ge(df['col1'], 1) & le(df['col1'], 1)]. Vấn đề đối với tôi thực sự là từ điển với các bộ lọc có thể chứa nhiều toán tử và kết nối chúng với nhau rất cồng kềnh. Có lẽ tôi có thể thêm từng mảng boolean trung gian vào một mảng lớn và sau đó chỉ cần sử dụng mapđể áp dụng andtoán tử cho chúng?
durden2.0

@ durden2.0 Tôi đã thêm một ý tưởng cho chức năng trợ giúp, mà tôi nghĩ là tương tự với những gì bạn đang tìm kiếm :)
Andy Hayden

Điều đó có vẻ rất gần với những gì tôi nghĩ ra! Cảm ơn ví dụ. Tại sao f()cần phải thực hiện *bthay vì chỉ b? Đây có phải là người dùng f()vẫn có thể sử dụng outtham số tùy chọn để logical_and()? Điều này dẫn đến một câu hỏi phụ nhỏ khác. Hiệu suất / đánh đổi hiệu suất của việc truyền trong mảng qua out()so với sử dụng cái được trả về là logical_and()gì? Cảm ơn một lần nữa!
durden2.0

Nevermind, tôi đã không nhìn đủ gần. Các *blà cần thiết vì bạn đang đi qua hai mảng b1b2và bạn cần phải giải nén chúng khi gọi logical_and. Tuy nhiên, câu hỏi khác vẫn đứng. Có một lợi ích hiệu suất để chuyển trong một mảng thông qua outtham số để logical_and()so với chỉ sử dụng giá trị trả về của nó?
durden2.0

2
@dwanderson bạn có thể chuyển một danh sách các điều kiện cho np.logical_and.reduce cho nhiều điều kiện. Ví dụ: np.logical_and.reduce ([df ['a'] == 3, df ['b']> 10, df ['c']. Isin (1,3,5)])
Kuzenbo

38

Điều kiện xâu chuỗi tạo ra các hàng dài, không được pep8 khuyến khích. Sử dụng phương thức .query buộc phải sử dụng các chuỗi, mạnh mẽ nhưng không linh hoạt và không năng động.

Khi từng bộ lọc được đặt, một cách tiếp cận là

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 hoạt động trên và nhanh, nhưng không mất nhiều hơn hai đối số, được xử lý bởi funcools.reduce.

Lưu ý rằng điều này vẫn còn một số dư thừa: a) việc cắt ngắn không xảy ra ở cấp độ toàn cầu b) Mỗi ​​điều kiện riêng lẻ chạy trên toàn bộ dữ liệu ban đầu. Tuy nhiên, tôi hy vọng điều này đủ hiệu quả cho nhiều ứng dụng và nó rất dễ đọc.


1
Có cách nào để thực hiện điều này cho một số điều kiện khác nhau không? Tôi đã cố gắng nối đoạn c_1, c_2, c_3, ... c_ntrong danh sách, và sau đó đi qua data[conjunction(conditions_list)]nhưng nhận được một lỗi ValueError: Item wrong length 5 instead of 37.Cũng đã cố gắng data[conjunction(*conditions_list)]nhưng tôi nhận được một kết quả khác nhau hơn data[conjunction(c_1, c_2, c_3, ... c_n )], không chắc chắn những gì đang xảy ra.
dùng5359531

Tìm thấy một giải pháp cho các lỗi ở nơi khác. data[conjunction(*conditions_list)]không hoạt động sau khi đóng gói các tệp dữ liệu vào một danh sách và giải nén danh sách tại chỗ
user5359531

1
Tôi chỉ để lại một bình luận về câu trả lời ở trên với một phiên bản chậm hơn nhiều, và sau đó nhận thấy câu trả lời của bạn. Rất sạch sẽ, tôi thích nó rất nhiều!
lùn

Đây là một câu trả lời tuyệt vời!
Charlie Crown

1
Tôi đã sử dụng: df[f_2 & f_3 & f_4 & f_5 ]với f_2 = df["a"] >= 0vv Không cần chức năng đó ... (mặc dù sử dụng tốt chức năng bậc cao hơn ...)
A. Rabus

18

Đơn giản nhất trong tất cả các giải pháp:

Sử dụng:

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

Một ví dụ khác , để lọc khung dữ liệu cho các giá trị thuộc về tháng 2-2018, hãy sử dụng mã dưới đây

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

tôi đang sử dụng biến thay vì hằng. nhận lỗi. df [df []] [df []] đưa ra thông điệp cảnh báo nhưng đưa ra câu trả lời đúng.
Nguai al

8

Kể từ khi gấu trúc cập nhật 0,22 , các tùy chọn so sánh có sẵn như:

  • gt (lớn hơn)
  • lt (ít hơn)
  • eq (bằng)
  • ne (không bằng)
  • ge (lớn hơn hoặc bằng)

và nhiều thứ khác nữa. Các hàm này trả về mảng boolean. Hãy xem cách chúng ta có thể sử dụng chúng:

# 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

Tại sao không làm điều này?

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

Bản giới thiệu:

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

Kết quả:

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

Bạn có thể thấy cột 'a' đã được lọc trong đó a> = 2.

Tốc độ này nhanh hơn một chút (thời gian gõ, không phải hiệu suất) so với chuỗi toán tử. Tất nhiên bạn có thể đặt việc nhập ở đầu tệp.


1

e cũng có thể chọn các hàng dựa trên các giá trị của một cột không có trong danh sách hoặc bất kỳ lần lặp nào. Chúng ta sẽ tạo biến boolean giống như trước đây, nhưng bây giờ chúng ta sẽ phủ định biến boolean bằng cách đặt ~ ở phía trước.

Ví dụ

list = [1, 0]
df[df.col1.isin(list)]
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.