Pandas 반복에 성능 문제가 있습니까?


96

pandas에서 반복을 사용할 때 성능이 매우 떨어지는 것을 발견했습니다.

이것은 다른 사람들이 경험하는 것입니까? 반복에만 해당되며 특정 크기의 데이터에 대해이 함수를 사용하지 않아야합니까 (2 ~ 3 백만 행으로 작업하고 있음)?

GitHub에 대한이 토론 은 데이터 프레임에서 dtype을 혼합 할 때 발생한다고 믿게 만들었지 만, 아래의 간단한 예제는 하나의 dtype (float64)을 사용하더라도 존재한다는 것을 보여줍니다. 내 컴퓨터에서 36 초가 걸립니다.

import pandas as pd
import numpy as np
import time

s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})

start = time.time()
i=0
for rowindex, row in dfa.iterrows():
    i+=1
end = time.time()
print end - start

벡터화 작업이 적용되는 것과 같은 작업이 훨씬 더 빠른 이유는 무엇입니까? 나는 거기에도 행 단위로 반복이 있어야한다고 상상합니다.

내 경우에 반복을 사용하지 않는 방법을 알 수 없습니다 (이는 향후 질문을 위해 저장하겠습니다). 따라서이 반복을 지속적으로 피할 수 있었다면 듣고 싶습니다. 별도의 데이터 프레임의 데이터를 기반으로 계산을하고 있습니다. 감사합니다!

--- 편집 : 내가 실행하고 싶은 것의 단순화 된 버전이 아래에 추가되었습니다 ---

import pandas as pd
import numpy as np

#%% Create the original tables
t1 = {'letter':['a','b'],
      'number1':[50,-10]}

t2 = {'letter':['a','a','b','b'],
      'number2':[0.2,0.5,0.1,0.4]}

table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)

#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])

#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():   
    t2info = table2[table2.letter == row['letter']].reset_index()
    table3.ix[row_index,] = optimize(t2info,row['number1'])

#%% Define optimization
def optimize(t2info, t1info):
    calculation = []
    for index, r in t2info.iterrows():
        calculation.append(r['number2']*t1info)
    maxrow = calculation.index(max(calculation))
    return t2info.ix[maxrow]

7
apply벡터화되지 않습니다. iterrows모든 것을 상자에 넣을 때 더 나쁩니다 (즉, 성능 차이는 apply). iterrows매우 적은 상황 에서만 사용해야 합니다. IMHO 절대. 실제로 무엇을하고 있는지 보여 iterrows줍니다.
제프

2
대신 연결 한 문제는 DatetimeIndexinto Timestamps(python 공간에서 구현 됨) 의 boxing 과 관련이 있으며 이것은 master에서 훨씬 개선되었습니다.
제프

1
자세한 내용은이 문제를 참조하십시오 : github.com/pydata/pandas/issues/7194 .
제프

특정 질문에 대한 링크 (이 일반있을 것입니다) : stackoverflow.com/questions/24875096/...
KieranPC

iterrows () 사용을 권장하지 마십시오. 판다 역사상 최악의 안티 패턴의 노골적인 조력자입니다.
cs95

답변:


186

일반적으로 iterrows매우 특정한 경우에만 사용해야합니다. 다음은 다양한 작업의 성능에 대한 일반적인 우선 순위입니다.

1) vectorization
2) using a custom cython routine
3) apply
    a) reductions that can be performed in cython
    b) iteration in python space
4) itertuples
5) iterrows
6) updating an empty frame (e.g. using loc one-row-at-a-time)

사용자 지정 Cython 루틴을 사용하는 것은 일반적으로 너무 복잡하므로 지금은 건너 뛰겠습니다.

1) 벡터화는 항상 최고의 선택입니다. 그러나 명백한 방법으로 벡터화 할 수없는 작은 케이스 세트 (일반적으로 재발 포함)가 있습니다. 또한 작은 DataFrame에서는 다른 방법을 사용하는 것이 더 빠를 수 있습니다.

3) apply 일반적으로 Cython 공간에서 반복자가 처리 할 수 ​​있습니다. 이것은 apply표현식 내부에서 일어나는 일에 따라 다르지만 팬더가 내부적으로 처리 합니다. 예를 들어, df.apply(lambda x: np.sum(x))매우 빠르게 실행되지만 물론 df.sum(1)더 좋습니다. 그러나 이와 같은 df.apply(lambda x: x['b'] + 1)것은 Python 공간에서 실행되므로 결과적으로 훨씬 느립니다.

4) itertuples데이터를 Series. 튜플 형태로 데이터를 반환합니다.

5) iterrows데이터를 Series. 정말로 필요하지 않다면 다른 방법을 사용하십시오.

6) 빈 프레임을 한 번에 한 행씩 업데이트합니다. 나는이 방법이 너무 많이 사용되는 것을 보았다. 훨씬 느립니다. 아마도 일반적인 장소 (그리고 일부 파이썬 구조의 경우 상당히 빠름)이지만 DataFrame인덱싱에 대해 상당한 수의 검사를 수행하므로 한 번에 행을 업데이트하는 데 항상 매우 느립니다. 새로운 구조를 만들고 concat.


1
네, 6 번 (그리고 5 번)을 사용했습니다. 할 일이 좀 있습니다. 상대적인 초보자에게는 당연한 선택 인 것 같습니다.
KieranPC

3
제 경험상 3, 4, 5의 차이는 사용 사례에 따라 제한됩니다.
IanS

8
이 노트북 에서 런타임을 확인하려고했습니다 . 어떻게 든 itertuples더 빠릅니다 apply:(
Dimgold

1
pd.DataFrame.apply종종보다 느립니다 itertuples. 또한 벡터화 할 수 없는 계산에 대해 목록 이해,, map이름이 저조 np.vectorize하고 numba(특정 순서가 아님) , 예를 들어 this answer를 참조하십시오 .
jpp

2
@Jeff, 호기심 때문에 여기에 목록 이해력을 추가하지 않은 이유는 무엇입니까? 인덱스 정렬 또는 누락 된 데이터를 처리하지 않는 것이 사실이지만 (try-catch와 함께 함수를 사용하지 않는 한) 팬더 메서드가 벡터화되지 않은 많은 사용 사례 (문자열 / 정규식 항목)에 적합합니다 ( 진정한 의미에서) 구현. LC가 pandas 적용 및 많은 pandas 문자열 함수보다 더 빠르고 낮은 오버 헤드 대안이라는 것을 언급 할 가치가 있다고 생각하십니까?
cs95

17

Numpy와 pandas의 벡터 연산은 다음과 같은 몇 가지 이유로 vanilla Python의 스칼라 연산보다 훨씬 빠릅니다 .

  • Amortized type lookup : Python은 동적으로 입력되는 언어이므로 배열의 각 요소에 대한 런타임 오버 헤드가 있습니다. 그러나 Numpy (및 따라서 pandas)는 C (종종 Cython을 통해)에서 계산을 수행합니다. 배열의 유형은 반복이 시작될 때만 결정됩니다. 이 절약만으로도 가장 큰 성과 중 하나입니다.

  • 더 나은 캐싱 : C 배열을 반복하는 것은 캐시 친화적이므로 매우 빠릅니다. pandas DataFrame은 "열 지향 테이블"입니다. 즉, 각 열은 실제로 배열 일뿐입니다. 따라서 DataFrame에서 수행 할 수있는 기본 작업 (예 : 열의 모든 요소 합산)에는 캐시 누락이 거의 없습니다.

  • 병렬 처리를위한 더 많은 기회 : SIMD 명령어를 통해 간단한 C 어레이를 작동 할 수 있습니다. Numpy의 일부는 CPU 및 설치 프로세스에 따라 SIMD를 활성화합니다. 병렬 처리의 이점은 정적 타이핑 및 더 나은 캐싱만큼 극적이지는 않지만 여전히 확실한 승리입니다.

이야기의 도덕 : Numpy와 pandas에서 벡터 연산을 사용합니다. 이러한 연산이 C 프로그래머가 어쨌든 손으로 작성한 것과 똑같다는 단순한 이유 때문에 Python의 스칼라 연산보다 빠릅니다. (배열 개념이 SIMD 명령어가 포함 된 명시 적 루프보다 읽기가 훨씬 쉽다는 점을 제외하면)


11

문제를 해결하는 방법은 다음과 같습니다. 이것은 모두 벡터화됩니다.

In [58]: df = table1.merge(table2,on='letter')

In [59]: df['calc'] = df['number1']*df['number2']

In [60]: df
Out[60]: 
  letter  number1  number2  calc
0      a       50      0.2    10
1      a       50      0.5    25
2      b      -10      0.1    -1
3      b      -10      0.4    -4

In [61]: df.groupby('letter')['calc'].max()
Out[61]: 
letter
a         25
b         -1
Name: calc, dtype: float64

In [62]: df.groupby('letter')['calc'].idxmax()
Out[62]: 
letter
a         1
b         2
Name: calc, dtype: int64

In [63]: df.loc[df.groupby('letter')['calc'].idxmax()]
Out[63]: 
  letter  number1  number2  calc
1      a       50      0.5    25
2      b      -10      0.1    -1

매우 명확한 답변 감사합니다. 병합을 시도하지만 50 억 행 (250 만 * 2000)을 갖게 될 것이기 때문에 의문이 있습니다. 이 Q 장군을 유지하기 위해 특정 Q를 만들었습니다.이 거대한 테이블을 알고 있다면이 거대한 테이블을 피할 수있는 대안을
찾게되어 기쁩니다

1
이것은 데카르트 곱을 생성하지 않습니다. 압축 된 공간이고 메모리 효율성이 높습니다. 당신이하는 일은 매우 표준적인 문제입니다. 한 번 해봐. (연결된 질문은 매우 유사한 SOLN있다)
제프

7

또 다른 옵션은를 사용 to_records()하는 것 입니다. 이는 itertuplesiterrows.

그러나 귀하의 경우에는 다른 유형의 개선을위한 여지가 많이 있습니다.

여기에 최적화 된 최종 버전이 있습니다.

def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    t2info = table2.to_records()
    for index, letter, n1 in table1.to_records():
        t2 = t2info[grouped.groups[letter].values]
        # np.multiply is in general faster than "x * y"
        maxrow = np.multiply(t2.number2, n1).argmax()
        # `[1:]`  removes the index column
        ret.append(t2[maxrow].tolist()[1:])
    global table3
    table3 = pd.DataFrame(ret, columns=('letter', 'number2'))

벤치 마크 테스트 :

-- iterrows() --
100 loops, best of 3: 12.7 ms per loop
  letter  number2
0      a      0.5
1      b      0.1
2      c      5.0
3      d      4.0

-- itertuple() --
100 loops, best of 3: 12.3 ms per loop

-- to_records() --
100 loops, best of 3: 7.29 ms per loop

-- Use group by --
100 loops, best of 3: 4.07 ms per loop
  letter  number2
1      a      0.5
2      b      0.1
4      c      5.0
5      d      4.0

-- Avoid multiplication --
1000 loops, best of 3: 1.39 ms per loop
  letter  number2
0      a      0.5
1      b      0.1
2      c      5.0
3      d      4.0

전체 코드 :

import pandas as pd
import numpy as np

#%% Create the original tables
t1 = {'letter':['a','b','c','d'],
      'number1':[50,-10,.5,3]}

t2 = {'letter':['a','a','b','b','c','d','c'],
      'number2':[0.2,0.5,0.1,0.4,5,4,1]}

table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)

#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index)


print('\n-- iterrows() --')

def optimize(t2info, t1info):
    calculation = []
    for index, r in t2info.iterrows():
        calculation.append(r['number2'] * t1info)
    maxrow_in_t2 = calculation.index(max(calculation))
    return t2info.loc[maxrow_in_t2]

#%% Iterate through filtering relevant data, optimizing, returning info
def iterthrough():
    for row_index, row in table1.iterrows():   
        t2info = table2[table2.letter == row['letter']].reset_index()
        table3.iloc[row_index,:] = optimize(t2info, row['number1'])

%timeit iterthrough()
print(table3)

print('\n-- itertuple() --')
def optimize(t2info, n1):
    calculation = []
    for index, letter, n2 in t2info.itertuples():
        calculation.append(n2 * n1)
    maxrow = calculation.index(max(calculation))
    return t2info.iloc[maxrow]

def iterthrough():
    for row_index, letter, n1 in table1.itertuples():   
        t2info = table2[table2.letter == letter]
        table3.iloc[row_index,:] = optimize(t2info, n1)

%timeit iterthrough()


print('\n-- to_records() --')
def optimize(t2info, n1):
    calculation = []
    for index, letter, n2 in t2info.to_records():
        calculation.append(n2 * n1)
    maxrow = calculation.index(max(calculation))
    return t2info.iloc[maxrow]

def iterthrough():
    for row_index, letter, n1 in table1.to_records():   
        t2info = table2[table2.letter == letter]
        table3.iloc[row_index,:] = optimize(t2info, n1)

%timeit iterthrough()

print('\n-- Use group by --')

def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    for index, letter, n1 in table1.to_records():
        t2 = table2.iloc[grouped.groups[letter]]
        calculation = t2.number2 * n1
        maxrow = calculation.argsort().iloc[-1]
        ret.append(t2.iloc[maxrow])
    global table3
    table3 = pd.DataFrame(ret)

%timeit iterthrough()
print(table3)

print('\n-- Even Faster --')
def iterthrough():
    ret = []
    grouped = table2.groupby('letter', sort=False)
    t2info = table2.to_records()
    for index, letter, n1 in table1.to_records():
        t2 = t2info[grouped.groups[letter].values]
        maxrow = np.multiply(t2.number2, n1).argmax()
        # `[1:]`  removes the index column
        ret.append(t2[maxrow].tolist()[1:])
    global table3
    table3 = pd.DataFrame(ret, columns=('letter', 'number2'))

%timeit iterthrough()
print(table3)

최종 버전은 원래 코드보다 거의 10 배 빠릅니다. 전략은 다음과 같습니다.

  1. groupby값을 반복적으로 비교하지 않으려면 사용 합니다.
  2. to_records원시 numpy.records 객체에 액세스하는 데 사용 합니다.
  3. 모든 데이터를 컴파일 할 때까지 DataFrame에서 작동하지 마십시오.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.