[ Python ] sklearn_pandas 로 정형데이터 전처리하기(Preprocessing)

2020. 1. 19. 20:22분석 Python/Scikit Learn (싸이킷런)

728x90

광고 한 번만 눌러주세요 블로그 운영을 하는 데 있어서 큰 힘이 됩니다. : )

파이썬으로 분석을 하다 보면 sklearn과 pandas는 정형 데이터에서 자주 사용하는 패키지일 것이다.
정형 데이터에서 보통 범주형 데이터와 수치형 변수를 나눠서 전처리를 할 때 2개를 같이 쓰게 된다.
하지만 각각의 변수별로 처리를 하게 된다면 보통 다음과 같이 진행할 것이다.

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

imp = SimpleImputer(strategy='mean')
std = StandardScaler()
## a 
data["a"] = imp.fit_transform(data["a"])
data["a"] = std.fit_transform(data["a"])
## b
data["b"] = imp.fit_transform(data["b"])
data["b"] = std.fit_transform(data["b"])

하지만 이렇게 하다 보면 똑같은 작업을 많이 하다 보니 패턴이 생겨서 다음과 같이 하게 될 것이다.

for col in ["a","b"] :
	data[col] = imp.fit_transform(data[col])
	data[col] = std.fit_transform(data[col])

이런 방식을 사용하면 코드는 간단하게 되겠지만 먼가 개별적인 처리를 if else로 해야 돼서 아쉬울 때가 있을 것이다.
그리고 또 하나의 문제점은 저 코드에서 나오는 imp, std라는 객체값을 변수마다 모두 저장해줘야 한다는 사실이다.
그렇다면 저것을 재사용할 때 중간에 하던 것을 까먹는다면 귀찮아질 것이다.

그래서 좀 더 공부를 하신 분이라면 sklearn 에서 pipeline이라는 것을 발견할 것이다.
이것을 사용하면 여러개의 변수를 전체 pipeline에 넣어서 처리할 수 있어서 굉장히 코드를 해석하거나 사용하는 데 있어
서 편리해질 수 있다.
뿐만 아니라 모델링까지 하는 코드도 같이 넣을 수 있어서, 하나의 pipeline을 잘 만들어 놓으면, 다시 사용할 때 굉장히 편리하고 남들에게 보여줄 때도 좀 더 직관적으로 보여줄 수 있을 것이다.
간단한 예제는 다음과 같다.
아래 코드에서는 결측치를 mean으로 대체하고 표준화를 해주는 pipeline을 만든 것이다.

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

numeric_features = num_col 
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='mean')),
    ('scaler', StandardScaler())])
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features)])
clf = Pipeline(steps=[('preprocessor', preprocessor),])

 

만약 이것을 확장해서 범주형 변수까지 한다고 했을 때와 그 후에 모델링까지 하는 코드는 2개의 글을 참고하면 도움이 될 것이다.

https://data-newbie.tistory.com/365?category=780536

 

[ Python ] Scikit-Learn, 변수 전처리 Pipeline Transformer 만들어보기

광고 한 번만 눌러주세요! 블로그 운영에 큰 힘이 됩니다 (Click my ADs) 파이썬을 하다보면 Scikit-learn에 있는 전처리 코드를 많이 사용하게 된다. 개별적으로 다 처리하거나 퉁쳐서 할 수도 있지만, 이것을 P..

data-newbie.tistory.com

https://data-newbie.tistory.com/366?category=780536

 

[ Python ] Scikit-Learn Pipeline + RandomizedSearchCV + shap,eli5

광고 한 번만 눌러주세요! 블로그 운영에 큰 힘이 됩니다 ( Click my ADs! ) 이번 글에서는 전체 모델링을 하고 나서 모델 해석을 위해 eli5 , shap을 사용하려고 한다. 핵심 포인트는 Pipeline과 Shap , Eli5를..

data-newbie.tistory.com

사실 필자는 저 위의 전처리 코드를 짜면서 class 공부를 할 수 있어서 좋았지만, 좀 더 pandas와 sklearn를 엮어 놓은 패키지의 필요성을 느꼈다. 
그리고 찾은 것이 sklearn_pandas !!

https://github.com/scikit-learn-contrib/sklearn-pandas

 

scikit-learn-contrib/sklearn-pandas

Pandas integration with sklearn. Contribute to scikit-learn-contrib/sklearn-pandas development by creating an account on GitHub.

github.com

 

DataFrameMapper를 통해서 각각의 변수별로 더 직관적으로 쉽게 할 수 있다는 것을 알게 됐다.
이 패키지를 쓰면 좀 더 pipeline을 쉽게 사용할 수 있다. 
변수를 새로 만든다던지, 이름을 바꾼다던지와 같은 작업이 용이해진다.
그래서 해본 것은 다음과 같다.
income data를 가지고 전처리를 해보려고 한다.

from sklearn.impute import SimpleImputer
from sklearn_pandas import DataFrameMapper, CategoricalImputer
from sklearn.base import TransformerMixin, BaseEstimator
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.preprocessing import (
    StandardScaler, LabelBinarizer, Imputer, 
    FunctionTransformer,PolynomialFeatures, OrdinalEncoder,
    RobustScaler , PowerTransformer
)

현재 수치형 변수와 범주형 변수는 저런 식으로 구성되어 있다.
이제 이놈들을 다음과 같이 처리하려고 한다.

import sklearn
mapper = DataFrameMapper([
    (["age"],[SimpleImputer(strategy="mean"), StandardScaler()]),
    (["fnlwgt"],[SimpleImputer(strategy="median"), StandardScaler()]),
    (["education-num"],[SimpleImputer(strategy="mean"), RobustScaler()]),
    (["capital-gain","capital-loss"],[SimpleImputer(strategy="median"), PowerTransformer()]),
    (["hours-per-week"],[SimpleImputer(strategy="median"), PowerTransformer()]),
    ####
#     (['workclass', 'education', 'marital-status', 'occupation',
#       'relationship', 'race', 'sex', 'native-country'],
#     [CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
    (['workclass'],[CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
    (['education'],[CategoricalImputer(strategy='most_frequent'), OneHotEncoder()]),
    (['marital-status'],[CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
    (['occupation'],[CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
    (['relationship'],[CategoricalImputer(strategy='most_frequent'), OneHotEncoder()]),
    (['race'],[CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
    (['native-country'],[CategoricalImputer(strategy='most_frequent') , OneHotEncoder()]),
#     ### PCA
    (['capital-gain', 'capital-loss'], sklearn.decomposition.PCA(1)),
    (['capital-gain', 'capital-loss','hours-per-week'], sklearn.decomposition.KernelPCA(2))
    
] , df_out=True)

아래 mapper는 다음과 같이 처리한다는 것을 의미한다.

## 수치형 변수

  • age : 결측 mean 대체 , 표준화
  • fnlwgt : 결측 median 대체 , 표준화
  • education-num : 결측 mean 대체 , Robust 표준화
  • capital-gain , capital-loss : 각각 medain 대체 , BoxCox 변환
  • hours-per-week : 결측 median 대체, BoxCox 변환

## 범주형 변수

  • workclass : 결측 최빈값 대체, Onehot Encoding
  • education : 결측 최빈값 대체, Onehot Encoding
  • marital-status : 결측 최빈값 대체, Onehot Encoding
  • occupation : 결측 최빈값 대체, Onehot Encoding
  • relationship : 결측 최빈값 대체, Onehot Encoding
  • race : 결측 최빈값 대체, Onehot Encoding
  • native-country :결측 최빈값 대체 , Onehot Encoding

## 새로운 변수 추가

  • capital-gain, capital-loss PCA 1차원으로 압축
  • capital-gain, capital-loss, hours-per-week KernelPCA 2차원으로 압축

 

주의) 각각을 개별적으로 처리할 수도 있고 묶어서도 할 수 있지만, 묶어서 처리할 경우 이름도 묶이게 된다.

그래서 웬만하면 각각 하는 것이 나중에 확인할 때는 더 좋을 것 같다.
변수 이름이 중요하지 않다면, 묶어서 해도 상관없을 것 같긴 하다.

그리고 위에서 처럼 변수를 추가할 수도 있고, 각 변수들끼리도 쉽게 결합하고 변수도 지울 수도 있다.

class NewTransformer(TransformerMixin):

    def transform(self, X, **transform_params):
        X['new_var'] = X['fnlwgt'] * X['age']
        X = X.drop(columns=['fnlwgt','age'], axis=1)
        return X

    def fit(self, X, y=None, **fit_params):
        return self

new_var라는 새로운 변수를 만들고 fnlwgt , age 변수를 제거하는 class 함수를  만들 수 있다.

그다음에 위 2개를 Pipeline을 통해서 묶어줄 수 있다.
Pipeline은 쉽게 생각해보자면, 위의 것을 진행하고 나서 다음 것을 진행하는 것이니
그래서 mapper 함수를 하고 나서 나온 dataframe을 가지고 NewTransformer를 하는 것이니
변수 이름이 어떻게 나오는지를 아는 것이 중요할 것이다.

pipe = Pipeline([
    ('map', mapper),
    ('feature_gen', NewTransformer())
])
data_1 , data_2 = train_test_split(data , test_size = 0.3)
pipe = pipe.fit(data_1)
pre_data = pipe.transform(data_1)

굉장히 쉽게 각 변수를 원하는 전처리 기법을 사용해서 할 수 있다.
이것의 장점은 이 자체를 저장해서 다른 데이터셋이 올 때도 사용할 수 있다는 것이 큰 장점이다.

pre_data2 = pipe.transform(data_2)

여기서 주의해야 할 점은 onehot을 하는 데 있어서 각 범주형 변수들에서 범주는 2개의 데이터셋에서 동일해야 한다.

아니면 이런 식의 에러가 뜨니 조심!

이제 새롭게 만든 변수에 대해서 궁금하니 한번 살펴보자.
다음과 같이 차원 축소된 새로운 변수를 얻을 수 있게 되었다.

 - 끝 -

728x90