13wk-2: Pandas – apply, map, applymap

pandas
Author

최규빈

Published

November 29, 2023

1. 강의영상

2. Imports

import numpy as np
import pandas as pd

3. apply

A. Motive

- 아래와 같은 상황이 있었다. (05wk-2의 숙제)

df = pd.DataFrame({'A':[1,2,3,4]})
df
A
0 1
1 2
2 3
3 4
df[['A']].apply(np.mean)
A    2.5
dtype: float64
df['A'].apply(np.mean)
0    1.0
1    2.0
2    3.0
3    4.0
Name: A, dtype: float64

B. s.apply()

- 가능한 형태는 아래와 같다.

  1. 변환함수(스칼라입력,스칼라출력): 로그, 제곱
  2. 변환함수(벡터입력,벡터출력): 표준화, 정렬
  3. 집계함수(벡터입력,스칼라출력): 평균, 최대값

쓸모있는건 1 뿐이다.

# 예제1s.apply + 스칼라입력, 스칼라출력

s = pd.Series([1,2,3])
s
0    1
1    2
2    3
dtype: int64
s.apply(lambda x: -x)
0   -1
1   -2
2   -3
dtype: int64

이건 사실 아래의 동작으로 이해하면 된다.

1 -> -1 
2 -> -2
3 -> -3 

코드로는 아래와 같은 느낌

[(lambda x: -x)(i) for i in s]
[-1, -2, -3]

#

# 예제2s.apply + 벡터입력/스칼라출력(집계함수) // 가능은한데 사실상 스칼라입력,스칼라출력으로 해석해야함

s = pd.Series([1,2,3])
s
0    1
1    2
2    3
dtype: int64
s.apply(np.sum) # ??
0    1
1    2
2    3
dtype: int64
  • 에러는 안나지만 원하는 동작은 아님

이것은 사실 아래의 동작으로 이해할 수 있다.

1 -> sum(1) = 1 
2 -> sum(2) = 2
3 -> sum(3) = 3

코드로는 아래의 느낌

[np.sum(i) for i in s]
[1, 2, 3]

#

# 예제3s.apply + 벡터입력/벡터출력 // 가능은 한데 사실상 스칼라입력,스칼라출력 함수로 해석해야함

s = pd.Series([1,2,3])
s
0    1
1    2
2    3
dtype: int64
s.apply(lambda x: x-np.mean(x))
0    0.0
1    0.0
2    0.0
dtype: float64
  • 에러는 안나지만 원하는 동작은 아님

이것은 사실 아래의 동작으로 이해할 수 있다.

1 -> 1-mean(1) = 0 
2 -> 2-mean(2) = 0
3 -> 3-mean(3) = 0

코드로는 아래의 느낌

[i-np.mean(i) for i in s]
[0.0, 0.0, 0.0]

#

C. df.apply()

- 가능한 형태는 아래와 같다.

  1. 변환함수(벡터입력,벡터출력): 표준화, 정렬
  2. 집계함수(벡터입력,스칼라출력): 평균, 최대값

쓸모있는건 1,2 모두이다.

# 예제1df.apply + 스칼라입력, 스칼라출력 (불가능)

df = pd.DataFrame({'X':[0.1,0.2,0.3],'Y':[-0.1,-0.2,-0.3]})
df
X Y
0 0.1 -0.1
1 0.2 -0.2
2 0.3 -0.3
df.apply(lambda x: 'pos' if x>0 else 'neg')
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

# 예제2df.apply + 스칼라입력,스칼라출력? 벡터입력,벡터출력!!

df = pd.DataFrame({'X':[0.1,0.2,0.3],'Y':[-0.1,-0.2,-0.3]})
df
X Y
0 0.1 -0.1
1 0.2 -0.2
2 0.3 -0.3
df.apply(lambda x: x**2) # 이건 스칼라입력, 스칼라출력 아니고 벡터입력 벡터출력으로 컴퓨터가 해석함
X Y
0 0.01 0.01
1 0.04 0.04
2 0.09 0.09

이것은 사실 아래의 동작으로 이해할 수 있다.

df['X'] -> (df['X'])**2
df['Y'] -> (df['Y'])**2

코드로는 아래의 느낌이다.

[(lambda x: x**2)(df[i]) for i in df]
[0    0.01
 1    0.04
 2    0.09
 Name: X, dtype: float64,
 0    0.01
 1    0.04
 2    0.09
 Name: Y, dtype: float64]

#

# 예제3df.apply + 벡터입력,스칼라출력(집계함수)

df = pd.DataFrame({'X':[0.1,0.2,0.3],'Y':[-0.1,-0.2,-0.3]})
df
X Y
0 0.1 -0.1
1 0.2 -0.2
2 0.3 -0.3
df.apply(np.sum)
X    0.6
Y   -0.6
dtype: float64

#

# 예제4df.apply + 벡터입력,스칼라출력(집계함수)

df = pd.DataFrame({'X':[0.1,0.2,0.3],'Y':[-0.1,-0.2,-0.3]})
df
X Y
0 0.1 -0.1
1 0.2 -0.2
2 0.3 -0.3
df.apply(np.sum,axis=1)
0    0.0
1    0.0
2    0.0
dtype: float64

s.apply에서는 axis가 유효한 인자가 아니지만 df.apply에서는 axis가 유효한 입력이고 디폴트값은 0이다.

#

# 예제5df.apply + 벡터입력,벡터출력

df = pd.DataFrame({'X':[1,2,3],'Y':[4,5,6]})
df
X Y
0 1 4
1 2 5
2 3 6
df.apply(lambda x: x-np.mean(x))
X Y
0 -1.0 -1.0
1 0.0 0.0
2 1.0 1.0
df.apply(lambda x: x-np.mean(x),axis=1)
X Y
0 -1.5 1.5
1 -1.5 1.5
2 -1.5 1.5

#

# 예제6df.apply + 벡터입력, 벡터출력

df = pd.DataFrame({'X':[ 3.285,  0.328, -1.261],'Y':[ 1.068,  0.145, -0.222]})
df
X Y
0 3.285 1.068
1 0.328 0.145
2 -1.261 -0.222
df.apply(np.sort)
X Y
0 -1.261 -0.222
1 0.328 0.145
2 3.285 1.068
df.apply(np.sort, axis=1)
0      [1.068, 3.285]
1      [0.145, 0.328]
2    [-1.261, -0.222]
dtype: object
df.apply(lambda x: x*0+np.sort(x), axis=1) # 그다지 안중요한 트릭..
X Y
0 1.068 3.285
1 0.145 0.328
2 -1.261 -0.222

#

4. map

- 그냥 모든 원소에 동일적용

A. s.map()

- 가능한 형태는 아래와 같다.

  1. 변환함수(스칼라입력,스칼라출력): 로그, 제곱
  2. 변환함수(벡터입력,벡터출력): 표준화, 정렬
  3. 집계함수(벡터입력,스칼라출력): 평균, 최대값
  4. 딕셔너리

쓸모있는건 1,4 이다. 특히 4는 특정상황에서 매우 쓸모있음

# 예제1s.map + 스칼라입력,스칼라출력

s = pd.Series(['A','B','B','B','A'])
s
0    A
1    B
2    B
3    B
4    A
dtype: object
s.map(lambda x: x.lower())
0    a
1    b
2    b
3    b
4    a
dtype: object

#

# 예제2s.map + 스칼라입력,스칼라출력

s = pd.Series([1,3,4,2])
s
0    1
1    3
2    4
3    2
dtype: int64
s.map(lambda x: x**2)
0     1
1     9
2    16
3     4
dtype: int64

#

# 예제3s.map + 벡터입력,스칼라출력 // 가능은한데 사실 스칼라입력,스칼라출력으로 해석해야함

s = pd.Series([1,3,4,2])
s
0    1
1    3
2    4
3    2
dtype: int64
s.map(np.sum)
0    1
1    3
2    4
3    2
dtype: int64

#

# 예제4s.map + 벡터입력,벡터출력 // 가능은한데 사실 스칼라입력,스칼라출력으로 해석해야함

s = pd.Series([1,3,4,2])
s
0    1
1    3
2    4
3    2
dtype: int64
s.map(lambda x: x-np.mean(x))
0    0.0
1    0.0
2    0.0
3    0.0
dtype: float64

#

# 예제5s.map + 딕셔너리

s = pd.Series(['A','B','B','B','A'])
s
0    A
1    B
2    B
3    B
4    A
dtype: object
s.map({'A':'A+','B':'B0'})
0    A+
1    B0
2    B0
3    B0
4    A+
dtype: object

#

B. df.map() = df.applymap()

코랩실습자를 위한 주의사항1

코랩에서 실습할경우

df.map()

이 동작하지 않습니다. 대신 아래와 같이 applymap이 동작합니다.

df.applymap()

이것은 코랩에서 기본으로 설치되어있는 pandas의 버전이 너무 낮아서 생기는 문제입니다. 따라서 코랩을 쓰시는 분들은 아래의 강의노트들을 df.applymap()으로 바꿔서 실습하시기 바랍니다.

코랩실습자를 위한 주의사항2

만약 코랩에서도 df.map()을 사용하시려면 pandas를 높은버전으로 새로 설치하고 사용하시면 됩니다. 즉

1. 코랩커널재시작 (컴퓨터 다시 할당)
2. `import pandas as pd` 를 하기 전에 `!pip install pandas -U`를 이용하여 판다스를 최신버전으로 재설치 
3. 판다스를 임포트 하고 (`import pandas as pd`) 실습

와 같이 하시면 됩니다.

- 가능한 형태는 아래와 같다.

  1. 변환함수(스칼라입력,스칼라출력): 로그, 제곱
  2. 집계함수(벡터입력,스칼라출력)

1만 쓸모있다. 여기에서 df.map(변환함수) 꼴은 사실 df.applymap(변환함수)와 가능이 같다.

# 예제1df.map + 스칼라입력,스칼라출력

df = pd.DataFrame({'A':[2143,2143],'B':['-',3456]})
df
A B
0 2143 -
1 2143 3456
df.map(lambda x: 0 if x == '-' else x)
A B
0 2143 0
1 2143 3456

#

# 예제2df.map + 벡터입력,벡터출력 // 불가능해

df = pd.DataFrame({'A':np.random.randn(5), 'B':np.random.randn(5)+5})
df
A B
0 -0.474722 4.616944
1 -0.123587 5.941885
2 1.011905 2.960560
3 0.306291 3.769737
4 -1.231884 6.354306
df.map(np.sort) # 불가능..
AxisError: axis -1 is out of bounds for array of dimension 0

#

# 예제3df.map + 벡터입력,스칼라출력(집계함수) // 가능하긴한데 사실 스칼라입력,스칼라출력으로 해석해야함

df = pd.DataFrame({'A':np.random.randn(5), 'B':np.random.randn(5)+5})
df
A B
0 -0.331755 4.738557
1 1.064457 3.152778
2 1.096634 2.535014
3 0.586018 5.692224
4 -1.619656 5.566970
df.map(np.mean) # 사실상 스칼라입력,스칼라출력으로 봐야함
A B
0 -0.331755 4.738557
1 1.064457 3.152778
2 1.096634 2.535014
3 0.586018 5.692224
4 -1.619656 5.566970

#

# 예제4df.map + 딕셔너리 // 불가능

df = pd.DataFrame({'guebin':[0,1,0,1,0,1],'hynn':[0,1,1,1,1,1]})
df
guebin hynn
0 0 0
1 1 1
2 0 1
3 1 1
4 0 1
5 1 1
df.map({0:'fail',1:'pass'})
TypeError: the first argument must be callable

#

5. HW

아래의 자료가 있다고 하자.

df = pd.DataFrame({'guebin':[0,1,0,1,0,1],'hynn':[0,1,1,1,1,1]})
df
guebin hynn
0 0 0
1 1 1
2 0 1
3 1 1
4 0 1
5 1 1

이 자료를 lambdadf.applymap을 이용하여 아래와 같이 변경하라.

#
guebin hynn
0 fail fail
1 pass pass
2 fail pass
3 pass pass
4 fail pass
5 pass pass