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

 

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

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

 

Posted by R Friend Rfriend

댓글을 달아 주세요

  1. 2021.02.07 19:51  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • R Friend Rfriend 2021.02.08 11:43 신고  댓글주소  수정/삭제

      안녕하세요.

      3개 범주를 가지는 multi-class classification 문제인건가요?
      3개 범주를 가지는 y값에 대해서 one-hot encoding을 미리 해두신거지요?

      RNN 모델에서 input_shape 부분은 문제 없어보이구요, 에러 메시지를 봐서는 마지막 부분에서 y class 개수(3개 class)만큼으로 차원을 줄여주면 될거 같습니다. compile 들어가기 직전에 Shapes (None, 3)이고 activation='softmax'인 Dense layer를 추가해보실래요?

      model = Sequential()
      model.add(LSTM(n_hidden, input_shape=(n_timesteps, n_features)))
      model.add(Dropout(0.5))
      model.add(Dense(100, activation='relu'))
      model.add(Dense(n_outputs, activation='softmax')) # <--- ** 이거 추가해보세요.**
      model.compile(loss='categorical_crossentropy',optimizer='rmsprop',metrics=['accuracy'])
      model.fit(trainX, trainy,batch_size=batch_size,epochs=epochs)


      데이터 없이 코드만 보고 답변 달려니 이게 제대로 작동할런지 잘 모르겠습니다.
      혹시 안되면 데이터 링크나 추가 에러메시지 남겨주세요.

  2. 2021.02.15 18:33  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • R Friend Rfriend 2021.02.15 18:51 신고  댓글주소  수정/삭제

      안녕하세요.

      지난번 에러났던 부분은 이제 잘 수행된다고 하니 다행입니다.

      "list index out of range" 에러는 리스트에서 값을 인덱싱해오려고 할 때 인덱스 범위를 벗어났다는 에러 메시지입니다.

      남겨주신 코드 중에서
      prediction = model.predict(ex1) 에서 에러가 나는가요?

      아니면, np.argmax(prediction[0]) 에서 에러가 나는가요?

      'list index out of range' 의 에러 메시지만 보면 두번째의 np.argmax(prediction[0]) 에서 prediction[0] 부분이 문제인거 같은데요, 이는 prediction = model.predict(ex1) 이 제대로 정상작동 안해서 prediction 객체가 비어있어서(???) 그런게 아닐까 추측을 해봅니다.

      predict() 함수에 예제 데이터를 집어넣기 전에, 먼저 training 단계에서 수행했던 데이터 전처리를 동일하게 수행해주셔야 합니다.

      데이터나 전처리에 대해서 설명을 안해주셔서 잘 모르겠습니다만, 가령, 이미지 데이터 분류 문제라면 이미지 크기 조정을 한다든지, 0~1 사이 값으로 정규화를 한다든지, 칼라/흑백 여부에 따라서 축 개수를 변경한다든지 ... 전처리 과정이 필요할텐데요, training 단계에서 사용했던 전처리를 파이프라인으로 만들어서 prediction 할때도 그대로 수행해줘봐 주세요.

      그리고, 앞으로 질문을 남기실때는 다른 분들도 참고할 수 있도록 가급적이면 '비밀댓글' 말고 '공개댓글'로 해주시면 좋겠습니다.

  3. 2021.02.15 19:46  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  4. 머신러닝 공부중 2021.02.15 21:25  댓글주소  수정/삭제  댓글쓰기

    답변 감사드립니다! 다시 해보겠습니다.
    또 한가지 궁금한점이 있습니다. 혹시 텍스트 파일이나 csv 파일을 이용하여 숫자 데이터들 오버샘플링 하거나 값들을 전체적으로 조금씩 늘리거나 줄여주는 방법에 대한 예제가 있는지 궁금합니다.

  5. 머신러닝 공부중 2021.02.17 16:08  댓글주소  수정/삭제  댓글쓰기

    선생님! 아직 비슷한값으로 데이터셋 증가시키는건 못했지만, 전처리와 cnn,rnn으로 예측하게 하는것은 성공했습니다! 그런데 예측과정에서 3가지 라벨값이면 3가지 값이 출력되는데 제가 예전에 봤던 예제는 %로 나오고 라벨별로 확률을 출력할 수 있게 되어있었는데 그런것은 따로 predict 함수가 있는것인가요?

    • R Friend Rfriend 2021.02.17 16:29 신고  댓글주소  수정/삭제

      안녕하세요.
      CNN, RNN 모두 예측에 성공하셨다니 축하드립니다.

      multi-class classification 모델링을 하셨다면 model.predict() 함수로 예측을 했을 때 multi-class 개수만큼의 확률이 array 로 해서 반환이 될거예요. 그리고 그중에서 argmax() 로 최대 확률값인 위치의 라벨을 분류 예측값으로 사용하구요.

      원하시는 아웃풋 포맷이 정확하게 어떤 것인지 모르겠는데요, 아마 원리는 predict() 함수의 결과로 반환되는 array 에서 indexing 해서 formatting 해서 프린트 하는 것이거 같네요.

      가령 3개 class 의 라벨이 labels = ['dog', 'cat', 'horse'] 의 순서대로 라고 하고, 어떤 input에 대한 예측확률 배열이 prob = array([0.1, 0.7, 0.2]) 라고 하면, idx = argmax(prob) 는 1 이고,

      pred_label = labels[idx]
      pred_prob = 100*prob[idx]
      print('{pred_label}일 확률이 {pred_prob}% 입니다".foramt(pred_label=pred_label, pred_prob=pred_prob)

      이런식으로 해주면,
      "'cat' 일 확률이 70% 입니다."
      라는 식으로 프린트해줄 수 있겠네요.

      foramt 을 지정해서 문자열 프린트 하는 것은 https://rfriend.tistory.com/328 를 참고하세요.

    • 머신러닝 공부중 2021.02.17 18:42  댓글주소  수정/삭제

      와! 선생님이 알려주신 방법이 맞는것 같습니다! 그런데 제가 계속 실험해본 결과
      제 predictions 배열값을 0번째 값 밖에 출력을 하지 못하고
      index 1 is out of bounds for axis 0 with size 1
      라는 오류가 나옵니다..

    • R Friend Rfriend 2021.02.17 18:45 신고  댓글주소  수정/삭제

      predict_classes() 함수 대신에 predict_proba() 함수 또는 predict() 함수를 이용하면 확률을 반환할거예요.

    • 머신러닝 공부중 2021.02.17 20:39  댓글주소  수정/삭제

      스승님 predict_proba를 사용하니까 됩니다! 그리고 predictions[1] 이 아니라 predictions[0,0] 이런식으로 해야 값이 나오네요. 혹시 model.predict 상에서 라벨값 뽑아내는 방법은 없는거죠??

    • R Friend Rfriend 2021.02.17 20:49 신고  댓글주소  수정/삭제

      라벨 클래스 들어있는 리스트 하나 만들고, no.argmax()로 구한 포지션 인덱스로 인덱싱 해오시면 될거예요

    • 머신러닝 공부중 2021.02.18 13:35  댓글주소  수정/삭제

      np.argmax()로 최대값은 가져와집니다!
      최대값 1개 출력 이외에 확률 높은것부터 차례대로 정렬 하는 방법 있을까요? sort는 안먹히는것 같습니다.

    • R Friend Rfriend 2021.02.18 13:40 신고  댓글주소  수정/삭제

      np.argsort() 가 순서 인덱스 반환해요

  6. 머신러닝 공부중 2021.02.20 23:49  댓글주소  수정/삭제  댓글쓰기

    선생님 주말 잘 보내고 계신가요? 항상 답변주셔서 감사합니다. 제가 더 세분화시켜서 예측을 해보고 싶은데요. 학습시키고 예측까지 인풋 사이즈와 아웃풋 사이즈가 이미지파일뿐만 아니라 텍스트파도 일도 같아야 하는것 같은데... 현재는 csv 또는 txt파일에 들어있는 숫자들로 예측을 하려고 하고있는데도 행과 열의 크기를 맞춰야 예측이 가능합니다. 혹시 크기 상관 없이 예측할 수 있는 방법있을까요?
    그리고 제가 300행의 데이터를 이용한다면 각 30행씩 나눠서
    1~30행은 10%확률 31~60행은 20%확률 이런식으로 나오게끔 가능할까요?

    • R Friend Rfriend 2021.02.21 16:47 신고  댓글주소  수정/삭제

      안녕하세요.

      (1) size를 조정하든, 차원을 조정하든 해서 input, output의 shape은 맞춰줘야 합니다.

      (2) 난수를 생성해서 샘플링하면 되겠네요. 아래 링크 참조하세요. (요건에 맞게 사용자정의함수 새로 짜주셔야 합니다. 아래는 난수 발생 참고용도로 사용하세요)

      https://rfriend.tistory.com/520
      https://rfriend.tistory.com/613

    • 머신러닝 공부중 2021.02.23 14:43  댓글주소  수정/삭제

      이 방법으로 해보니 분할되는데 0~20 20~40은 안되고 0~20 0~40 0~60 이렇게 되는것같습니다

    • R Friend Rfriend 2021.02.23 15:07 신고  댓글주소  수정/삭제

      코드를 남겨주시면 한번 봐볼께요.

    • 머신러닝 공부중 2021.02.23 17:14  댓글주소  수정/삭제

      코드는

      trainX, testX, trainy, testy = train_test_split(trainX, trainy, train_size=0.2, shuffle=False, random_state=1004)
      이렇게 추가하였고

      (60, 500, 50) (60, 3) (240, 500, 50) (240, 3)

      이렇게 맨 앞의 데이터파일 갯수부분만 비율적으로 줄어들고,
      두번째 세번째가 행과 열 데이터인데 이부분은 변화 없었습니다.

    • R Friend Rfriend 2021.02.23 17:29 신고  댓글주소  수정/삭제

      아래 포스팅에 여러가지 방법으로 난수 발생시켜서 training, test set 분할하는 예제들이 있습니다.

      이 코드들을 참고하셔서 분석 목적에 맞게 직접 코드를 짜보시기 바랍니다. 원리는 난수 발생시키고, 원하는 확률만큼 구간에 할당해주는 것입니다. 아래 포스팅의 예제는 원리, 함수를 참고하라는 것이구요, 그대로 써도 된다는 뜻이 아닙니다. 인풋 데이터 형태와 데이터 처리 방식이 달라지면 코드는 그에 맞게 새로 짜줘야 합니다.

      https://rfriend.tistory.com/519

  7. 머신러닝 공부중 2021.02.23 14:40  댓글주소  수정/삭제  댓글쓰기

    선생님! 만약 데이터셋으로 사용할 csv 데이터 파일이 몇십~몇백개 있고 이 파일들 안의 열들은 고정되어있지만, 행의 개수가 모두 다르다면 행의 개수를 임의로 고정시켜주고 부족한 데이터는 채워주고 넘치는 데이터는 줄여줘야하는데 데이터 훼손이 가지 않게 하려면 어떤 방법이 최적의 방법인지 궁금합니다.