Feature Engineering with Housing Price Prediction - Numerical Features

Page content

강의 홍보

개요

  • Feature Engineering를 이해하고 실습한다.
    • 결측치를 처리한다
    • Categorical Feature를 다룬다.

I. 사전 준비작업

  • Kaggle API 설치 후 데이터를 Kaggle에서 직접 가져오는 것을 구현한다.

(1) Kaggle API 설치

  • 구글 코랩에서 API를 불러오려면 다음 소스코드를 실행한다.
!pip install kaggle
Requirement already satisfied: kaggle in /usr/local/lib/python3.6/dist-packages (1.5.8)
Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from kaggle) (2.23.0)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from kaggle) (1.24.3)
Requirement already satisfied: python-dateutil in /usr/local/lib/python3.6/dist-packages (from kaggle) (2.8.1)
Requirement already satisfied: tqdm in /usr/local/lib/python3.6/dist-packages (from kaggle) (4.41.1)
Requirement already satisfied: certifi in /usr/local/lib/python3.6/dist-packages (from kaggle) (2020.6.20)
Requirement already satisfied: slugify in /usr/local/lib/python3.6/dist-packages (from kaggle) (0.0.1)
Requirement already satisfied: six>=1.10 in /usr/local/lib/python3.6/dist-packages (from kaggle) (1.15.0)
Requirement already satisfied: python-slugify in /usr/local/lib/python3.6/dist-packages (from kaggle) (4.0.1)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests->kaggle) (2.10)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests->kaggle) (3.0.4)
Requirement already satisfied: text-unidecode>=1.3 in /usr/local/lib/python3.6/dist-packages (from python-slugify->kaggle) (1.3)

(2) Kaggle Token 다운로드

  • Kaggle에서 API Token을 다운로드 받는다.
  • [Kaggle]-[My Account]-[API]-[Create New API Token]을 누르면 kaggle.json 파일이 다운로드 된다.
  • 이 파일을 바탕화면에 옮긴 뒤, 아래 코드를 실행 시킨다.
from google.colab import files
uploaded = files.upload()
for fn in uploaded.keys():
  print('uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))
  
# kaggle.json을 아래 폴더로 옮긴 뒤, file을 사용할 수 있도록 권한을 부여한다. 
!mkdir -p ~/.kaggle/ && mv kaggle.json ~/.kaggle/ && chmod 600 ~/.kaggle/kaggle.json

Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.

Saving kaggle.json to kaggle.json
uploaded file "kaggle.json" with length 64 bytes
  • 실제 kaggle.json 파일이 업로드 되었다는 뜻이다.
ls -1ha ~/.kaggle/kaggle.json
/root/.kaggle/kaggle.json

(3) Kaggle 데이터 불러오기

  • Kaggle 대회 리스트를 불러온다.
!kaggle competitions list
Warning: Looks like you're using an outdated API Version, please consider updating (server 1.5.6 / client 1.5.4)
ref                                            deadline             category            reward  teamCount  userHasEntered  
---------------------------------------------  -------------------  ---------------  ---------  ---------  --------------  
contradictory-my-dear-watson                   2030-07-01 23:59:00  Getting Started     Prizes        263           False  
gan-getting-started                            2030-07-01 23:59:00  Getting Started     Prizes        103           False  
tpu-getting-started                            2030-06-03 23:59:00  Getting Started  Knowledge        360           False  
digit-recognizer                               2030-01-01 00:00:00  Getting Started  Knowledge       2451           False  
titanic                                        2030-01-01 00:00:00  Getting Started  Knowledge      18594            True  
house-prices-advanced-regression-techniques    2030-01-01 00:00:00  Getting Started  Knowledge       4834            True  
connectx                                       2030-01-01 00:00:00  Getting Started  Knowledge        548           False  
nlp-getting-started                            2030-01-01 00:00:00  Getting Started  Knowledge       1422            True  
competitive-data-science-predict-future-sales  2020-12-31 23:59:00  Playground           Kudos       8720            True  
hashcode-drone-delivery                        2020-12-14 23:59:00  Playground       Knowledge         23           False  
lish-moa                                       2020-11-30 23:59:00  Research           $30,000       1186           False  
conways-reverse-game-of-life-2020              2020-11-30 23:59:00  Playground            Swag         64           False  
lyft-motion-prediction-autonomous-vehicles     2020-11-25 23:59:00  Featured           $30,000        389           False  
rsna-str-pulmonary-embolism-detection          2020-10-26 23:59:00  Featured           $30,000         72           False  
osic-pulmonary-fibrosis-progression            2020-10-06 23:59:00  Featured           $55,000       1720           False  
stanford-covid-vaccine                         2020-10-05 23:59:00  Research           $25,000        570           False  
landmark-recognition-2020                      2020-09-29 23:59:00  Research           $25,000        643           False  
halite                                         2020-09-22 23:59:00  Featured              Swag       1143           False  
birdsong-recognition                           2020-09-15 23:59:00  Research           $25,000       1395           False  
global-wheat-detection                         2020-08-19 01:59:00  Research           $15,000       2245           False  
  • 여기에서 참여하기 원하는 대회의 데이터셋을 불러오면 된다.
  • 이번 basic강의에서는 house-prices-advanced-regression-techniques 데이터를 활용한 데이터 가공과 시각화를 연습할 것이기 때문에 아래와 같이 코드를 실행하여 데이터를 불러온다.
!kaggle competitions download -c house-prices-advanced-regression-techniques
Warning: Looks like you're using an outdated API Version, please consider updating (server 1.5.6 / client 1.5.4)
Downloading test.csv to /content
  0% 0.00/441k [00:00<?, ?B/s]
100% 441k/441k [00:00<00:00, 66.4MB/s]
Downloading train.csv to /content
  0% 0.00/450k [00:00<?, ?B/s]
100% 450k/450k [00:00<00:00, 62.5MB/s]
Downloading data_description.txt to /content
  0% 0.00/13.1k [00:00<?, ?B/s]
100% 13.1k/13.1k [00:00<00:00, 13.2MB/s]
Downloading sample_submission.csv to /content
  0% 0.00/31.2k [00:00<?, ?B/s]
100% 31.2k/31.2k [00:00<00:00, 28.5MB/s]
  • 실제 데이터가 잘 다운로드 받게 되었는지 확인한다.
!ls
data_description.txt  sample_data  sample_submission.csv  test.csv  train.csv

(4) 데이터 불러오기

  • pandas 모듈을 활용해서 데이터를 확인해본다.
import pandas as pd
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# Id Column 저장
train_ID = train['Id']
test_ID = test['Id']

print("Id 컬럼 삭제 전 Train 데이터 size : {} ".format(train.shape))
print("Id 컬럼 삭제 전 Test 데이터 size : {} ".format(test.shape))

# Id 삭제
train.drop("Id", axis = 1, inplace = True)
test.drop("Id", axis = 1, inplace = True)

print("Id 컬럼 삭제 후 Train 데이터 size : {} ".format(train.shape))
print("Id 컬럼 삭제 후 Test 데이터 size : {} ".format(test.shape))
Id 컬럼 삭제 전 Train 데이터 size : (1460, 81) 
Id 컬럼 삭제 전 Test 데이터 size : (1459, 80) 
Id 컬럼 삭제 후 Train 데이터 size : (1460, 80) 
Id 컬럼 삭제 후 Test 데이터 size : (1459, 79) 
  • 변수명이 삭제된 것을 확인한다.

II. 데이터 싸이언스의 전반적인 흐름 리뷰

  • 전반적인 흐름은 보통 다음과 같이 이루어진다.

    • 문제정의
    • 데이터 수집
    • 데이터 EDA (기초 통계 및 시각화)
    • Feature Preprocessing - 정규표현식 통해서 데이터를 문자열 데이터를 처리하는 방법, 불필요한 변수 삭제,
    • Feature Engineering - Feature를 선택, 추출함 via 통계적 기법, 도메인 지식을 활용
    • 모델링
    • 모형 평가
    • 모형 배포 및 보고서
  • 데이터 시각화는 다음 페이지에 연재했기 때문에 참고한다.

  • Feature Preprocessing에 관련된 추가 자료는 다음을 참조하자.

III. Feature Engineering

  • 도메인 지식을 활용하여 특징(Feature)을 만들어내는 과정
  • 머신러닝 모델을 위한 Column 생성하거나 또는 선택하는 작업 의미
  • Feature Engineering은 모델 성능에 즉각적으로 영향을 미치기 때문에 ML응용에 있어서 굉장히 중요한 단계이며, 전문성과 시간과 비용이 많이 듬
  • 리뷰하는 차원에서 말하면, Feature Preprocessing관한 일반적인 내용은 다음과 같다.
    • 수치형: Scaling, Outliers, Rank, np.log(1+x) and np.sqrt(1+x)
    • 범주형: Label Encoding for tree based models, One-hot encoding for non-tree-based models
    • 날짜: 주기성(=Periodicity), Time Since, 날짜간의 차이
    • 위도 & 경도: places, centers, statistics
  • Feature Engineering은 결국 Feature를 컨트롤 및 추출하는 것으로 정의할 수 있다.
    • 수치형 데이터: Binarization, Log Transformation, Scaling, Normalization, Regularization
    • 텍스트 데이터: Bag of X, Stopwords, Tokenization, Tf-IDF Transformation, Regularization
    • 범주형 데이터: One-hot encoding, Dummy Encoding, Effect Coding
    • 차원축소(PCA)
  • 그러나, 결측치를 채우는 것도 Feature Engineering에 포함되며, 이 때 Target에 따른 mean encoding도 포함하게 된다. 특히, mean encoding은 분류모형을 다룰 때 다시 설명하도록 한다.

(1) 데이터 합치기

  • 데이터를 합치는데, target 데이터를 별도 변수로 저장한다.
ntrain = train.shape[0] # 행 개수 저장
ntest = test.shape[0] # 행 개수 저장

y_train = train.SalePrice.values # y_train 추출

house_df = pd.concat((train, test)).reset_index(drop = True)
house_df.drop(['SalePrice'], axis=1, inplace=True)
print("전체 데이터 Size : {}".format(house_df.shape))
전체 데이터 Size : (2919, 79)

(2) 결측치 데이터 확인

  • 결측치 check 함수를 만든다.
def check_na(data):
  isnull_na = (data.isnull().sum() / len(data)) * 100
  data_na = isnull_na.drop(isnull_na[isnull_na == 0].index).sort_values(ascending=False)
  missing_data = pd.DataFrame({'Missing Ratio' :data_na, 
                               'Data Type': data.dtypes[data_na.index]})
  print("결측치 데이터 컬럼과 비율:\n", missing_data)
check_na(house_df)
결측치 데이터 컬럼과 비율:
               Missing Ratio Data Type
PoolQC            99.657417    object
MiscFeature       96.402878    object
Alley             93.216855    object
Fence             80.438506    object
FireplaceQu       48.646797    object
LotFrontage       16.649538   float64
GarageFinish       5.447071    object
GarageYrBlt        5.447071   float64
GarageQual         5.447071    object
GarageCond         5.447071    object
GarageType         5.378554    object
BsmtExposure       2.809181    object
BsmtCond           2.809181    object
BsmtQual           2.774923    object
BsmtFinType2       2.740665    object
BsmtFinType1       2.706406    object
MasVnrType         0.822199    object
MasVnrArea         0.787941   float64
MSZoning           0.137033    object
BsmtFullBath       0.068517   float64
BsmtHalfBath       0.068517   float64
Utilities          0.068517    object
Functional         0.068517    object
Exterior2nd        0.034258    object
Exterior1st        0.034258    object
SaleType           0.034258    object
BsmtFinSF1         0.034258   float64
BsmtFinSF2         0.034258   float64
BsmtUnfSF          0.034258   float64
Electrical         0.034258    object
KitchenQual        0.034258    object
GarageCars         0.034258   float64
GarageArea         0.034258   float64
TotalBsmtSF        0.034258   float64

(3) 결측치 데이터 채우기

  • 위 데이터에 순차적으로 처리하도록 한다.
  • 몇몇 분들은 PoolQC, MiscFeature와 같은 Feature는 삭제하기도 한다.
var_lists = ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu']
for fea in var_lists:
  house_df[fea] = house_df[fea].fillna('None')

check_na(house_df)
결측치 데이터 컬럼과 비율:
               Missing Ratio Data Type
LotFrontage       16.649538   float64
GarageQual         5.447071    object
GarageFinish       5.447071    object
GarageYrBlt        5.447071   float64
GarageCond         5.447071    object
GarageType         5.378554    object
BsmtExposure       2.809181    object
BsmtCond           2.809181    object
BsmtQual           2.774923    object
BsmtFinType2       2.740665    object
BsmtFinType1       2.706406    object
MasVnrType         0.822199    object
MasVnrArea         0.787941   float64
MSZoning           0.137033    object
BsmtFullBath       0.068517   float64
Functional         0.068517    object
Utilities          0.068517    object
BsmtHalfBath       0.068517   float64
Exterior2nd        0.034258    object
Exterior1st        0.034258    object
SaleType           0.034258    object
BsmtFinSF1         0.034258   float64
BsmtFinSF2         0.034258   float64
TotalBsmtSF        0.034258   float64
Electrical         0.034258    object
KitchenQual        0.034258    object
GarageCars         0.034258   float64
GarageArea         0.034258   float64
BsmtUnfSF          0.034258   float64
  • LotFrontage의 데이터 typefloat64이기 때문에 median으로 처리하도록 한다.
  • 이 때 Neighborhood에 따른 median으로 지정하는 코드를 작성한다.
house_df['LotFrontage'] = house_df.groupby('Neighborhood')['LotFrontage'].transform(lambda x: x.fillna(x.median()))
check_na(house_df)
결측치 데이터 컬럼과 비율:
               Missing Ratio Data Type
GarageQual         5.447071    object
GarageFinish       5.447071    object
GarageYrBlt        5.447071   float64
GarageCond         5.447071    object
GarageType         5.378554    object
BsmtCond           2.809181    object
BsmtExposure       2.809181    object
BsmtQual           2.774923    object
BsmtFinType2       2.740665    object
BsmtFinType1       2.706406    object
MasVnrType         0.822199    object
MasVnrArea         0.787941   float64
MSZoning           0.137033    object
BsmtFullBath       0.068517   float64
BsmtHalfBath       0.068517   float64
Functional         0.068517    object
Utilities          0.068517    object
Exterior1st        0.034258    object
Exterior2nd        0.034258    object
SaleType           0.034258    object
BsmtFinSF1         0.034258   float64
BsmtFinSF2         0.034258   float64
TotalBsmtSF        0.034258   float64
Electrical         0.034258    object
KitchenQual        0.034258    object
GarageCars         0.034258   float64
GarageArea         0.034258   float64
BsmtUnfSF          0.034258   float64
  • GarageQual, GarageFinish, GarageCond, GarageType의 데이터는 object이므로 모두 None으로 처리한다.
for fea in ('GarageQual', 'GarageFinish', 'GarageCond', 'GarageType'):
  house_df[fea] = house_df[fea].fillna('None')

check_na(house_df)
결측치 데이터 컬럼과 비율:
               Missing Ratio Data Type
GarageYrBlt        5.447071   float64
BsmtCond           2.809181    object
BsmtExposure       2.809181    object
BsmtQual           2.774923    object
BsmtFinType2       2.740665    object
BsmtFinType1       2.706406    object
MasVnrType         0.822199    object
MasVnrArea         0.787941   float64
MSZoning           0.137033    object
BsmtFullBath       0.068517   float64
BsmtHalfBath       0.068517   float64
Functional         0.068517    object
Utilities          0.068517    object
TotalBsmtSF        0.034258   float64
BsmtUnfSF          0.034258   float64
BsmtFinSF2         0.034258   float64
GarageArea         0.034258   float64
BsmtFinSF1         0.034258   float64
Electrical         0.034258    object
KitchenQual        0.034258    object
GarageCars         0.034258   float64
Exterior2nd        0.034258    object
Exterior1st        0.034258    object
SaleType           0.034258    object
  • BsmtCond, BsmtExposure, BsmtQual, BsmtFinType2, BsmtFinType1, MasVnrType는 범주형 데이터이므로 모두 None으로 처리한다.
  • GarageYrBlt, MasVnrArea, BsmtFullBath, BsmtHalfBath, TotalBsmtSF, BsmtUnfSF, BsmtFinSF2, GarageArea, BsmtFinSF1는 모두 숫자형 데이터이므로 모두 0으로 처리한다.
for fea in ('BsmtCond', 'BsmtExposure', 'BsmtQual', 'BsmtFinType2', 'BsmtFinType1', 'MasVnrType'):
  house_df[fea] = house_df[fea].fillna('None')

for fea in ('GarageYrBlt', 'MasVnrArea', 'BsmtFullBath', 'BsmtHalfBath', 'TotalBsmtSF', 'BsmtUnfSF', 'BsmtFinSF2', 'GarageArea', 'BsmtFinSF1', 'GarageCars'):
  house_df[fea] = house_df[fea].fillna(0)

check_na(house_df)
결측치 데이터 컬럼과 비율:
              Missing Ratio Data Type
MSZoning          0.137033    object
Functional        0.068517    object
Utilities         0.068517    object
SaleType          0.034258    object
KitchenQual       0.034258    object
Electrical        0.034258    object
Exterior2nd       0.034258    object
Exterior1st       0.034258    object
  • 우선 Utilities 데이터는 AllPub 한개의 값만 있기 때문에 삭제한다.
house_df = house_df.drop(['Utilities'], axis = 1)
check_na(house_df)
결측치 데이터 컬럼과 비율:
              Missing Ratio Data Type
MSZoning          0.137033    object
Functional        0.068517    object
SaleType          0.034258    object
KitchenQual       0.034258    object
Electrical        0.034258    object
Exterior2nd       0.034258    object
Exterior1st       0.034258    object
  • MSZoning 내 각 데이터의 빈도수를 확인해본다.
house_df['MSZoning'].value_counts()
RL         2265
RM          460
FV          139
RH           26
C (all)      25
Name: MSZoning, dtype: int64
  • 위와 같이 RL의 빈도수가 매우 많기 때문에 RL, 즉 최빈값을 대체해서 넣도록 한다.
  • 나머지 결측치의 데이터를 위와 같이 대체해서 넣도록 한다.
for fea in ('MSZoning', 'Functional', 'SaleType', 'KitchenQual', 'Electrical', 'Exterior2nd', 'Exterior1st'):
  house_df[fea] = house_df[fea].fillna(house_df[fea].mode()[0])

check_na(house_df)
결측치 데이터 컬럼과 비율:
 Empty DataFrame
Columns: [Missing Ratio, Data Type]
Index: []

(4) 데이터 변환

  • 몇몇 수치형 데이터 시각화를 진행해본다.
    • 이 때, 유용한 시각화는 산점도를 그려보면 된다.
print("The data type of `MSSubClass` is:", house_df['MSSubClass'].dtypes)
print("The data type of `OverallCond` is:", house_df['OverallCond'].dtypes)
The data type of `MSSubClass` is: int64
The data type of `OverallCond` is: int64
import matplotlib.pyplot as plt

fig, ax = plt.subplots(1,2, figsize=(10, 5))

# MSSubClass
ax[0].scatter(house_df['MSSubClass'], house_df['LotFrontage'])
ax[0].grid(True)
ax[0].set_title("LotFrontage ~ MSSubClass")

# OverallCont
ax[1].scatter(house_df['OverallCond'], house_df['LotFrontage'])
ax[1].grid(True)
ax[1].set_title("LotFrontage ~ OverallCond")

plt.tight_layout()
plt.show()

png

  • 분명 데이터타입은 수치형이지만, 막상 그래프는 제대로 된 산점도가 그려지지 않는다.
    • 이런 경우에는 문자형으로 바꿔주는게 좋다.
    • 실제, data_description.txt를 보면, 수치형 데이터로 보기에는 무리가 있다.
    • 마찬가지로 연도, 의 경우도 숫자로 보기에는 무리가 있다.
  • 따라서, 위 데이터를 모두 문자형으로 바꾼다.
for fea in ('MSSubClass', 'OverallCond', 'YrSold', 'MoSold'):
  house_df[fea] = house_df[fea].apply(str)

(5) Label Encoding vs One-Hot Encoding

png

  • 두 방법 모두 문자형 데이터를 수치형으로 바꿔주는 것이 주 목적이다.
    • 머신러닝 알고리즘은 수식으로 되어 있기 때문에, 수치형으로 바꿔주는 것이다.
  • 그런데, 데이터에 대한 적절한 이해 없이 적용하면 안된다.
  • 그러면, 언제 사용하는 것이 좋을까? 그럴려면, 우선 문자형 데이터에 대한 이해가 필요하다.
    • 문자형 데이터가 명목형(예: 국가, 시도별 등)인 경우에는 One-Hot Encoding을 사용한다.
    • 문자형 데이터가 순서형(예: 학력, 등급)인 경우에는 Label Encoding을 사용한다.
  • 따라서, 위 두개를 혼용해서 적정하게 사용해야 한다. 즉, 전체 문자형 데이터를 무조건 One-Hot Encoding 또는 Label Encoding을 적용해서는 안된다.
  • 보다 자세한 내용은 다음 글을 참조하기를 바란다.

(A) Label Encoding

  • 먼저 Label Encoding을 적용하는 소스코드를 만든다.
  • 먼저 간단한 코드로 LabelEncoder를 익히도록 한다.
from sklearn.preprocessing import LabelEncoder

cities = ["서울", "경기", "인천", "서울", "부산"]
lbl_temp = LabelEncoder()
lbl_temp.fit(cities)
labels = lbl_temp.transform(cities)
print("인코딩 변환값:", labels)
인코딩 변환값: [2 0 3 2 1]
  • 위 코드를 토대로 이제 각 변수에 위 코드를 적용하도록 한다.
from sklearn.preprocessing import LabelEncoder

features = ('FireplaceQu', 'BsmtQual', 'BsmtCond', 'GarageQual', 'GarageCond', 
        'ExterQual', 'ExterCond','HeatingQC', 'PoolQC', 'KitchenQual', 'BsmtFinType1', 
        'BsmtFinType2', 'Functional', 'Fence', 'BsmtExposure', 'GarageFinish', 'LandSlope',
        'LotShape', 'PavedDrive', 'Street', 'Alley', 'CentralAir', 'MSSubClass', 'OverallCond', 
        'YrSold', 'MoSold')

for fea in features:
  print("The current feature is:", fea)
  lbl = LabelEncoder() 
  lbl.fit(list(house_df[fea].values))
  house_df[fea] = lbl.transform(list(house_df[fea].values))
  unique_value = house_df[fea].unique() 
  print("현재 인코딩 클래스", lbl.classes_)
  print("현재 인코딩 변환값", unique_value)

# shape        
print('Shape house_df: {}'.format(house_df.shape))
The current feature is: FireplaceQu
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'None' 'Po' 'TA']
현재 인코딩 변환값 [3 5 2 1 0 4]
The current feature is: BsmtQual
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'None' 'TA']
현재 인코딩 변환값 [2 4 0 3 1]
The current feature is: BsmtCond
현재 인코딩 클래스 ['Fa' 'Gd' 'None' 'Po' 'TA']
현재 인코딩 변환값 [4 1 2 0 3]
The current feature is: GarageQual
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'None' 'Po' 'TA']
현재 인코딩 변환값 [5 1 2 3 0 4]
The current feature is: GarageCond
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'None' 'Po' 'TA']
현재 인코딩 변환값 [5 1 3 2 4 0]
The current feature is: ExterQual
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'TA']
현재 인코딩 변환값 [2 3 0 1]
The current feature is: ExterCond
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'Po' 'TA']
현재 인코딩 변환값 [4 2 1 3 0]
The current feature is: HeatingQC
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'Po' 'TA']
현재 인코딩 변환값 [0 2 4 1 3]
The current feature is: PoolQC
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'None']
현재 인코딩 변환값 [3 0 1 2]
The current feature is: KitchenQual
현재 인코딩 클래스 ['Ex' 'Fa' 'Gd' 'TA']
현재 인코딩 변환값 [2 3 0 1]
The current feature is: BsmtFinType1
현재 인코딩 클래스 ['ALQ' 'BLQ' 'GLQ' 'LwQ' 'None' 'Rec' 'Unf']
현재 인코딩 변환값 [2 0 6 5 1 4 3]
The current feature is: BsmtFinType2
현재 인코딩 클래스 ['ALQ' 'BLQ' 'GLQ' 'LwQ' 'None' 'Rec' 'Unf']
현재 인코딩 변환값 [6 1 4 0 5 3 2]
The current feature is: Functional
현재 인코딩 클래스 ['Maj1' 'Maj2' 'Min1' 'Min2' 'Mod' 'Sev' 'Typ']
현재 인코딩 변환값 [6 2 0 3 4 1 5]
The current feature is: Fence
현재 인코딩 클래스 ['GdPrv' 'GdWo' 'MnPrv' 'MnWw' 'None']
현재 인코딩 변환값 [4 2 1 0 3]
The current feature is: BsmtExposure
현재 인코딩 클래스 ['Av' 'Gd' 'Mn' 'No' 'None']
현재 인코딩 변환값 [3 1 2 0 4]
The current feature is: GarageFinish
현재 인코딩 클래스 ['Fin' 'None' 'RFn' 'Unf']
현재 인코딩 변환값 [2 3 0 1]
The current feature is: LandSlope
현재 인코딩 클래스 ['Gtl' 'Mod' 'Sev']
현재 인코딩 변환값 [0 1 2]
The current feature is: LotShape
현재 인코딩 클래스 ['IR1' 'IR2' 'IR3' 'Reg']
현재 인코딩 변환값 [3 0 1 2]
The current feature is: PavedDrive
현재 인코딩 클래스 ['N' 'P' 'Y']
현재 인코딩 변환값 [2 0 1]
The current feature is: Street
현재 인코딩 클래스 ['Grvl' 'Pave']
현재 인코딩 변환값 [1 0]
The current feature is: Alley
현재 인코딩 클래스 ['Grvl' 'None' 'Pave']
현재 인코딩 변환값 [1 0 2]
The current feature is: CentralAir
현재 인코딩 클래스 ['N' 'Y']
현재 인코딩 변환값 [1 0]
The current feature is: MSSubClass
현재 인코딩 클래스 ['120' '150' '160' '180' '190' '20' '30' '40' '45' '50' '60' '70' '75'
 '80' '85' '90']
현재 인코딩 변환값 [10  5 11  9  4  8 15  0  6 14 13  2 12  3  7  1]
The current feature is: OverallCond
현재 인코딩 클래스 ['1' '2' '3' '4' '5' '6' '7' '8' '9']
현재 인코딩 변환값 [4 7 5 6 3 1 2 8 0]
The current feature is: YrSold
현재 인코딩 클래스 ['2006' '2007' '2008' '2009' '2010']
현재 인코딩 변환값 [2 1 0 3 4]
The current feature is: MoSold
현재 인코딩 클래스 ['1' '10' '11' '12' '2' '3' '4' '5' '6' '7' '8' '9']
현재 인코딩 변환값 [ 4  7 11  3  1 10  2  6  0  9  5  8]
Shape house_df: (2919, 78)

(B) One-Hot Encoding

  • 이번에는 One-Hot Encoding을 적용하는 소스코드를 만든다.
  • 그런데, 보통 Label-Encoding을 한 후에 원-핫 인코딩을 적용하는 것이 일반적이다.
  • 그러나 pandas 데이터를 활용할 경우 pd.get_dummies(dataset)를 활용한다.
    • 본 코드에서는 pd.get_dummies(dataset)를 활용한다.
    • pandas 공식 홈페이지: pandas.get_dummies
import pandas as pd

cities = ["서울", "경기", "인천", "서울", "부산"]
temp_df = pd.DataFrame({"시도": cities})
pd.get_dummies(temp_df)
시도_경기 시도_부산 시도_서울 시도_인천
0 0 0 1 0
1 1 0 0 0
2 0 0 0 1
3 0 0 1 0
4 0 1 0 0
  • 그러나, 이 부분은 Feature Scaling 이후에 진행해도록 한다.

  • One-Hot Encoding은 가장 마지막으로 해주는 것이 좋다.

  • 데이터를 추가한다.

house_df['TotalSF'] = house_df['TotalBsmtSF'] + house_df['1stFlrSF'] + house_df['2ndFlrSF']

(6) Feature Scaling

  • Label EncodingOne-Hot Encoding을 통해서 범주형 데이터를 다룬다면, 이제 수치형 데이터를 다뤄야 한다.
  • 몇몇 머신러닝 알고리즘은 수치형 데이터를 다룰 때 특히, 엄격하게 다뤄야 할 필요가 있다.
    • 수치형 데이터는 매우 자주 이상치(=Outliers)를 가지고 있다 (이상치에 대한 설명은 쉽게 검색해서 찾을 수 있어서, 해당 내용은 생략).
    • 이러한 이상치를 다룰 때, 주로 쓰이는 기법이 Min-Max Scaling = Normalization, Centering = Standardization, Skewness = Box Transformation등이 있다.

(A) Normalization(정규화)

  • 보통 Min-Max Scaling으로 불리우기도 한다.
  • 기존 수치형 데이터의 범위를 [0, 1]사이로 조정한다.
  • 시각화로 파악을 한다.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import minmax_scale

# 10000개의 데이터를 생성한다. 
raw_data = np.random.exponential(size = 10000)

# min-max scaling을 적용한다. 
min_max_data = minmax_scale(raw_data)

# 두개의 그래프를 그린 후 확인한다. 
fig, ax = plt.subplots(1,2, figsize=(10, 5))
ax[0].hist(raw_data, color='y')
ax[0].grid(True)
ax[0].set_title("raw Data")

ax[1].hist(min_max_data, color = 'g')
ax[1].grid(True)
ax[1].set_title("Scaled data")
plt.show()

png

  • 위 그래프에서 알 수 있는 것은 세가지가 있다.

    • 정규화를 진행할 경우, X축의 값이 [0,1]로 바뀌었다.
    • 데이터 정규화를 해도 위치가 변하는 것은 아니다.
    • 범위가 바뀌었기 때문에 해석의 관점에서는 손실이 있다.
  • 공식은 다음과 같이 구한다. $$ x_{normalized} = \frac{x - x_{min}}{x_{max} - x_{min}} $$

  • sklearn 패키지에는 MinMaxScaler를 사용하기도 한다.

(B) Standardization(표준화)

  • 어떤 책에서는 Centering으로 표현되기도 한다.
  • 정규화하고는 조금 다르다.
  • 평균이 0이고 표준편차가 1인 가우시안 정규분포(Gaussian Distribution)로 변환하는 것을 의미한다.
  • 시각화로 구체적으로 확인해본다.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats

np.random.seed(1)

# 10000개의 데이터를 생성한다. 
raw_data = np.random.exponential(size = 10000) + np.random.exponential(size = 10000) 
num_bins = 50

# raw_data
raw_mu = raw_data.mean()  # mean of distribution
raw_sigma = raw_data.std()  # standard deviation of distribution
raw_x = raw_mu + raw_sigma * np.random.randn(10000)


# scaled_data
scaled_data = stats.zscore(raw_data)
scaled_mu = scaled_data.mean()  # mean of distribution
scaled_sigma = scaled_data.std()  # standard deviation of distribution
scaled_x = scaled_mu + scaled_sigma * np.random.randn(10000)

fig, ax = plt.subplots(1,2, figsize=(10, 5))

# raw data
n, bins, patches = ax[0].hist(raw_x, num_bins, density=1)
raw_y = ((1 / (np.sqrt(2 * np.pi) * raw_sigma)) *
     np.exp(-0.5 * (1 / raw_sigma * (bins - raw_mu))**2))

ax[0].plot(bins, raw_y, '--')
ax[0].set_xlabel('Smarts')
ax[0].set_ylabel('Probability density')
ax[0].set_title(r'Raw Data Histogram of IQ: $\mu={}$, $\sigma={}$'.format(round(raw_mu, 2), round(raw_sigma, 2)))

# scaled data
n, bins, patches = ax[1].hist(scaled_x, num_bins, density=1)
scaled_y = ((1 / (np.sqrt(2 * np.pi) * scaled_sigma)) *
     np.exp(-0.5 * (1 / scaled_sigma * (bins - scaled_mu))**2))

ax[1].plot(bins, scaled_y, '--')
ax[1].set_xlabel('Smarts')
ax[1].set_ylabel('Probability density')
ax[1].set_title(r'Scaled Data Histogram of IQ: $\mu={:.2f}$, $\sigma={:.2f}$'.format(abs(scaled_mu), scaled_sigma))

# Tweak spacing to prevent clipping of ylabel
fig.tight_layout()
plt.show()

png

  • 위 시각화 소스코드보다는 내용을 보자.
    • 임의의 Raw 데이터의 평균은 2.0이고, 표준편차는 1.39으로 확인된다.
    • 그러나, Scaled한 경우에는 평균은 0이고, 표준편차는 1.0로 변동된 것을 알 수 있다.
    • 공식은 다음과 같다.

$$ x_{standardized} = \frac{x - \mu}{\sigma} $$

(C) Skewness & Log Transformation

  • 수치형 데이터의 또다른 문제는 Skewness(왜도)에 대한 처리법이다.
  • 왜도에 관한 그래프를 먼저 작성해본다.
  • SalePrice가 대표적인 예이기 때문에 직접 그래프를 작성한다.
from scipy import stats
from scipy.stats import norm, skew #for some statistics
import seaborn as sns

(norm_mu, norm_sigma) = norm.fit(y_train)
print( 'norm_mu = {:.2f} and norm_sigma = {:.2f}'.format(norm_mu, norm_sigma))

fig, ax = plt.subplots(ncols=2, figsize=(10, 5))
sns.distplot(y_train, ax = ax[0])
sns.distplot(y_train, ax = ax[1], fit=norm)
plt.legend(['Normal dist. \n($\mu=$ {:.2f} \n $\sigma=$ {:.2f} )'.format(norm_mu, norm_sigma)],
            loc=[0.5, 0.8])
plt.show()
norm_mu = 180921.20 and norm_sigma = 79415.29

png

  • SalePrice의 그래프는 right skewed라고 불리운다.
    • 데이터 분석 시에는 통상적으로는 이렇게 자료가 왼쪽이 많고, 오른쪽이 적은 형태의 데이터가 많다.
  • 이러한 자료들은 정규분포를 따르지 않는다고 판단하며, 선형모델에는 유의한 결과가 나오지 않기 때문에 통상적으로 log Transformation을 취해준다.
temp_y = np.log1p(y_train)

(norm_mu, norm_sigma) = norm.fit(temp_y)
print( 'norm_mu = {:.2f} and norm_sigma = {:.2f}'.format(norm_mu, norm_sigma))

fig, ax = plt.subplots(ncols=2, figsize=(10, 5))
sns.distplot(temp_y, ax = ax[0])
sns.distplot(temp_y, ax = ax[1], fit=norm)
plt.legend(['Normal dist. \n($\mu=$ {:.2f} \n $\sigma=$ {:.2f} )'.format(norm_mu, norm_sigma)],
            loc=[0.5, 0.8])
plt.show()
norm_mu = 12.02 and norm_sigma = 0.40

png

  • np.log1p() 함수를 사용해서 Log-Transformation을 적용하였다.
  • 문제는 이러한 수치형 데이터를 하나하나 확인하면서 진행할 수 없다.
    • 따라서, 보통의 경우 skew를 확인한 뒤, 해당되는 변수만 뽑아서 처리하는 작업을 진행하게 된다.
numeric_features = house_df.dtypes[house_df.dtypes != "object"].index

skewed_feats = house_df[numeric_features].apply(lambda x: skew(x.dropna())).sort_values(ascending=False)
print("\nSkew in numerical features: \n")
skewness = pd.DataFrame({'Skew' :skewed_feats})
skewness.head
Skew in numerical features: 






<bound method NDFrame.head of                     Skew
MiscVal        21.947195
PoolArea       16.898328
LotArea        12.822431
LowQualFinSF   12.088761
3SsnPorch      11.376065
LandSlope       4.975157
KitchenAbvGr    4.302254
BsmtFinSF2      4.146143
EnclosedPorch   4.003891
ScreenPorch     3.946694
BsmtHalfBath    3.931594
MasVnrArea      2.613592
OpenPorchSF     2.535114
WoodDeckSF      1.842433
TotalSF         1.511479
LotFrontage     1.505704
1stFlrSF        1.469604
BsmtFinSF1      1.425230
GrLivArea       1.269358
TotalBsmtSF     1.156894
BsmtUnfSF       0.919339
2ndFlrSF        0.861675
TotRmsAbvGrd    0.758367
Fireplaces      0.733495
HalfBath        0.694566
BsmtFullBath    0.624832
OverallCond     0.570312
HeatingQC       0.486656
FireplaceQu     0.333787
BedroomAbvGr    0.326324
GarageArea      0.239257
OverallQual     0.197110
FullBath        0.167606
MSSubClass      0.138396
YrSold          0.132399
BsmtFinType1    0.084633
GarageCars     -0.219581
YearRemodAdd   -0.451020
BsmtQual       -0.488273
YearBuilt      -0.599806
GarageFinish   -0.608033
LotShape       -0.617690
MoSold         -0.645257
Alley          -0.652040
BsmtExposure   -1.114856
KitchenQual    -1.448023
ExterQual      -1.801409
Fence          -1.993777
ExterCond      -2.497719
BsmtCond       -2.862585
PavedDrive     -2.979084
BsmtFinType2   -3.044328
GarageQual     -3.074152
CentralAir     -3.459022
GarageCond     -3.595790
GarageYrBlt    -3.906205
Functional     -4.055757
Street        -15.500133
PoolQC        -20.723994>

(D) Skewness & Box-Cox Transformation

  • Skewness를 보정하는 또다른 방법론 중의 하나는 Box-Cox Transformation을 수행하는 것이다.
    • Box-Cox의 기본적인 원리는 Skeness의 수치를 진단한 후, 왜도가 높은 부분만 가려내어 필터링을 한다는 것이 주 목적이다.
    • scipy 모듈 안에 boxcox1p() 함수를 사용한다.
    • Box-Cox에 대한 설명은 다음 자료를 참조한다.
    • 공식은 크게 2개로 구분된다.
      • $x^{*} = \frac{x^{\lambda}-1}{\lambda} , \text{ if } \lambda \neq 0 $
      • $x^{*} = log(x) , \text{ if } \lambda = 0$
  • $\lambda = 0$ 이면 log-Transformation을 수행한다.
  • $\lambda \neq 0$ 이면 Box-Cox Transformation을 수행한다.
    • 이 때, boxcox1p() 함수가 사용된다는 것을 의미한다.
    • boxcox1p 함수에 대한 구체적인 설명은 공식홈페이지를 참조한다.
  • 물론 $\lambda = 2$인 경우, $\lambda = 0.5$인 경우, $\lambda = -1$인 경우에도 다른 적용법이 있기도 하다. 그러나, 여기에서는 그러한 구체적인 적용법은 Box-Cox(1964)의 논문을 참조하기를 바란다.
  • sklearn에서는 PowerTransformer를 활용할 수도 있다.
  • 이제 구체적으로 코드를 작성한다.
skewness = skewness[abs(skewness) > 0.75]
print("박스-콕스 변환이 필요한 수치형 변수의 개수는 {}으로 확인된다. ".format(skewness.shape[0]))
박스-콕스 변환이 필요한 수치형 변수의 개수는 59으로 확인된다. 
from scipy.special import boxcox1p
skewed_features = skewness.index
lam = 0.15
for fea in skewed_features:
    house_df[fea] = boxcox1p(house_df[fea], lam)

(E) One-hot Encoding

  • 이제 마지막으로 원핫 인코딩을 진행한다.
print("원핫 인코딩 전의 Shape:", house_df.shape)
house_df = pd.get_dummies(house_df)
print("원핫 인코딩 후의 Shape:", house_df.shape)
원핫 인코딩 전의 Shape: (2919, 79)
원핫 인코딩 후의 Shape: (2919, 221)

IV. 결론

  • Feature Engineering의 이론적인 개념을 배웠다.
    • 실제 각각에 대해서 깊이 배우고자 한다면, Feature Engineering 교재를 읽으시는 것을 추천한다.
  • 결측치를 채우는 방법에 대해서 배웠다
  • 데이터 변환 & Label Encoding과 One-Hot Encoding 방법에 대해 기술 하였다.
    • 아직, 각각의 인코딩 방법이 각각 어떠한 머신러닝 알고리즘에 유용한지는 배우지는 않았다. 이는 추후에 다시 정리할 예정이다.
  • 수치형 데이터에 변환을 주는 작업을 진행하였다.
    • 정규화, 표준화의 차이점을 이해한다.
    • Box-Cox에 대해 이해하고 코드를 적용하였다.
  • Feature Engineering이 끝나면 이제 본격적으로 모델링 작업을 수행하도록 한다.