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

사용자가 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으로 쉽게 확장 할 수 있다고 가정합니다.



답변

팬더 (및 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


답변

연쇄 조건은 긴 줄을 생성하며, 이는 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)]


답변

가장 간단한 솔루션 :

사용하다:

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

다른 예제 , Feb-2018에 속하는 값에 대해 데이터 프레임을 필터링하려면 아래 코드를 사용하십시오.

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


답변

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


답변

왜 이러지?

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

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


답변

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

예를 들어

list = [1, 0]
df[df.col1.isin(list)]


답변