GIL's LAB

Pandas의 Apply 메서드 본문

파이썬/데이터 분석을 위한 파이썬

Pandas의 Apply 메서드

GIL~ 2021. 10. 5. 12:40

이번 포스팅에서는 Pandas를 효율적으로 사용하는데 꼭 필요한 함수인 apply 메서드에 대해서 알아보도록 하자.

 

개요

공식 문서에서는 apply 메서드를 다음과 같이 소개하고 있다.

 

pandas.DataFrame.apply

DataFrame.apply(func, axis=0, raw=False, result_type=None, args=(), **kwargs)

 

Apply a function along an axis of the DataFrame.

Objects passed to the function are Series objects whose index is either the DataFrame’s index (axis=0) or the DataFrame’s columns (axis=1). By default (result_type=None), the final return type is inferred from the return type of the applied function. Otherwise, it depends on the result_type argument.

 

pandas.Series.apply

Series.apply(func, convert_dtype=True, args=(), **kwargs)

Invoke function on values of Series.

Can be ufunc (a NumPy function that applies to the entire Series) or a Python function that only works on single values.

 

공식 문서에 따르면, apply 메서드는 데이터프레임과 시리즈의 각 요소들에 함수를 적용하는 것이라 할 수 있다. 

시리즈를 예로 들어 설명하면 아래 그림과 같이, S라는 시리즈에 있는 요소 A, B, C에 일괄적으로 함수 func을 적용하는데 사용하는 메서드가 apply인 것이다.

여기서 func은 시리즈의 각 요소를 입력으로 받는 함수여야 한다.

 

apply를 사용해야 하는 이유

왜 반복문이 아니라 apply 함수를 써야하는가라는 의문이 들 수 있다.

반복문을 사용한 것과 apply를 사용하는 것이랑 어떤 차이가 있는 것일까?

직접 간단한 실험을 하면서 알아보자.

 

먼저, pandas를 임포트하고 1, 2, 3, 4, 5로 구성된 시리즈 S1과 S2를 만든다. 

import pandas as pd
S1 = pd.Series([1,2,3,4,5])
S2 = pd.Series([1,2,3,4,5])

S1에 대해서는 apply 메서드를 사용하여 각 요소에 제곱을 취하는 함수를 적용하자.

S2에 대해서는 for 문을 사용하여 각 요소에 제곱을 취하자. 

그리고 두 방법의 시간을 %timeit 매직키워드를 이용하여 수행 시간을 비교할 것이다.

수월하게 시간을 비교하기 위해, 두 방법을 모두 다음과 같이 함수화하자.

 

def apply_test(S):
    S = S.apply(lambda x:x**2)
    return S

def for_test(S):
    for i in range(5):
        S.iloc[i] = S.iloc[i] ** 2
    return S

apply_test 함수는 S가 입력되면 S의 요소에 제곱을 취하는 람다 함수를 apply한 것이고, for_test 함수는 S가 입력되면 S의 요소를 순회하면서 제곱을 취하는 것이다.

 

이제 %timeit을 이용하여 두 함수의 소요 시간을 비교해보자.

드라마틱한 차이는 아니지만, apply를 사용하는 것이 더 빠른 것을 알 수 있다.

 

 

시리즈의 크기가 커지면 어떠한 차이가 있는지를 살펴보도록 하자.

이번에는 S1과 S2을 난수로 구성된 크기 1만인 시리즈로 정의하고 위의 실험을 다시 진행해보자.

S1 = pd.Series([1] * 10000)
S2 = pd.Series([1] * 10000)

10.4ms와 1.34s로 100배가 넘는 차이가 있음을 확인했다.

그러니까 시리즈의 크기가 클수록 apply 함수를 사용하는 것이 훨씬 효율적임을 알 수 있다.

 

 

다양한 apply를 사용 방법 (1) 다른 인자가 있을 때

위에서 예시로 사용한 함수는 lambda x:x**2로 하나의 요소를 받는 함수이다.

그런데 아래와 같은 간단한 함수를 생각해보자.

def f(x, a):
    return x + a

 

시리즈의 요소 x에 a를 더하고 싶은 경우에는 어떻게 해야 할까?

이러한 경우에는 apply(func, 인자)와 같은 구조로 함수의 인자에 값을 입력할 수 있다.

 

즉, 아래 예시와 같이 S라는 시리즈의 모든 요소에 3을 더할 수 있다.

S = pd.Series([1,2,3,4,5])
S.apply(f, a = 3)

여기서 반드시 a = 3과 같이 인자명을 밝혀줘야 한다.

비록 함수 f가 x와 a만 받지만, 인자를 밝혀주지 않으면 S의 요소가 x인지, a인지를 구분하지 못해 다음과 같은 오류가 발생한다.

 

 

다양한 apply를 사용 방법 (2) 데이터프레임의 각 열/행에 함수를 적용하고 싶은 경우

이제 DataFrame에 대해서 apply를 사용하는 상황을 생각해보자.

DataFrame에 대해 적용할 일이 그리 많지는 않지만, 해당 내용은 반드시 알아둬야 한다.

데이터프레임의 각 열 및 행에 함수를 적용하는 경우는 보통 열/행별 통계량을 구하는데 주로 활용된다.

열별 최대값, 행별 표준편차 등이 그 대표적인 예시다. 

 

코드 구조는 DataFrame.apply(func, axis)와 같다.

여기서 func은 iterable한 객체를 입력받는 함수여야 한다. 

 

열별 최대값과 행별 평균을 구하는 예시를 통해 더 자세히 알아보자.

먼저 예제 데이터를 생성한다.

df = pd.DataFrame({"A":[1,2,3,4,5], "B":[3,4,5,6,7]})

이제 이 데이터에 apply를 사용해서 열별 최대값을 구해보자.

df.apply(max, axis = 0)

max 함수는 bulit-in 함수이기 때문에 따로 정의하지 않아도 사용할 수 있다.

 

마찬가지로 행별 평균은 다음과 같이 구할 수 있다.

import numpy as np
df.apply(np.mean, axis = 1)

여기서 평균을 구하는 함수는 built-in 함수가 없어서 numpy의 mean을 사용하였다.

 

강의를 하면서 이 내용을 설명하면 굉장히 많이 듣는 질문 중 하나가, axis = 0은 행을 나타내고, axis = 1은 열을 나타내는 것 아니냐라는 질문이었다.

틀린 말은 아니지만, 정확히는 axis는 계산 방향을 결정하는 것이다.

df.apply(max, axis = 0)의 경우에는 행 방향 (→)을 기준으로 계산하기 때문에 결과가 열벡터꼴이 나오는 것이고, df.apply(np.mean, axis = 1)의 경우에는 열 방향 (↓)을 기준으로 계산하기 때문에 결과가 행벡터꼴이 나오는 것이다.

굉장히 중요한 내용이기 때문에 다른 포스팅에서 자세히 정리해보도록 하자.

 

다양한 apply를 사용 방법 (3) 몇몇 컬럼에 대해 적용하고 싶을 때

이제 둘 이상의 컬럼을 활용해서 새로운 컬럼을 만드는 상황을 생각해보자.

예를 들어, A와 B라는 컬럼을 바탕으로 C = A + B라는 컬럼을 만드는 상황을 생각할 수 있다.

 

이러한 경우에는 보통 다음과 같이 구현한다.

df["C"] = df["A"] + df["B"]

그런데 간단한 유니버설 함수로 새로운 컬럼을 정의하기 어려운 경우에는 apply 함수를 사용할 수 있다.

이 경우에는 apply 함수의 입력으로 데이터프레임을 받도록 설계하면 된다.

def f(x):
	return x["A"] + x["B"]

 

그리고나서 다음과 같이 사용할 컬럼을 정의해준 뒤, axis = 1로 함수를 apply하면 된다.

df["C"] = df[["A", "B"]].apply(f, axis = 1)

 

다시 한 번 이야기하지만, 위의 예시는 설명을 위한 예시이고, 실제로는 유니버설 함수로 정의하기 어려운 경우에 사용한다. 

Comments