상세 컨텐츠

본문 제목

[머신러닝] Houde Price 데이터를 사용한 데이터 전처리 + EDA+ Feature Engineering + 회귀모델

멋사 AISCOOL 7기 Python

by dundunee 2022. 11. 17. 18:55

본문

🏡DATA: House Price

https://www.kaggle.com/c/house-prices-advanced-regression-techniques

🟡 Description

  • 주택 구매자에게 자신이 꿈꾸는 주택에 대해 설명하도록 요청하면 지하실 천장의 높이나 동서 철도와의 근접성으로 시작하지 않을 것입니다.
  • 그러나 이 데이터 세트는 (그러한 특성이) 침실의 수나 흰색 울타리보다 가격 협상에 훨씬 더 많은 영향을 미친다는 것을 증명합니다.
  • 아이오와주 에임스에 있는 주거용 주택의 (거의) 모든 측면을 설명하는 79개의 설명 변수가 있는 이 경쟁은 각 주택의 최종 가격을 예측하는 데 도전합니다.
  • 평가는 RMSE로 평가하며, 비싼 집값과 싼 집값에 공평하게 영향을 미치고자 한다.
    • 예를 들어 2억짜리 집을 4억으로 예측한 것이 100억짜리 집을 110으로 예측한 것보다 더 잘못예측함.
  • 따라서 예측값의 로그 값으로 제출하라

✅ Case2. Train + Test를 합친 경우

df = pd.concat([train, test])
print(df.shape)
>>> (2919, 80)

1️⃣ 전처리 + EDA

1. 왜도와 첨도 비교하기

  • 왜도(skewness)란? 실수값 확률 변수의 확률 분포 비대칭성을 나타내는 지표이다. 왜도의 값은 양수나 음수가 될 수 있으며, 정의되지 않을 수 있다. skew()
    • 음수일 경우 확률민도함수의 왼쪽부분에 긴 꼬리를 가지며, 중앙값을 포함한 자료가 오른쪽에 더 많이 분포하고 있다. 평균(Mean)과 중위수(Median)가 최빈값(Mode)보다 작는 것을 의미하게 됩니다
    • 양수일 때는 오른쪽부분에 긴 꼬리를 가지고 있으며, 중앙값을 포함한 자료가 왼쪽에 더 많이 분포하고 있다. 평균(Mean)과 중위수(Median)가 최빈값(Mode)보다 크는 것을 의미합니다.
    • 평균과 중앙값이 같으면 왜도는 0이 된다
  • 첨도(kurtosis)란? 확률분포의 뾰족한 정도를 나타내는 척도이다. 관측치들이 어느 정도 집중적으로 중심에 몰려 있는가를 측정할 때 사용된다. kurt()
  • 왜도와 첨도는 변수가 수백개가 될 때 전체적으로 왜도와 첨도가 높은 값을 추출해서 전처리를 할 수 있다.
  • 또한 왜도와 첨도 모두 0에 가까울 수록 정규분포에 가깝다고 볼 수 있다.
print("왜도(Skewness):", train["SalePrice"].skew())
print("첨도(Kurtosis):", train["SalePrice"].kurtosis())
>>> 왜도(Skewness): 1.8828757597682129
>>> 첨도(Kurtosis): 6.536281860064529

train["SalePrice_log1p"] = np.log1p(train["SalePrice"])
print("왜도(Skewness):", train["SalePrice_log1p"].skew())
print("첨도(Kurtosis):", train["SalePrice_log1p"].kurt())
>>> 왜도(Skewness): 0.12134661989685333
>>> 첨도(Kurtosis): 0.809519155707878
  • 로그변환을 해준 후 왜도와 첨도를 봤을 때 정규분포에 가까워진것을 볼 수 있다.

2. 결측치 보기

isna_sum = df.isnull().sum()
isna_mean = df.isnull().mean()

pd.concat([isna_sum, isna_mean], axis=1).nlargest(10,1)

# 결측치 비율이 80%이상인 컬럼은 삭제
null_feature = isna_mean[isna_mean > 0.8].index

df = df.drop(columns=null_feature)
print(df.shape)
>>> (2919, 76)

# 확인
df.isnull().mean().sort_values(ascending=False).nlargest(10)
>>> SalePrice       0.499829
    FireplaceQu     0.486468
    LotFrontage     0.166495
    GarageCond      0.054471
    GarageYrBlt     0.054471
    GarageFinish    0.054471
    GarageQual      0.054471
    GarageType      0.053786
    BsmtExposure    0.028092
    BsmtCond        0.028092
    dtype: float64

2️⃣ 수치형변수

1. 상관분석

corr = df.corr()
corr.loc[corr["SalePrice"] >= 0.5, "SalePrice"].sort_values(ascending=False)
SalePrice       1.000000
OverallQual     0.790982
GrLivArea       0.708624
GarageCars      0.640409
GarageArea      0.623431
TotalBsmtSF     0.613581
1stFlrSF        0.605852
FullBath        0.560664
TotRmsAbvGrd    0.533723
YearBuilt       0.522897
YearRemodAdd    0.507101
Name: SalePrice, dtype: float64
  • 시각화 그려보기
sp_cor_up = corr.loc[corr["SalePrice"] >= 0.7, "SalePrice"].index
sns.pairplot(train[sp_cor_up], corner=True)

2. 파생변수

  • TotalSF = TotalBsmtSF + 1stFlrSF + 2ndFlrSF
df["TotalSF"] = df["TotalBsmtSF"] + df["1stFlrSF"] + df["2ndFlrSF"]
df[["TotalSF","TotalBsmtSF","1stFlrSF","2ndFlrSF"]]

3. 결측치 채우기: .fillna()

# Garage 관련 범주형 변수 'None' 으로 결측치 대체
Garage_None = ['GarageType', 'GarageFinish', 'GarageQual', 'GarageCond']

# df[Garage_None] = df[Garage_None].fillna("None")으로 한줄도 가능함
for col in Grage_None;
    df.loc[df[col].isnull(), col] = "None"

# Garage 관련 수치형 변수 0 으로 결측치 대체
Garage_0 = ['GarageYrBlt', 'GarageArea', 'GarageCars']

# df[Garage_0] = df[Garage_0].fillna(0)
for col in Garage_0:
    df.loc[df[col].isnull(), col] = 0

# 최빈값으로 채우기
fill_mod = ['MSZoning', 'KitchenQual', 'Exterior1st', 'Exterior2nd', 'SaleType', 'Functional']

# df[fill_mod] = df[fill_mod].fillna(df[fill_mod].mode().loc[0])
for col in fill_mod;
		df.loc[df[col].isnull(), col] = df[col].value_counts().sort_values(ascending=False).index[0]

# 중앙값으로 대체
feature_num = df.select_dtypes(include="number").columns.tolist()
feature_num.remove("SalePrice")

# df[feature_num] = df[feature_num].fillna(df[feature_num].median())
for col in feature_num:
		df.loc[df[col].isnull(), col] = df.describe().loc["50%"][col]

4. 데이터 타입 바꾸기: .astype()

  • 수치데이터의 nunique값을 구해서 어떤 값을 one-hot-encoding하면 좋을 지 찾아보자
  • 수치데이터를 그대로 ordinal encoding된 값을 그대로 사용해도 되지만 범주값으로 구분하고자 하면 category나 object 타입으로 변환하면 one-hot encoding 할 수 있다.
  • 이것의 목적은 ordinal enoding을 one-hot-encoding할 수 있다.
num_to_str_col = ["MSSubClass", "OverallCond", "YrSold", "MoSold"]

# df[num_to_str_col] = df[num_to_str_col].astype('object')
# 문자형태로 변경하게 되면 나중에 one-hot encoding으로 변경하게 된다.
for col in num_to_str_col:
    df[col] = df[col].astype("object")
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2919 entries, 1 to 2919
Data columns (total 4 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   MSSubClass   2919 non-null   object
 1   OverallCond  2919 non-null   object
 2   YrSold       2919 non-null   object
 3   MoSold       2919 non-null   object
dtypes: object(4)
memory usage: 178.6+ KB

5. 로그변환

📍 왜도가 큰 피쳐

# 왜도가 큰 피쳐들만 찾기
feature_skew = abs(df.skew())
skewed_col = feature_skew[feature_skew>2].index
skewed_col
  • 이런 피처들은 로그변환을 해줘야 하지만 히스토그램을 통해 로그변환이 필요한지 살펴볼 필요가 있다.

📍nunique()가 작은 피쳐

# 수치형변수 중 nunique값이 작은 피처 찾기
log_features = ['LotFrontage','LotArea','MasVnrArea','BsmtFinSF1','BsmtFinSF2','BsmtUnfSF',
                 'TotalBsmtSF','1stFlrSF','2ndFlrSF','LowQualFinSF','GrLivArea',
                 'BsmtFullBath','BsmtHalfBath','FullBath','HalfBath','BedroomAbvGr','KitchenAbvGr',
                 'TotRmsAbvGrd','Fireplaces','GarageCars','GarageArea','WoodDeckSF','OpenPorchSF',
                 'EnclosedPorch','3SsnPorch','ScreenPorch','PoolArea','MiscVal','YearRemodAdd']

lf_nu = df[log_features].nunique()[df[log_features].nunique() < 30].index
num_log_feature = list(set(log_feature) - set(lf_nu))
num_log_feature # 여기서 출력된 변수들은 로그변환을 해줘도 될 가능성이 있다.

6. 다항피쳐 생성

sklearn에서 제공되는 기능 외에도 pandas로 바로 계산이 가능하다

주로 uniform한 분포일 때 값을 강조하거나 구분해서 보기 위해 사용한다.

squared_features = ['YearRemodAdd', 'LotFrontage', 
              'TotalBsmtSF', '1stFlrSF', '2ndFlrSF', 'GrLivArea','GarageArea', 'TotalSF']

df_square = df[squared_features] ** 2

3️⃣ 범주형변수

df_cate = df.select_dtypes(include="object")

1. 결측치 처리

  • 범주형 변수는 결측치를 남겨줘도 상관이 없다. 원핫인코딩으로 처리되기 떄문이다.
  • 하지만 결측치가 많은 변수를 제외하고자 한다면 슬라이싱으로 제거할 수 있다.
feature_cate = df_cate.isnull().mean().sort_values()[:-2].index
feature_cate # 사용할 피쳐

4️⃣ 모델링

1. label 및 feature names 정의

label_name = "SalePrice_log1p"

feature_names = []
feature_names.extend(num_log_feature)
feature_names.append("TotalSF")
feature_names.extend(feature_cate)
feature_names.remove("2ndFlrSF")
feature_names.remove("1stFlrSF")
feature_names.remove("BsmtFinSF1")
feature_names.remove("BsmtFinSF2")
feature_names

list.append()는 봉지과자를 통째로 넣는 것이고, list.extend()는 봉지과자를 뜯어서 넣는것이다.

a = []
# append
a.append([1, 2, 3])
a => [[1, 2, 3]] # 리스트의 인덱스개수는 1개이다.

# extend
a.extend([1, 2, 3])
a => [1, 2, 3] # 리스트의 인덱스개수는 3개이다.

2. 원핫인코딩

train과 test가 concat되어 있다면 pd.get_dummies를 사용하는 것이 편리하다.

df_ohe = pd.get_dummies(df[feature_names])

df[feature_names].shape, df_ohe.shape
>>> ((2919, 57), (2919, 297))

3. 데이터셋 만들기

X_train = df_ohe.loc[train.index]
X_test = df_ohe.loc[test.index]
y_train = train[label_name]

4. 머신러닝 모델 불러오기

from sklearn.ensemble import RandomForestRegressor

model = RandomForestRegressor(random_state=42)
model

4.1 KFold로 Cross Validation하기 & 예측값 구하기

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_predict

kf = KFold(random_state=42, n_splits=5, suffle=True)
y_val_predict = cross_val_predict(model, X_train, y_train, cv=kf, n_jobs=-1)

4.2 metrics

rmse = np.sqrt(np.square(y_train-y_predict).mean())
rmse
>>> 0.15360940285057656
from sklearn.metrics import r2_score
r2score = r2_score(y_train, y_val_predict)
r2score
>>> 0.8520176585188195

4.3 regplot/ kdeplot으로 비교하기

sns.regplot(x = y_train, y = y_val_predict)

sns.kdeplot(y_train)
sns.kdeplot(y_val_predict)

5. 학습(훈련)과 예측

y_predict = model.fit(X_train, y_train).predict(X_test)

# 피쳐 중요도 시각화
fi = pd.Series(model.feature_importances_)
fi.index = model.feature_names_in_
fi.nlargest(20).plot(kind="barh")


feature engineering

수치형 데이터

  • 결측치 대체(Imputation)
    • 원래의 값이 너무 왜곡되지 않도록 하는 주의가 필요하다.
    • 중앙값, 평균값 등의 대표값으로 대체할 수 있다.
    • 머신러닝 기법ㅇㄹ 통해 대체할 수 있다.
  • 스케일링: Standard, Minmax, Robust
  • 변환: log
  • 이상치(너무 크거나 작은 범위를 벗어나는 값) 제거 혹은 대체
  • 오류값(잘못된 값)제거 혹은 대체
  • 이산화: cut, qcut

범주형 데이터

  • 결측치 대체
  • 인코딩: label, ordinal, one-hot-enciding
  • 범주 중 빈도가 적은 값은 대체하기

관련글 더보기