Python pandas에는 DataFrame 내 숫자형 변수의 결측값 여부를 확인(rfriend.tistory.com/260)하거나 결측값을 채우는(rfriend.tistory.com/262) 다양하고 간단한 함수를 제공합니다.

 

이번 포스팅에서는 범주형 자료의 결측값을 각 범주별 구성비율에 비례하여 확률적으로 채우는 방법을 소개하겠습니다. 가령, 'A' 범주가 50%, 'B' 범주가 30%, 'C' 범주가 20%를 구성하고 있으며, 결측값 발생 시 각 범주별 구성비율에 따라서 확률적으로 결측값을 채워넣어보겠습니다.

(엄밀히 말하면 공백 '', '   ' 나 'None' 등은 결측값이라고 말하기 곤란합니다만... 문자열 '' 을 결측값이라고 정의하겠습니다.) 

 

댓글로 위의 요건을 수행하는 Python 코드에 대한 질문을 남겨주신 분이 계셔서 그때 답변 달았던 내용을 포스팅으로 옮겨보았습니다.

 

절차는 2단계로 이루어집니다.

 

(1) 균등분포(uniform distribution)로 부터 난수를 생성하여 각 범주의 구성비율에 따라 결측값을 채우는 값을 지정하는 사용자 정의 함수 생성

(2) for loop 반복문으로 범주형 변수의 결측값일 경우 (1)번 사용자 정의 함수를 실행하여 결측값 채우기

 

 

 

먼저, 예제로 사용할 문자열로 구성된 칼럼에 결측값('')을 가진 간단한 DataFrame을 만들어보겠습니다.

 

## DataFrame with missing value in categorical variable
import pandas as pd

df = pd.DataFrame({'x1': ['A', 'A', 'C', '', 'A', 'B', 'A', 'B', 'A', 'A', 
                          'C', '', 'A', 'A', '', 'A', 'B', 'A', 'C', 'A', '']})
                          
print(df)
[Out]
   x1
0   A
1   A
2   C
3      <-- missing
4   A
5   B
6   A
7   B
8   A
9   A
10  C
11     <-- missing
12  A
13  A
14     <-- missing
15  A
16  B
17  A
18  C
19  A
20     <-- missing
20   

 

(1) 균등분포(uniform distribution)로 부터 난수를 생성하여 각 범주의 구성비율에 따라 결측값을 채우는 값을 지정하는 사용자 정의 함수 생성

 

균등분포(uniform distribution)는 구간 [min, max] 에서 값이 균등하게 퍼져있는 집단, 일어날 확률이 균등한 분포를 말합니다. 가령, 구간 [0, 1] 에서 임의로 난수를 생성할 경우 그 값이 뽑힐 확률은 모두 1로서 동일하게 됩니다. (만약 구간 [0, 10] 에서 난수를 생성할 경우 각 값이 뽑힐 확률은 모두 0.1로서 동일하게 됨).

 

numpy 의 random.uniform(min, max, size) 메소드를 사용해서 균등분포로 부터 난수를 생성할 수 있습니다. 아래는 구간 [0. 1] 에서 난수 10개를 생성한 예입니다.

 

import numpy as np

np.random.uniform(0, 1, 10)

[Out]
array([0.56947875, 0.95692566, 0.9978566 , 0.99739644, 0.00885555,
       0.92047312, 0.00443685, 0.12121749, 0.46886965, 0.32319941])

 

그럼, 이제 모집단에서 각 범주가 차지하는 비율이 'A' 범주(category, class)는 50%, 'B' 범주는 30%, 'C' 범주는 20%라고 하고, 이 각 범주별 구성비율에 비례해서 범주의 결측치를 확률적으로, 임의로 채우는 사용자 정의 함수를 정의해보겠습니다.

 

## 'A' 0.5 : 'B' 0.3 : 'C' 0.2
def cat_fill_na():
    # generate random number from uniform distrubution
    rnd_num = np.random.uniform(0, 1, 1)
    
    if rnd_num > 0.8:
        x = 'C'
    elif rnd_num > 0.5:
        x = 'B'
    else:
        x = 'A'
    
    return x

 

 

(2) for loop 반복문으로 범주형 변수의 결측값일 경우 (1)번 사용자 정의 함수를 실행하여 결측값 채우기 

 

이제 위의 (1)번에서 정의한 사용자 정의 함수를 사용해서 범주형 변수의 관측치들 중에서 결측값('')의 경우 확률적으로 범주 값을 채워넣어보겠습니다.

for i in range(df.shape[0]):
    if df['x1'].iloc[i] == '':
        df['x1'].iloc[i] = cat_fill_na
        
df
[Out]
x1
0	A
1	A
2	C
3	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
4	A
5	B
6	A
7	B
8	A
9	A
10	C
11	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
12	A
13	A
14	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
15	A
16	B
17	A
18	C
19	A
20	B  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)

 

 

---------------------------------------------------------------------------------------------

참고로, 아래처럼 코드를 수행하면 단 한번만 균등분포로부터 난수를 발생하여 해당 난수값이 포함된 단 하나의 범주값을 모든 결측값에 채워넣게 되므로 이번 요건에는 적합하지 않습니다.

 

## define UDF
def cat_fill_na(x):
    if x == '':
        rnd_int = random.randint(1, 10)
        if rnd_int == 9:
            x = 'B'
        elif rnd_int == 10:
            x = 'C'
        else:
            x = 'A'
    else:
        x = x
        
    return x
    
## run UDF    
df2 = df.apply(lambda x: cat_fill_na(x['x1']), axis=1)

df2
0     A
1     A
2     C
3     A  <-- filled with the same value
4     A
5     B
6     A
7     B
8     A
9     A
10    C
11    A   <-- filled with the same value
12    A
13    A
14    A   <-- filled with the same value
15    A
16    B
17    A
18    C
19    A
20    A   <-- filled with the same value
dtype: object

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,