Pandas | 매핑

pandas
Author

강신성

Published

2023-12-14

apply, map, applymap에 대하여 자세히 알아보자.

1. 라이브러리 imports

import pandas as pd
import numpy as np

#!pip install pandas -U  ## 코랩 기본 판다스가 1.5.3이라 업데이트 해야 해당되는 내용임... jupyter lab에는 정상적으로 작동함...

2. apply

A. Motive


- 아래와 같은 상황이 있었다.

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. 변환함수(벡터 입력, 벡터 출력) : 표준화(StandardScaler()?), 정렬(sort_values())
  3. 집계함수(벡터 입력, 스칼라 출력) : 평균, 최댓값

여기서 쓸모있는 건 “1”뿐이다.

# 예제 1 s.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]

# 예제 2 s.apply + 벡터 입력 / 스칼라 출력

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]

# 예제 3 s.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” 모두이다.

# 예제 1 df.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: ignored

불가하다.

- apply() 작동기전 :

[(lambda x : 'pos' if x > 0 else 'neg')(df[i]) for i in df]

# 예제 2 df.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+1)
X Y
0 1.1 0.9
1 1.2 0.8
2 1.3 0.7

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

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

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

[(lambda x : x+1)(df[i]) for i in df]
[0    1.1
 1    1.2
 2    1.3
 Name: X, dtype: float64,
 0    0.9
 1    0.8
 2    0.7
 Name: Y, dtype: float64]

# 예제 3 df.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

# 예제 4 + 벡터 입력, 스칼라 출력(집계함수) 2 : axis = 0, 1

df.apply(np.sum, axis = 1)
0    0.0
1    0.0
2    0.0
dtype: float64

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

# 예제 5 df.apply + 벡터 입력, 벡터 출력(axis 옵션에 따른 차이)

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

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

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    [-1.261, 0.145]
2    [-0.222, 0.328]
dtype: object

각 row 별로 함수를 걸어주게 되면 결과가 판다스 시리즈로 나오지 않음… 그걸 다시 묶으면 데이터프레임이 나올 수가 없게 된다.

df.apply(lambda x: x*0+np.sort(x), axis=1) # x*0을 추가하여 시리즈 형식을 유지해줬다. 그다지 안중요한 트릭..
X Y
0 1.068 3.285
1 -1.261 0.145
2 -0.222 0.328

시리즈로 묶어서 s.apply()로 하는 게 가장 적합하다.

3. map

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

### A. s.map()

- s.apply()와 거의 똑같다.

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

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

# 예제 1 쓰는 거. s.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

# 예제 2 s.map + 스칼라 입력, 스칼라 출력(굳이 쓸 일 없음. apply써도 동일)

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

# 예제 3 s.map + 벡터 입력, 스칼라 출력 > 가능은 한데, 사실 스칼라 입력, 스칼라 출력으로 해석해야 하고, 쓸모없음.

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

# 예제 4 쓰는 거. s.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. 변환함수(스칼라 입력, 스칼라 출력) : 로그, 제곱
  2. 집계함수(벡터 입력, 스칼라 출력)

“1”만 쓸모가 있다. 여기서 df.map(변환함수)df.applymap(변환함수)와 기능이 같다.

# 예제 1 df.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)  ## 왜 코랩은 map 안됨???
A B
0 2143 0
1 2143 3456

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

df = pd.DataFrame({'A':np.random.randn(5), 'B':np.random.randn(5)+5})
df
A B
0 0.385620 8.123319
1 2.016558 5.388919
2 -0.187575 7.458609
3 -0.277671 4.883501
4 -0.070624 4.129147
df.map(np.sort) # 불가능..
AxisError: ignored

# 예제 3 df.map + 벡터 입력, 스칼라 출력(집계함수) >> 사실상 스칼라 입력, 스칼라 출력. 의미없음

df = pd.DataFrame({'A':np.random.randn(5), 'B':np.random.randn(5)+5})
df
A B
0 -2.705998 3.788923
1 0.944862 5.549552
2 -0.405228 4.180423
3 0.594811 4.114496
4 0.004424 4.233611
df.map(np.mean)
A B
0 -2.705998 3.788923
1 0.944862 5.549552
2 -0.405228 4.180423
3 0.594811 4.114496
4 0.004424 4.233611

# 예제 4 df.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: ignored