본문 바로가기
머신러닝/아웃라이어, 결측치

인코딩을 통한 명목형 변수 knnimputing

by 혜 림 2021. 4. 13.

(150 000, 6) 중에서 3,000 개의 결측치 셀 

 

 

import pandas as pd
import numpy as np
import category_encoders as ce

from sklearn.impute import KNNImputer
from sklearn.preprocessing import RobustScaler

#######################작업 일지####################
## 하나의 변수만 missing일 때만 해봅시다
## =-> 이제 성공적으로 완수했으니까 그 다음 단계로 넘어가서
## 여러개의 category가 missing인 경우도 해봅시다
## => 이제 성공적으로 완수했으니까 그 다음 단계로 넘어가서
## 1. missing_col의 자동 탐색 및 => 해결(210314)
## 2. 자동으로 생성되는 #(한 col의 class)+1 col의 제거
## 3. scaling 작업도 해줘야 함 => 해결(210413)
## 4. num과 cate를 하나로 묶어주기 => 해결(210414)
## 5. class의 수에 따라서 진행할지 말지 선택하도록 하기
#######################작업 일지####################

####################### 버전 업 ####################
## 1. num=>cate or num& cate:
### 1-1 만약에 num=> cate를 따르게 한다면,
## pca를 해서 두번째 계산과정을 줄일 수 있도록 할 것
### 1-2 만약에 num&cate면 대폭 수정

## 2. class의 제한을 어떻게 둘 것인가?
### 2-1 절대적인 한계 설정
### 2-2 데이터 프레임 전체의 사이즈를 고려해서 이미 크다면
#### class 수를 조금만 풀어주고 작다면 충분히 고려해주기
################## 버전 업 #########################

##################알고리즘 개요####################
## 0. KNNImpute_num을 통해 numerical한 변수의 결측치는 미리 채운다
## 1. 원핫인코딩을 한다
## 2. 인코딩 과정에서 생략된 nan 값을 다시 입력해준다
## 3. KNNImpute_num을 통해 인코딩된 변수의 결측치를 대체한다
## 4. 평균이 산출되므로, 인코딩 값으로 변환한다(0 또는 1)
## 5. 디코딩 한다
## 끝!
##################알고리즘 개요####################

def KNNImpute(df):
        # 명목형 에서 missing이 없으면 바로 num으로 가게 하고
        # 만약 그게 아니라면 cate로 가게 하기
        new_df=KNNImpute_num(df)
        if new_df.isnull().sum!=0:
            ## 여기에 class의 수를 보는 조건문 넣기
            new_df=KNNImpute_cate(new_df)
        else: return new_df

def KNNImpute_num(df):

    new_df=df.select_dtypes(include=np.number)
    #입력된 데이터프레임 중에 Numerical한 column
    cols=new_df.columns
    #col 이름 저장
    scaler=RobustScaler()
    new_df=scaler.fit_transform(new_df)

    imputer=KNNImputer(n_neighbors=5)
    new_df=imputer.fit_transform(new_df)
    new_df=pd.DataFrame(new_df, columns=cols)

    new_df=scaler.inverse_transform(new_df)

    for col in df.columns:
        if col in cols:
            df[col] = new_df[col]
    #빠졌던 Categorical data를 다시 채워줌
    return df


## df_miss: 불완전한 데이터 셋
## missing_col: 불완전한 categorical 컬럼 명, 리스트 형태로 넣어줌
## => 버전 업하면서 개선할 예정


def KNNImpute_cate(df_miss):
    # 결측치 있는 명목형 변수 col 탐색

    missing_col=list(df_miss.columns[df_miss.isnull().sum()!=0])

    ## 데이터프레임 내 존재하는 모든 명목형변수를 원핫 인코딩함(결측치 있던 없던)
    cols_o=df_miss.select_dtypes(exclude=np.number).columns
    #categorical 변수들 목록

    one_en=ce.one_hot.OneHotEncoder(handle_missing="value")
    x_one=one_en.fit_transform(df_miss,cols_o) #df_miss를 인코딩한 df


    ## step2 인코딩 되버린 결측치를 다시 nan값으로 변경

    ## 원핫인코딩을 하면 해당 col이 a_1, a_2, 이런 식으로 생성됨
    ## cols는 이렇게 파생된 모든 col을 저장한 리스트

    index_total={}
    cols_total={}
    for i in range(len(missing_col)):
        target_col=missing_col[i]
        index_total[target_col]=list(df_miss.loc[df_miss[target_col].isna(),:].index) #missing 한 index 리스트
        cols_total[target_col]=list(x_one.columns[x_one.columns.str.contains(pat = target_col)]) ## missing 한 col에서 파생된 col 리스트
        x_one.loc[index_total[target_col],cols_total[target_col]]=np.nan # 결측치 값으로 바꿔주기


    ## step3 만들어둔 모둘 이용
    x_one_imputed=KNNImpute_num(x_one)

    ## step 4 : 평균이 산출되므로, 인코딩 값으로 변환한다(0 또는 1)
    ## ::: 동일 col 별로 묶어서 argmax를 한다 (a_1,a_2 중에서 가장 큰 값)

    for j in range(len(missing_col)):
        target_col=cols_total[missing_col[j]]
        target_idx=index_total[missing_col[j]]
        imputed_val=np.argmax(x_one_imputed.loc[target_idx,target_col].to_numpy(),axis=1)
        long=range(len(target_col))
        for i in range(len(target_idx)):
            x_one_imputed.loc[target_idx[i],[target_col[imputed_val[i]]]]=1 #최대값을 1로 impute
            cols_0=[target_col[j] for j in long if j!=imputed_val[i]]
            x_one_imputed.loc[target_idx[i],cols_0]=0 # 그 외의 값 0으로 impute


        ## 6. 그런 다음에 디코딩을 한다

    x_one_de=one_en.inverse_transform(x_one_imputed)

    return x_one_de

x_one_de이 대체한 데이터 셋, original이 원본 데이터 셋

 

 

 

## scaling 기능 붙이고 난후

 

  오히려 잘못 대체한 경우가 많아진 것으로 봐서, 본 데이터 셋은 scaling 안 된 경우가 낫다는 것을 알 수 있음. 즉 numerical한 변수와 우리가 대체하고자 하는 column이 높은 상관성이 있으면 오히려 scaling하지 않은 것이 낫다. 그러나 우리가 그것을 데이터 셋을 뜯어보기 전에 알 수 있는 확률이 적다고 판단된다..

댓글