2016-02-09 18 views
7

Nếu bạn biết chính xác làm thế nào bạn muốn lọc một dataframe, giải pháp là tầm thường:gấu trúc: Có thể lọc một khung dữ liệu với các tiêu chí boolean dài tùy ý không?

df[(df.A == 1) & (df.B == 1)]

Nhưng nếu bạn chấp nhận đầu vào người sử dụng và không biết trước có bao nhiêu tiêu chí người dùng muốn sử dụng không? Ví dụ, người dùng muốn một khung dữ liệu đã lọc nơi cột [A, B, C] == 1. Có thể làm điều gì đó như:

def filterIt(*args, value): 
    return df[(df.*args == value)] 

vì vậy nếu người dùng gọi filterIt(A, B, C, value=1), nó sẽ trả về:

df[(df.A == 1) & (df.B == 1) & (df.C == 1)] 
+0

Có thể dupe: https://stackoverflow.com/questions/11869910/pandas-filter-rows-of-dataframe-with-operator-chaining – chishaku

+0

https://stackoverflow.com/questions/13611065/efficient-way- to-apply-multiple-filters-to-pandas-dataframe-hoặc-series – chishaku

+0

Bạn luôn muốn so sánh các cột khác nhau có cùng giá trị không? (1 trong trường hợp này?) – joris

Trả lời

5

Tôi nghĩ rằng cách thanh lịch nhất để làm điều này là sử dụng df.query(), nơi bạn có thể xây dựng một chuỗi với tất cả các điều kiện của bạn, ví dụ:

import pandas as pd 
import numpy as np 

cols = {} 
for col in ('A', 'B', 'C', 'D', 'E'): 
    cols[col] = np.random.randint(1, 5, 20) 
df = pd.DataFrame(cols) 

def filter_df(df, filter_cols, value): 
    conditions = [] 
    for col in filter_cols: 
     conditions.append('{c} == {v}'.format(c=col, v=value)) 
    query_expr = ' and '.join(conditions) 
    print('querying with: {q}'.format(q=query_expr)) 
    return df.query(query_expr) 

đầu ra Ví dụ (kết quả của bạn có thể khác nhau do dữ liệu được tạo ngẫu nhiên):

filter_df(df, ['A', 'B'], 1) 
querying with: A == 1 and B == 1 
    A B C D E 
6 1 1 1 2 1 
11 1 1 2 3 4 
1

Điều này khá lộn xộn nhưng có vẻ như nó hoạt động.

import operator 

def filterIt(value,args): 
    stuff = [getattr(b,thing) == value for thing in args] 
    return reduce(operator.and_, stuff) 

a = {'A':[1,2,3],'B':[2,2,2],'C':[3,2,1]} 
b = pd.DataFrame(a) 
filterIt(2,['A','B','C']) 

0 False 
1  True 
2 False 
dtype: bool 



(b.A == 2) & (b.B == 2) & (b.C ==2) 

0 False 
1  True 
2 False 
dtype: bool 
+0

'return reduce (operator.and_, stuff)' là những gì bạn đang tìm kiếm (thay vì vòng lặp 'for'). –

+0

Cảm ơn! chỉnh sửa giải pháp của tôi – steven

1

Cảm ơn các bạn đã trợ giúp. Tôi đã đưa ra một cái gì đó tương tự như Marius sau khi tìm hiểu về df.query():

def makeQuery(cols, equivalence=True, *args): 
operator = ' == ' if equivalence else ' != ' 
query = '' 
for arg in args: 
    for col in cols: 
     query = query + "({}{}{})".format(col, operator, arg) + ' & ' 

return query[:-3] 


query = makeQuery([A, B, C], False, 1, 2) 

Nội dung truy vấn là một chuỗi:

(A != 1) & (B != 1) & (C != 1) & (A != 2) & (B != 2) & (C != 2) 

có thể được truyền cho df.query (query)

+1

''&'. join ('({} {} {})' định dạng (col, toán tử, arg) cho col trong cols)' có thể thay thế vòng lặp bên trong của bạn. –

5

Đây là một cách tiếp cận khác. Nó sạch hơn, hiệu quả hơn và có lợi thế là columns có thể trống (trong trường hợp đó toàn bộ khung dữ liệu được trả về).

def filter(df, value, *columns): 
    return df.loc[df.loc[:, columns].eq(value).all(axis=1)] 

Giải thích

  1. values = df.loc[:, columns] chọn chỉ có các cột chúng ta quan tâm.
  2. masks = values.eq(value) đưa ra một khung dữ liệu boolean chỉ ra sự bình đẳng với các giá trị mục tiêu.
  3. mask = masks.all(axis=1) áp dụng AND trên các cột (trả về mặt nạ chỉ mục). Lưu ý rằng bạn có thể sử dụng masks.any(axis=1) cho OR.
  4. return df.loc[mask] áp dụng mặt nạ chỉ mục cho khung dữ liệu.

Demo

import numpy as np 
import pandas as pd 

df = pd.DataFrame(np.random.randint(0, 2, (100, 3)), columns=list('ABC')) 

# both columns 
assert np.all(filter(df, 1, 'A', 'B') == df[(df.A == 1) & (df.B == 1)]) 

# no columns 
assert np.all(filter(df, 1) == df) 

# different values per column 
assert np.all(filter(df, [1, 0], 'A', 'B') == df[(df.A == 1) & (df.B == 0)]) 

Alternative

Đối với một số lượng nhỏ các cột (< 5), các giải pháp sau đây, dựa trên steven's answer, nhiều performant hơn ở trên, mặc dù ít linh hoạt hơn. Như là, nó sẽ không hoạt động cho một tập hợp rỗng columns và sẽ không hoạt động bằng cách sử dụng các giá trị khác nhau cho mỗi cột.

from operator import and_ 

def filter(df, value, *columns): 
    return df.loc[reduce(and_, (df[column] == value for column in columns))] 

Lấy ra một đối tượng Series bằng phím (df[column]) là nhanh hơn so với việc xây dựng một đối tượng DataFrame xung quanh một tập hợp con của các cột (df.loc[:, columns]) đáng kể.

In [4]: %timeit df['A'] == 1 
100 loops, best of 3: 17.3 ms per loop 

In [5]: %timeit df.loc[:, ['A']] == 1 
10 loops, best of 3: 48.6 ms per loop 

Tuy nhiên, việc tăng tốc này trở nên không đáng kể khi xử lý nhiều cột hơn. Các nút cổ chai trở thành ANDing các mặt nạ với nhau, trong đó reduce(and_, ...) là chậm hơn so với Pandas được xây dựng all(axis=1).

Các vấn đề liên quan