팬더 DataFrame 또는 시리즈에 여러 필터를 적용하는 효율적인 방법


148

사용자가 Pandas DataFrame 또는 Series 객체에 여러 필터를 적용하려는 시나리오가 있습니다. 본질적으로 사용자가 런타임에 지정하는 여러 필터링 (비교 작업)을 효율적으로 연결하고 싶습니다.

필터는 추가되어야합니다 (일명 적용되는 각 필터는 결과가 좁아 야 함).

현재 사용하고 reindex()있지만 매번 새 객체를 만들고 기본 데이터를 복사합니다 (문서를 올바르게 이해하면). 따라서 큰 Series 또는 DataFrame을 필터링 할 때 실제로 비효율적 일 수 있습니다.

내가 사용하는 것을 생각하고 apply(), map()또는 이와 유사한 일이 더 좋을 수 있습니다. 나는 여전히 팬더를 처음 접했지만 여전히 모든 것을 머리로 감싸려고합니다.

TL; DR

다음 형식의 사전을 가져 와서 각 작업을 주어진 Series 객체에 적용하고 '필터링 된'Series 객체를 반환하고 싶습니다.

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

긴 예

나는 현재 가지고있는 것의 예부터 시작하여 단일 Series 객체를 필터링합니다. 아래는 현재 사용중인 기능입니다.

   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을 필터링 할 수 있도록 이것을 확장하고 싶습니다. 그러나 Series에 작동하는 모든 것을 DataFrame으로 쉽게 확장 할 수 있다고 가정합니다.


또한이 문제에 대한 접근 방식이 제대로 작동하지 않을 수 있음을 충분히 알고 있습니다. 따라서 전체 접근 방식을 재고하는 것이 유용 할 것입니다. 사용자가 런타임에 일련의 필터 작업을 지정하고 실행할 수 있도록하고 싶습니다.
durden2.0

팬더가 R의 data.table과 비슷한 일을 할 수 있는지 궁금합니다. df [col1 <1 ,,] [col2> = 1]
xappppp

df.querypd.eval사용 사례에 대한 좋은 맞는 것처럼 보인다. pd.eval()함수 계열, 기능 및 사용 사례 에 대한 자세한 내용은 pd.eval ()을 사용하여 팬더의 Dynamic Expression Evaluation을 방문하십시오 .
cs95

답변:


245

팬더 (및 numpy)는 부울 인덱싱을 허용 하므로 훨씬 효율적입니다.

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()해야 합니까? 이 때문에 사용자는 여전히 선택적 매개 변수를 사용할 수 있습니까? 이것은 또 다른 작은 질문으로 이어집니다. 성능 이점을 통해 배열에 전달하는 트레이드 오프 (trade off) / 무엇 에서 반환 된 하나를 사용하여 대 ? 다시 감사합니다! *bbf()outlogical_and()out()logical_and()
durden2.0

신경 쓰지 마, 나는 충분히 가까이 보지 않았다. 은 *b당신이 두 배열을 전달하기 때문에 필요 b1하고 b2및 호출 할 때 당신이 그들을 압축을 풀 필요가있다 logical_and. 그러나 다른 질문은 여전히 ​​유효합니다. out매개 변수를 통해 배열에 전달 logical_and()값을 사용 하는 것보다 성능상의 이점이 있습니까?
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에 의해 권장되지 않습니다. .query 메소드를 사용하면 문자열이 강력하게 사용되지만 강력하지는 않지만 강력하지는 않습니다.

각 필터가 설치되면 한 가지 접근 방식은

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)]는 데이터 프레임을 목록으로 포장하고 목록을 제 위치에
user5359531

1
나는 훨씬 더 얇은 버전으로 위의 답변에 대한 의견을 남긴 후 귀하의 답변을 발견했습니다. 매우 깨끗합니다. 많이 좋아합니다!
dwanderson

이것은 좋은 답변입니다!
찰리 크라운

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 []]는 경고 메시지를 제공하지만 정답을 제공합니다.
Nguai al

8

pandas 0.22 update 이므로 다음과 같은 비교 옵션을 사용할 수 있습니다.

  • 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'가 a> = 2 인 경우 필터링되었음을 알 수 있습니다.

이것은 연산자 체인보다 약간 빠릅니다 (타이핑 시간이 아니라 성능). 물론 파일의 맨 위에 가져 오기를 넣을 수 있습니다.


1

또한 목록에 없거나 반복 가능한 열 값을 기반으로 행을 선택할 수 있습니다. 이전과 같이 부울 변수를 만들지 만 이제 ~를 앞에 두어 부울 변수를 무효화합니다.

예를 들어

list = [1, 0]
df[df.col1.isin(list)]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.