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

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]



답변

일반적으로 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.


답변

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 명령어가 포함 된 명시 적 루프보다 읽기가 훨씬 쉽다는 점을 제외하면)


답변

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

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


답변

또 다른 옵션은를 사용 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에서 작동하지 마십시오.


답변

예, Pandas itertuples ()는 iterrows ()보다 빠릅니다. https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html 문서를 참조 할 수 있습니다.

“행을 반복하는 동안 dtype을 보존하려면 값의 명명 된 튜플을 반환하고 일반적으로 반복보다 빠른 itertuples ()를 사용하는 것이 좋습니다.”


답변

이 비디오의 세부 사항

기준
여기에 이미지 설명 입력


답변