Pandas 에서 반복을 효율적으로 처리하는 방법


Pandas 를 통해 데이터 프로세싱을 할 때 종종 해야할일은 행에 반복적으로 접근을 하면서 그 값을 조작하는 일이다. 예를 들어, missing value 가 0 으로 코딩이 되어있는데, 이를 다른 값으로 바꾸고 싶을 경우 또는 A 컬럼의 값이 missing 일 때, B 컬럼의 값을 수정하고 싶은 경우 등이 있다. 이러한 작업을 하기 위해서는 모든 행을 조회 하면서 값을 조회하고 수정하는 일이 필요하다. 이번 포스팅에서는 이러한 반복작업이 필요한 상황에서 어떤 방법이 가장 효율적일지에 대해 정리해보려고한다.


사용할 데이터

diabetes.csv


1) pd.iterrows()


가장 기본적이고 많이 사용하는 방법이 iterrows 함수를 이용하는 것이다. 하지만 iterrows 함수는 다른 방법에 비해 느린 편이다. 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

diabetes = pd.read_csv("diabetes.csv")
diabetes.head()


PregnanciesGlucoseBloodPressureSkinThicknessInsulinBMIDiabetesPedigreeFunctionAgeOutcome
061487235033.60.627501
11856629026.60.351310
28183640023.30.672321
318966239428.10.167210
40137403516843.12.288331


missing value 가 0 으로 코딩이 되어있는데, 이를 nan 으로 바꾸는 코드를 iterrows 를 이용해서 짜보자. 

def fix_missing(df, col):
    for i, row in df.iterrows():
        val = row[col]
        if val == 0:
            df.loc[i, col] = np.nan

%timeit fix_missing(diabetes, "SkinThickness")


33.9 ms ± 1.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)



2) pd.loc[]/pd.iloc[]


두 번째 방법은 index 를 통해 for 문을 돌면서, loc 또는 iloc 함수를 이용해 dataframe의 row에 접근하는 방법이다. 

def fix_missing2(df, col):
    for i in df.index:
        val = df.loc[i, col]
        if val == 0 :
            df.loc[i, col] = np.nan

%timeit fix_missing2(diabetes, "Insulin")


9.54 ms ± 130 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)


1) iterrow 방법에 비해 약 3배 빨라졌다는 것을 알 수 있다. 따라서 iterrows 가 익숙하다고 하더라도 다른 방법으로 바꾸는 것이 같은 작업을 더 빠르게 실행할 수 있어 효율적이다.  


3) pd.get_value()/pd.set_value()


다음은 위 방법과 마찬가지로 index를 통해 for 문을 돌면서 get_value 와 set_value 함수를 이용하는 방법이다. 2) 방법이 내부적으로 get_value, set_value를 호출하는 것이기 때문에 3) 이를 직접적으로 호출하는 방법이므로 더욱 빠르다. 

def fix_missing3(df, col):
    for i in df.index:
        val = df.get_value(i, col)
        if val == 0:
            df.set_value(i, col, np.nan)

%timeit fix_missing3(diabetes, "BMI")


3.65 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


2)에 비해 3배 정도 빨라졌으며, 1)에 비해서는 거의 10배 정도의 속도차이가 난다. 


4) pd.apply()


네 번째 방법은 apply 를 이용하는 것이다. apply 를 이용하는 것은 특별한 형태의 function 을 필요로 하는데 (이를 helper function 이라고도 한다), 이것은 Series 혹은 Dataframe의 각 원소마다 적용시킬 함수이다. 

def fix_missing4(x):
    if x == 0 : 
        return -999
    else: return x
    
%timeit diabetes.Age.apply(fix_missing4)
483 µs ± 3.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

3) 방법과 비교하여 3650/484 = 7.5 배 속도가 증가했다. apply 함수를 이용하는 것의 장점은 작업이 "비교적 간단할 때" 유용하다. 만약, 여러개의 column 에서 if 문을 적용하서 값을 다이나믹하게 바꾸어야하는 작업에 있어서는 apply 함수보다 for 문을 이용하는 방법이 더 적절할 수 있다. 

정리 
  • Default 방법으로 index 를 돌면서 set_value 와 get_value 를 호출하는 방법을 추천
  • 비교적 큰 데이터에셋에서 비교적 간단한 작업을 할 때, apply 함수가 가장 효율적


참고

https://medium.com/@rtjeannier/pandas-101-cont-9d061cb73bfc

  • 죠옹 2020.04.24 04:02 신고

    유용한 글 잘 읽고 갑니다!

  • 티비다시보기 2020.06.11 16:11

    많은 것을 배워갑니다

  • kann 2020.09.17 22:43

    좋은 글 감사드립니다.
    글 중 pd.get_value() 는 안되길래 찾아봤더니 pd.at()으로 대체되었더군요.
    따라서 하시다 막히시는 분들은 참고하세요.

    • 슨년 2020.10.12 14:02

      지나가다 댓글 남깁니다. 저도 막혀서 찾아보니 get_value()가 deprecated라고 하네요. at[]이나 iat[]사용하라고 합니다. 출처 남깁니다.
      https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.DataFrame.get_value.html