본문 바로가기
파이썬 기초/데이터 프레임 다루기

전처리 과정 유용한 코드들

by 조기정 2023. 4. 9.

과제를 하면서 유용하게 만들었던 부분들을 정리해보고자 한다.

아래의 git_gist에서 풀 코드를 확인 할 수 있다.

https://gist.github.com/GiJungCho/08f767a46a3a07742f59d34ae8451dcf

 

전처리

전처리. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

==전체적 과정 == 

원핫 인코딩을 사용하기 위해 컬럼을 타입별로 나누었고, (ex: 명목형 변수=> object)타입을 변환했습니다. 변환이 되지 않는 컬럼은 이상치 값을 None값로 만든후 변환하였습니다.

파생변수로는 원하는 몸무게와 현재 몸무게를 뺸 컬럼을 생성하였습니다.

힙과 엉덩이 사이즈의 null값을 성,몸무게,키의 평균으로 대체하였습니다. 
그럼에도 각각 221,649개의 데이터가 부족하여 그 둘은 키와 몸무게의 평균값으로 null값을 보간하였습니다.
그러나 아직 각각 189개 592개의 null값이 존재하여 그 둘은 몸무게와 키로 sort하여 값을 기준으로 선형보간하였습다.

개인을 식별하는 컬럼은 3개나 되서 id컬럼 social_di1컬럼 zip_code컬럼을 모두 합쳐 ID2컬럼으로 만들고 삭제하였습니다.

더미화에 방해가 되는 컬럼은 직접 커스텀하여 원핫 인코딩 하였습니다. (ex:"1,2,3" 형식)
이후 나머지 object는 sklearn패키지로 더미화 하였습니다.

 

 

 

== 세부 내용 == 

 

import os
import numpy as np
import pandas as pd
import math #math 모듈을 먼저 import해야 한다.    
import matplotlib.pyplot as plt

path = os.getcwd() # 모델 저장할 경로
os_file_list = os.listdir(path)

def data_sorting(data):
    All_data = {}
    data_key_list = list(data.keys())
    for i in range(len(data_key_list)):
        All_data[data_key_list[i]] = data[data_key_list[i]]
    return All_data

파이썬 파일이 있는 경로를 읽어들여서 거기에 있는 파일 목록을 키로 가져온다.

예를들어 엑셀 파일이 있다면 모든 시트의 이름을 All_data이라는 딕셔너리안에 데이터 프레임 형태로 저장하는 꼴이다.

 

 

각각의 데이터들의 설명을 확인해 본 결과, object 타입과 아닌것을 구분해야 할 일이 생겼다. 

수치형 변수로 읽어드림에도 object로 해석할 변수(ex)성별)이 있기 때문에 변수를 나누어 주었다.

float_col = ['height', 'weight','want_diet', 'waist' , 'heap' ,  'bmi', 'p_price',"p_quantity"]
str_col = ['occupation', 'sex', 'married', 'dise', 'loss_part', 'diet_purpose', 'diet', 'order_id', 'pfid', 'p_name', 'ID_2']
date_col = ["p_created" , "created"]
del_col = ["seq_num","id", "social_id1" , "zip_code"]

해당 전처리 과정을 손보면서 문제가 생겼다. 

타입변환이 되지 않는것이다. 고객데이터를 확인하다보니 허리사이즈를 27-28 이런식으로 적어놓은 것이다. 

해당 방법은 27.5로 변환하는것이 적절하겠지만 너무나도 많은 데이터와 "??" 등으로 적어놓은 분들이 많아 일일히 전처리 하기에는 시간이 오래걸린다는 점이 있었다. 물론 해당 방법은 기업과제를 할 경우에는 일일히 고쳐주는것이 바람직하다고 생각한다.

 

def type_change_2(df,change_type= float):
    for i,v in enumerate(df):
        try:
            df[i] = float(v)
        except:
            df[i] = None
    return df

def type_change(df,float_col, str_col, date_col, del_col):
    col_list = list(data.columns)
    
    for i,v in enumerate(col_list):
        print(v)
        if v in float_col:
            try:
                df[v] = df[v].astype(float)
            except:
                print(df[v],v,"타입변환 실패한 컬럼, type_change_2 실행으로 변환안되는값 None")
                type_change_2(df[v],change_type = float)
            
            
            
        elif v in str_col:
            df[v] = df[v].astype(str)
            
        elif v in date_col:
            try:
                df[v] = pd.to_datetime(df[v], format='%Y-%m-%d %H:%M', errors='raise')
            except:
                print(df[v],"시간 컬럼이 안변함")
                pass
            
        elif v in del_col:
            del df[v]
    return df
    
    
data = type_change(data,float_col, str_col, date_col, del_col)

위 방법은 데이터 프레임의 타입을 변환시키는데 변환이 전체로 한번에 되지 않는경우 해당 컬럼안으로 들어가서 문제가 되는 컬럼을 type_chage_2 에서 None 형태로 변환시켜 두고 나중에 타입을 강제로 변환하게 만들었다.

한마디로 이상치들에 None값을 한번에 적용했다

 

또 파생변수로 현재 몸무게와 원하는 체중을 빼서 뺴야할 체중을 뜻하는 파생변수를 만들었다. 

data["want_diet - weight"] = data["weight"] - data["want_diet"]

이런 부분은 인공지능을 함에 있어서 중요하다고 생각한다. 

 

여기까지 완료했는데 

na값이 많이 존재하는것을 확인할 수 있었다. 

그래서 키 몸무게 성별을 기준으로 평균을 적용해서 이상치를 채웠다.

data = data.groupby(["height","weight","sex"]).apply(lambda x: x.fillna(x.mean())) # na값을 집단별 평균값으로 대체한다.

 

 

그렇지만 아직도 다 na값을 채우지 못했기 때문에 한번 더 키와 몸무게를 적용하여 집단의 평균값으로 대체하였다.

data = data.groupby(["height","weight"]).apply(lambda x: x.fillna(x.mean())) # na값을 집단별 평균값으로 대체한다.

그리고 마지막으로 개인을 판단하는 컬럼이 여기저기 떨어져 있었다. 해당 부분을 한개로 합치고 나머지 컬럼은 드롭했다.

data["ID_2"] = data["id"] + data["social_id1"] + data["zip_code"] # data["ID_2"] = str(data["id"]) + "_" + str(data["social_id1
data = data.drop(["p_name","order_id","zip_code"]  , axis = 1)

그 이후 제품을 집단별로 나누었는데 

DC = ['GDCBEST' ,'GDCMAIL' ,'PDCGOOD']
DEaT = ['MDFFUNC', 'PDFFUNC', 'PDFHSFD', 'MDFMEAL', 'PDFMEAL' ,'MDFFIBE','PDFFIBE'] 
DE = ['MDEPART' ,'MDEETCC', 'MDEHLCE', 'MDEHRWE']
EM = ['MDGVDCD', 'MDBWEEK' ,'MDGLIKE' ,'MDGFASH', 'MDGBEAT']

data["제품분류"] = 0
for i,v in enumerate(data["pfid"]):
    if v[:7] in DC:
        data["제품분류"][i]= "DC"
    elif v[:7] in DEaT:
        data["제품분류"][i]= "DEaT"
    elif v[:7] in DE:
        data["제품분류"][i]= "DE"
    elif v[:7] in EM:
        data["제품분류"][i]= "EM"
        
data.loc[data["제품분류"] == 0,"제품분류"] = "제품분류군 이외"
del data["pfid"]

해당 부분은 원 핫 인코딩을 쓰고 싶지만 제품분류군은 그렇게 쓸수가 없어서 조금 불편하지만 수동으로 구성하였다.

이런식으로 크게 군집화를 한 후 pd.get_dummies를 이용해서 편하게 원핫 인코딩을 적용하였다.

data = pd.get_dummies(data[use_col],dummy_na= False,drop_first = True)

drop_first 옵션은 예를들어 성별이 있다면 0과 1로 인코딩 되서 나올 수 있지만, 

0을 제외한 1만 생각해서 0과 1로 한 컬럼에 표현하게 하는 옵션이다. 

만약 3가지 분류가 있다면 2가지 분류만 표시하는것이다 둘다 아닌것은 제외하고 표현가능하기 때문이다.

dummy_na옵션은 na값은 na값으로 분류군을 추가하는것이다. 여기서는 na값을 모두 사용해서 생각할 필요가 없지만 나중에 유용하게 적용할 수 있는 옵션일 것이다.

 

여기까지 하고 정규화는 잘 생각해야 한다고 생각한다 이상치가 많으면 

robust 스칼라를 써도 될것이고 아니면 스텐다드 스칼라를 써도 된다. 일단 스텐다드를 적용해보았다.

여기서는 나중에 내가 구성한 모델이 더 잘 맞추는 방향의 스칼라를 적용하면 좋을 것 같다. 

 

from sklearn.preprocessing import StandardScaler
object_list = data.select_dtypes('float').columns.values

Scaler = StandardScaler()

data[object_list] = Scaler.fit_transform(data[object_list])