10 apply 메서드 활용¶
apply 메서드는 사용자가 작성한 함수를 한 번에 데이터프레임의 각 행과 열에 적용하여 실행할 수 있게 해주는 메서드입니다. 즉, 함수를 브로드캐스팅해야 하는 경우에 apply 메서드를 사용합니다. 물론 apply 메서드를 사용하지 않아도 for문을 이용하면 각 데이터에 함수를 적용할 수 있습니다. 하지만 대용량 데이터를 처리할 때는 apply 메서드가 for문보보다 더 빠르기 때문에 apply 메서드의 사용법은 반드시 알아두어야 합니다.
이 장을 보기 전에 알아두면 좋은 개념¶
- 반복문, 파이썬 리스트
10-1 간단한 함수 만들기¶
10-2 apply 메서드 사용하기 - 기초¶
10-3 apply 메서드 사용하기 - 고급¶
10-1 간단한 함수 만들기¶
이 책은 파이썬의 기초 지식을 어느 정도 아는 독자를 대상으로 집필되었습니다. 따라서 함수는 간단히 설명하고 넘어갑니다. 함수의 기본 구조는 다음과 같습니다.
# def my_funcktion():
그러면 제곱 함수와 n 제곱 함수를 직접 만들어보겠습니다. 제곱 함수와 n 제곱 함수는 10-2에서 apply 메서드와 함께 사용합니다. 여기에서 꼭 함수를 만들어보고 다음으로 넘어가세요
def my_sq(x):
return x ** 2
2. n 제곱 함수 my_exp는 다음과 같습니다.¶
def my_exp(x, n):
return x ** n
3. 과정 1~2에서 만든 함수를 사용해 볼까요?¶
print(my_sq(4))
16
print(my_exp(2, 4))
16
10-2 apply 메서드 사용하기 - 기초¶
다음 실습을 통해 apply 메서드의 사용법을 알아보겠습니다. apply 메서드로 브로드캐스팅한 결과가 올바른 값인지 확인하기 위해 시리즈, 데이터프레임의 기초 연산 결과와 비교하며 실습을 진행하겠습니다.
import pandas as pd
df = pd.DataFrame({'a':[10, 20, 30], 'b': [20, 30, 40]})
print(df)
a b 0 10 20 1 20 30 2 30 40
2.¶
다음은 앞에서 만든 제곱 함수(my_exp)를 사용하기 전에 a열을 제곱하여 얻을 결과입니다. 이 결괏값과 apply 메서드를 적용한 결괎값을 비교해 보겠습니다.
print(df['a'] ** 2)
0 100 1 400 2 900 Name: a, dtype: int64
3.¶
다음은 apply 메서드에 제곱 함수의 이름(my_sq)을 전달하여 시리즈의 모든 데이터에 제곱 함수를 적용한 것입니다. 이때 apply 메서드에 전달하는 함수(my_sq)가 1개의 인자를 받고록 구성되어 있다면 인잣값을 생략해야 합니다. 그러면 2개의 인자를 전달해야 할 경우에는 어떻게 해야 할까요?
sq = df['a'].apply(my_sq)
print(sq)
0 100 1 400 2 900 Name: a, dtype: int64
4.¶
이제 2개의 인자를 전달받아야 하는 n 제곱 함수(my_exp)와 apply 메서드를 함께 사용해 보겠습니다. apply 메서드의 첫 번째 인자에는 n 제곱 함수의 이름(my_exp)을 전달하고 두 번째 인자에는 n 제곱 함수의 두 번째 인자(n)를 전달합니다.
ex = df['a'].apply(my_exp, n = 2)
print(ex)
0 100 1 400 2 900 Name: a, dtype: int64
ex = df['a'].apply(my_exp, n = 3)
print(ex)
0 1000 1 8000 2 27000 Name: a, dtype: int64
5. 데이터프레임과 apply 메서드¶
이번에는 시리즈가 아니라 데이터프레임에 appply 메서드를 사용하는 방법을 알아보겠습니다. 먼저 데이터프레임을 다음과 같이 준비합니다.
df = pd.DataFrame({'a':[10, 20, 30], 'b': [20,30,40]})
print(df)
a b 0 10 20 1 20 30 2 30 40
6.¶
새로운 함수를 만들어보겠습니다. 이번에 사용할 함수는 1개의 값을 전달받아 출력하는 함수입니다.
def print_me(x):
print(x)
7.¶
이번에는 데이터프레임에 함수를 적용해야 하기 때문에 함수를 열 방향으로 적용할지 행 방향으로 적용할지 정해야 합니다. axis 인잣값을 0이나 1로 지정하면 함수를 열 또는 행 방향으로 적용할 수 있습니다.
print(df.apply(print_me, axis = 0))
0 10 1 20 2 30 Name: a, dtype: int64 0 20 1 30 2 40 Name: b, dtype: int64 a None b None dtype: object
print(df['a'])
0 10 1 20 2 30 Name: a, dtype: int64
print(df['b'])
0 20 1 30 2 40 Name: b, dtype: int64
8.¶
이번에는 3개의 인자를 입력받아 평균을 계산하는 함수를 사용해 보겠습니다.
def avg_3(x, y, z):
return ( x + y + z) / 3
9.¶
그런데 avg_3 함수를 apply 메서드에 전달하면 'avg_3 함수는 3개의 인잣값을 필요로 하는 함수인데 1개의 인잣값만 입력받았다.' 는 오류 메시지가 출력됩니다. 즉, avg_3 함수에 열단위 데이터(df['a'] 또는 df['b'])가 전달되었고 이 값을 avg_3 함수에서 1개의 인자로 인식한 것입니다. 따라서 avg_3 함수가 열 단위로 데이터를 처리할 수 있도록 수정해야 합니다.
print(df.apply(avg_3))
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-18-60357ad98728> in <module> ----> 1 print(df.apply(avg_3)) ~/.local/lib/python3.6/site-packages/pandas/core/frame.py in apply(self, func, axis, raw, result_type, args, **kwds) 7550 kwds=kwds, 7551 ) -> 7552 return op.get_result() 7553 7554 def applymap(self, func) -> "DataFrame": ~/.local/lib/python3.6/site-packages/pandas/core/apply.py in get_result(self) 183 return self.apply_raw() 184 --> 185 return self.apply_standard() 186 187 def apply_empty_result(self): ~/.local/lib/python3.6/site-packages/pandas/core/apply.py in apply_standard(self) 274 275 def apply_standard(self): --> 276 results, res_index = self.apply_series_generator() 277 278 # wrap results ~/.local/lib/python3.6/site-packages/pandas/core/apply.py in apply_series_generator(self) 303 for i, v in enumerate(series_gen): 304 # ignore SettingWithCopy here in case the user mutates --> 305 results[i] = self.f(v) 306 if isinstance(results[i], ABCSeries): 307 # If we have a view on v, we need to make a copy because TypeError: avg_3() missing 2 required positional arguments: 'y' and 'z'
10.¶
다음은 avg_3 함수가 열 단위로 데이터를 처리할 수 있도록 개선한 avg_3_apply 함수입니다. 개선한 함수를 apply 메서드에 적용하면 잘 동작하는 것을 알 수 있습니다.
def avg_3_apply(col):
x = col[0]
y = col[1]
z = col[2]
return(x + y + z ) / 3
print(df.apply(avg_3_apply))
a 20.0 b 30.0 dtype: float64
11.¶
앞의 과정에서는 데이터프레임의 개수가 3이라는 것을 알고 있다는 전제하에 avg_3_apply 함수를 작성했습니다. 하지만 일반적으로는 for문을 이용하여 오른쪽과 같이 작성합니다.
def avg_3_apply(col):
sum = 0
for item in col:
sum += item
return sum / df.shape[0]
12.¶
과정 11의 함수를 응용하면 행 방향으로 데이터를 처리하는 함수도 만들 수 있습니다. 마지막 return문의 df.shape[0]을 df.shape[1]로 바꾸면 됩니다.
def avg_2_apply(row):
sum = 0
for item in row:
sum += item
return sum / df.shape[1]
print(df.apply(avg_2_apply, axis=1))
0 5.0 1 10.0 2 15.0 dtype: float64
10 - 3 apply 메서드 사용하기 - 고급¶
이번에는 조금 더 큰 데이터를 사용하여 실습을 진행해 보겠습니다. 이번에 사용할 데이터는 seaborn라이브러리의 titanic 데이터 집합입니다.
import seaborn as sns
titanic = sns.load_dataset("titanic")
2.¶
다음은 titanic 데이터프레임의 데이터 정보를 출력한 것입니다.
print(titanic.info())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 survived 891 non-null int64 1 pclass 891 non-null int64 2 sex 891 non-null object 3 age 714 non-null float64 4 sibsp 891 non-null int64 5 parch 891 non-null int64 6 fare 891 non-null float64 7 embarked 889 non-null object 8 class 891 non-null category 9 who 891 non-null object 10 adult_male 891 non-null bool 11 deck 203 non-null category 12 embark_town 889 non-null object 13 alive 891 non-null object 14 alone 891 non-null bool dtypes: bool(2), category(2), float64(2), int64(4), object(5) memory usage: 80.6+ KB None
3.¶
다음은 누락값의 개수를 반환하는 count_missing 함수 입니다. 판다스의 isnull 메서드에 데이터프레임을 전달하면 누락값의 유무에 따라 True, False를 적용한 데이터프레임이 만들어집니다. 이값을 넘파이의 sum 메서드에 전달하면 누락값의 개수를 구할 수 있습니다.
import numpy as np
def count_missing(vec):
null_vec = pd.isnull(vec)
null_count = np.sum(null_vec)
return null_count
4.¶
다음은 apply 메서드에 count_missing 함수를 전달하여 얻은 결과입니다.
cmis_col = titanic.apply(count_missing)
print(cmis_col)
survived 0 pclass 0 sex 0 age 177 sibsp 0 parch 0 fare 0 embarked 2 class 0 who 0 adult_male 0 deck 688 embark_town 2 alive 0 alone 0 dtype: int64
5.¶
다음은 누락값의 비율을 계산하는 prop_missing 함수입니다. 과정 3에서 작설한 count_missing 함수를 이용해 데이터프레임의 누락값 개수를 구하고 size 속성을 이용해 데이터프레임의 전체 데이터 수를 구하려 나누면 누락값의 비율을 계산할 수 있습니다.
def prop_missing(vec):
num = count_missing(vec)
dem = vec.size
return num / dem
6.¶
다음은 apply 메서드에 prop_missing 함수를 적용한 것입니다.
pmis_col = titanic.apply(prop_missing)
print(pmis_col)
survived 0.000000 pclass 0.000000 sex 0.000000 age 0.198653 sibsp 0.000000 parch 0.000000 fare 0.000000 embarked 0.002245 class 0.000000 who 0.000000 adult_male 0.000000 deck 0.772166 embark_town 0.002245 alive 0.000000 alone 0.000000 dtype: float64
7.¶
과정 5에서 작성한 prop_missing 함수를 이용하면 누락값이 아닌 데이터의 비율도 구할 수 있습니다. 전체 비율(1)에서 누락값의 비율로 빼면 됩니다. 과정 5~6과 같은 방법으로 apply 메서드에 prop_complete 함수를 전달하여 결과를 확인해 보세요.
def prop_complete(vec):
return 1 - prop_missing(vec)
8. 데이터 프레임의 누락값 처리하기 - 행방향¶
이번에는 행 방향으로 데이터를 처리해 보겠습니다. 다음은 axis를 1로 설정하여 앞으로 만든 count_missing, prop_missing, prop_complete 함수를 행 방향으로 적용하여 실행 한 것입니다. 각 행의 누락값과 누락값의 비율, 누락값이 아닌 값의 비율을 잘 계산하고 있다는 것을 알 수있습니다.
cmis_row = titanic.apply(count_missing, axis = 1)
pmis_row = titanic.apply(prop_missing, axis =1 )
pcom_row = titanic.apply(prop_complete, axis = 1)
print(cmis_row.head())
0 1 1 0 2 1 3 0 4 1 dtype: int64
print(pmis_row.head())
0 0.066667 1 0.000000 2 0.066667 3 0.000000 4 0.066667 dtype: float64
print(pcom_row.head())
0 0.933333 1 1.000000 2 0.933333 3 1.000000 4 0.933333 dtype: float64
9.¶
다음은 누락값의 개수를 구하여 titanic 데이터프레임에 추가한 것입니다. 데이터프레임에 num_missing 열이 추가된 것을 알 수 있습니다.
titanic['num_missing'] = titanic.apply(count_missing, axis = 1)
print(titanic.head())
survived pclass sex age sibsp parch fare embarked class \ 0 0 3 male 22.0 1 0 7.2500 S Third 1 1 1 female 38.0 1 0 71.2833 C First 2 1 3 female 26.0 0 0 7.9250 S Third 3 1 1 female 35.0 1 0 53.1000 S First 4 0 3 male 35.0 0 0 8.0500 S Third who adult_male deck embark_town alive alone num_missing 0 man True NaN Southampton no False 1 1 woman False C Cherbourg yes False 0 2 woman False NaN Southampton yes True 1 3 woman False C Southampton yes False 0 4 man True NaN Southampton no True 1
10.¶
과정 9에서 누락값이 있는 데이터를 데이터프레임에 추가했기 때문에 누락값이 있는 데이터만 따로 모아서 볼 수도 있습니다. 다음은 누락값이 2개 이상인 데이터를 추출한 것입니다.
print(titanic.loc[titanic.num_missing > 1, :].sample(10))
survived pclass sex age sibsp parch fare embarked class \ 598 0 3 male NaN 0 0 7.2250 C Third 28 1 3 female NaN 0 0 7.8792 Q Third 888 0 3 female NaN 1 2 23.4500 S Third 643 1 3 male NaN 0 0 56.4958 S Third 612 1 3 female NaN 1 0 15.5000 Q Third 656 0 3 male NaN 0 0 7.8958 S Third 832 0 3 male NaN 0 0 7.2292 C Third 425 0 3 male NaN 0 0 7.2500 S Third 410 0 3 male NaN 0 0 7.8958 S Third 547 1 2 male NaN 0 0 13.8625 C Second who adult_male deck embark_town alive alone num_missing 598 man True NaN Cherbourg no True 2 28 woman False NaN Queenstown yes True 2 888 woman False NaN Southampton no False 2 643 man True NaN Southampton yes True 2 612 woman False NaN Queenstown yes False 2 656 man True NaN Southampton no True 2 832 man True NaN Cherbourg no True 2 425 man True NaN Southampton no True 2 410 man True NaN Southampton no True 2 547 man True NaN Cherbourg yes True 2
마무리하며¶
이 장에서는 여러 가지 실습을 통해 apply 메서드가 왜 데이터 분석에 유용한지 알아보았습니다. 내장 함수의 기능도 훌륭하지만 때로는 나만의 함수를 만들어 데이터 처리에 사용하는 것이 더 편리할 수도 있기 때문에 apply 메서드의 사용법은 반드시 알아두어야 합니다.
출처 : Do it 데이터 분석을 위한 판다스 입문¶
'Do it 판다스 입문' 카테고리의 다른 글
Do it pandas Chapter 12. 시계열 데이터 (0) | 2021.04.01 |
---|---|
Do it pandas Chapter 11. 그룹 연산 (0) | 2021.03.29 |
Do it pandas Chapter 9. 문자열 처리하기 (1) | 2021.03.28 |
Do it pandas Chapter 8. 판다스 자료형 (0) | 2021.03.28 |
Do it pandas Chapter 7. 깔끔한 데이터 (0) | 2021.03.28 |