Python/데이터분석 실습

[Kaggle ] Pima Indians Diabetes 예측 ② 데이터 전처리 후 모델 학습/예측

dori_0 2022. 3. 1. 19:52

Pima Indians Diabetes 예측하기

② 데이터 전처리 후 모델 학습/예측

데이터 전처리는 모든 데이터 분석 프로젝트에서 반드시 거쳐야 하는 과정입니다.

먼저, 아무것도 하지 않고 모델을 실행해 정확도를 본 후

여러 방법으로 데이터를 다듬어 정확도의 변화를 살펴보며 모델 성능을 개선해보겠습니다.

 


 

 

 ▶ 전처리 없이 모델 실행  

1. 데이터 로드 후 8:2로 학습, 예측 데이터로 나누기

# 라이브러리 로드
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# 데이터셋 로드
df = pd.read_csv("C:/data/diabetes.csv")

# 8:2로 학습, 예측 데이터셋 나누기
# 전체 데이터의 행에서 80% 위치에 해당되는 값을 구해서 split_count라는 변수에 담기
split_count = int(df.shape[0] * 0.8)

# train, test로 슬라이싱을 통해 데이터 나누기
train = df[:split_count].copy()
test = df[split_count:].copy()

# feature_names라는 변수에 학습과 예측에 사용할 컬럼 가져오기
feature_names = train.columns[:-1].tolist()

# label_name이라는 변수에 예측할 컬럼의 이름을 담습니다.
label_name = train.columns[-1]

# 학습, 예측 데이터셋 만들기
X_train = train[feature_names]
y_train = train[label_name]
X_test = test[feature_names]
y_test = test[label_name]

 

2. 머신러닝 알고리즘 Decision Tree를 불러와 학습 후 예측하기

from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier()

# 학습하기
model.fit(X_train, y_train)
# 예측하기
y_predict = model.predict(X_test)

 

3. 학습한 Decision Tree를 시각화하기

  • 의사결정나무의 장점 - 시각화해서 볼 수 있다
from sklearn.tree import plot_tree

plt.figure(figsize=(20, 20))
tree = plot_tree(model, 
                 feature_names=feature_names,
                 filled=True, 
                 fontsize=10)

 

  • Glucose가 분류에 가장 중요한 역할을 하는 것으로 보입니다.

 

 

4. 피쳐의 중요도 확인하기

# 피쳐의 중요도 추출하기
model.feature_importances_

# 피쳐의 중요도 시각화하기
sns.barplot(x = model.feature_importances_, y = feature_names)

  • 위에서 본대로 Glucose가 가장 중요한 역할을 하는 것으로 보입니다.

 

 

5. 정확도 측정하기

# 실제값 - 예측값을 해주면 같은 값은 0으로 나온다.
diff_count = abs(y_test - y_predict).sum()
diff_count  # 43

# 정확도
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_predict) * 100  # 72.07792207792207

# 정확도 직접 구하는 법
# (len(y_test) - diff_count) / len(y_test) * 100
  • 아무런 전처리를 하지 않고 모델을 실행해보니 약 72%의 정확도를 나타냅니다.

 

 

 

모델의 성능을 올리기 위해 데이터 전처리를 하겠습니다.

 ▶ Feature Engineering 

1. 수치형 변수를 범주형 변수로 만들기

 

(1) 임신 횟수

탐색 결과, 임신 횟수가 6번까지는 당뇨병이 발병하지 않을 확률이 더 높지만 그 이상은 발병할 확률이 더 높았습니다.

 

따라서 임신 횟수 6번을 기준으로 임신 횟수를 범주형 변수로 변형해주겠습니다.

# 임신횟수를 범주형으로 바꾸기
df["Pregnancies_high"] = df["Pregnancies"] > 6

 

그 후 학습, 예측에 사용할 컬럼에서 Pregnancies 컬럼을 제거해줍니다.

# feature_names라는 변수에 학습과 예측에 사용할 컬럼명을 가져옵니다.
feature_names = train.columns.tolist()
feature_names.remove("Pregnancies")
feature_names.remove("Outcome")

 

이제 정확도를 살펴보겠습니다.

# 틀린 개수
diff_count = abs(y_test - y_predict).sum()
diff_count  # 39

# 예측의 정확도
(len(y_test) - diff_count) / len(y_test) * 100  # 74.67532467532467
  • 틀린 개수가 조금 줄고, 정확도는 조금 높아졌습니다.

 

 

(2) 나이 

탐색 결과, 30세까지는 당뇨병이 발병하지 않을 확률이 더 높지만 그 이상은 발병할 확률이 더 높았습니다.

 

따라서 30세, 60세를 기준으로 연령대를 나눠주겠습니다.

# 연령대를 범주형으로 바꿔보기
# One-Hot-Encoding방법
df["Age_low"] = df["Age"] < 30
df["Age_middle"] = (df["Age"] >= 30) & (df["Age"] <= 60)
df["Age_high"] = df["Age"] > 60
df[["Age", "Age_low", "Age_middle", "Age_high"]].head()

 

그 후 학습, 예측에 사용할 컬럼에서 Age 컬럼을 제거해줍니다.

feature_names = train.columns.tolist()
feature_names.remove("Pregnancies")
feature_names.remove("Outcome")
feature_names.remove("Age")

 

이제 정확도를 살펴보겠습니다.

diff_count = abs(y_test - y_predict).sum()  # 55
(len(y_test) - diff_count) / len(y_test) * 100  # 64.28571428571429
  • 연령대를 범주화하니 성능이 오히려 낮아진 것을 확인할 수 있습니다.
  • 피쳐 엔지니어링을 한다고 성능이 무조건 좋아지는 것은 아닙니다.

 

 

2. 결측치 다루기

 

(1) 평균값

 

앞서 확인했을때, 인슐린의 결측치가 많았으므로 결측치를 조정해주도록 하겠습니다.

# 결측치(0)을 nan으로 변경
df["Insulin_nan"] = df["Insulin"].replace(0, np.nan)

# 결측치 비율
df["Insulin_nan"].isnull().mean()  #0.4869791666666667

 

Insulin과 Insulin_nan의 평균값, 중앙값을 Outcome에 따라 살펴보겠습니다.

df.groupby(["Outcome"])["Insulin", "Insulin_nan"].agg(["mean", "median"])

 

다음을 바탕으로 인슐린의 결측치를 평균값으로 채워주겠습니다.

# 결측치 채우기 (인슐린 중앙값으로)
df.loc[(df["Outcome"] == 0) & (df["Insulin_nan"].isnull()), "Insulin_nan"] = 130.3
df.loc[(df["Outcome"] == 1) & (df["Insulin_nan"].isnull()), "Insulin_nan"] = 206.8

 

이제 모델을 학습시키고 정확도를 확인해보겠습니다.

diff_count = abs(y_test - y_predict).sum()  # 23
(len(y_test) - diff_count) / len(y_test) * 100  # 84...
  • 틀린 개수가 23개로 줄었고, 정확도가 높아진 것을 확인할 수 있습니다.

 

 

(2) 중앙값

 

이번에는 인슐린의 결측치에 평균값이 아닌 중앙값을 넣은후 정확도를 살펴보겠습니다.

# 결측치 채우기 (인슐린 중앙값으로)
df.loc[(df["Outcome"] == 0) & (df["Insulin_nan"].isnull()), "Insulin_nan"] = 102.5
df.loc[(df["Outcome"] == 1) & (df["Insulin_nan"].isnull()), "Insulin_nan"] = 169.5

diff_count = abs(y_test - y_predict).sum()  # 19
(len(y_test) - diff_count) / len(y_test) * 100  # 87.01298701298701
  • 정확도가 87%로 높아진 것으로 보아 결측치에 평균값보다 중앙값을 넣는 것이 더 좋을 것 같습니다.

 

 

 

3. 수치형 변수를 정규분포 형태로 만들기

 

인슐린의 분포를 보면 다음과 같이 한 쪽에 몰려있습니다.

sns.distplot(df.loc[df["Insulin"]> 0, "Insulin"])

 

왜도, 첨도를 줄이고 정규분포로 나타내기 위해 log를 취하겠습니다.

로그를 취하면 0 이하의 마이너스 값들은 음의 무한대로 수렴하므로 1 같은 작은 수를 더해주겠습니다.

# 로그 변환
sns.distplot(np.log(df.loc[df["Insulin"]> 0, "Insulin"]+1))

 

이번에는 lnsulin_nan을 살펴보겠습니다.

sns.distplot(df["Insulin_nan"])

lnsulin_nan도 치우쳐져 있으므로 로그를 취하겠습니다.

 

df["Insulin_log"] = np.log(df["Insulin_nan"] + 1)
sns.distplot(df["Insulin_log"])

 

이제 모델의 정확도를 보겠습니다.

diff_count = abs(y_test - y_predict).sum()  # 18
(len(y_test) - diff_count) / len(y_test) * 100
  • 아주 약간 성능이 좋아진 것이 확인됩니다.
  • 머신러닝, 딥러닝 알고리즘은 정규분포 형태를 이룰때 더 좋은 성능을 냅니다.

 

 

 

4. 파생변수 만들기

 

앞서 상관 분석을 통해 인슐린과 글루코스의 상관관계가 높음을 확인했습니다.

Insulin_nan과 Glucose의 분포를 확인해보겠습니다.

sns.lmplot(data=df, x="Insulin_nan", y="Glucose", hue="Outcome")

  • 두 컬럼 모두 100이하의 값에서는 당뇨병 발병이 잘 안되는 것으로 보입니다.
  • 한 줄로 세워진 점들은 결측치를 중앙값으로 채워줬기 때문입니다.

 

인슐린, 글루코스의 상관계수가 낮은 구간을 파생변수로 만들어주겠습니다.

df["low_glu_insulin"] = (df["Glucose"] < 100) & (df["Insulin_nan"] <= 102.5)
pd.crosstab(df["Outcome"], df["low_glu_insulin"])

  • 글루코스가 100보다 작으면서 인슐린이 102.5보다 작을 때, 발병할 확률이 낮다는 것을 확인할 수 있습니다.

 

low_glu_insulin 컬럼을 추가해 모델 학습 후 예측해보겠습니다.

diff_count = abs(y_test - y_predict).sum()  # 18
(len(y_test) - diff_count) / len(y_test) * 100
  • 이전과 비슷한 성능을 보입니다.

 

 

 

5. 이상치 다루기

 

이번에는 인슐린의 이상치를 확인한 후 제거해보겠습니다.

plt.figure(figsize=(15, 2))
sns.boxplot(df["Insulin_nan"])

  • 600 이상을 보면 몇 개의 이상치들이 존재한다는 것을 확인할 수 있습니다.

 

인슐린의 통계량을 살펴보겠습니다.

df["Insulin_nan"].describe()

  • max와 75% 값의 차이가 큰 것이 확인됩니다.

 

다음으로 이상치 값을 확인해보겠습니다.

IQR1 = df["Insulin_nan"].quantile(0.25)
IQR3 = df["Insulin_nan"].quantile(0.75)
IQR = IQR3 - IQR1
IQR  #67.0

# 이상치값
OUT = IQR3 + (IQR * 1.5)
OUT  #270.0
# 이상치값 개수
df[df["Insulin_nan"] > OUT].shape  #(51, 16)
  • 이상치 값은 270으로 이상치에 해당하는 개수는 총 51개입니다.

51개를 모두 제거하기엔 이상치 값이 많으므로 600 이상의 데이터만 제거하겠습니다.

 

 

이제 모델 학습 후 정확도를 살펴보겠습니다.

# 데이터 학습 후 예측
df = pd.read_csv("C:/data/diabetes.csv")

# 8:2로 데이터 나누기
split_count = int(df.shape[0] * 0.8)

# train, test 슬라이싱을 통해 데이터 나누기
train = df[:split_count].copy()
train = train[train["Insulin_nan"] < 600]
test = df[split_count:].copy()

# feature_names라는 변수에 학습과 예측에 사용할 컬럼명 가져오기
feature_names = train.columns.tolist()
feature_names.remove("Pregnancies")
feature_names.remove("Outcome")
feature_names.remove("Age_low")
feature_names.remove("Age_middle")
feature_names.remove("Age_high")
feature_names.remove("Insulin")
feature_names.remove("Insulin_log")

# feature_names라는 변수에 학습과 예측에 사용할 컬럼명 가져오기
feature_names = train.columns[:-1].tolist()
# label_name이라는 변수에 예측할 컬럼의 이름 담기
label_name = train.columns[-1]

# 학습, 예측 데이터셋 만들기
X_train = train[feature_names]
y_train = train[label_name]
X_test = test[feature_names]
y_test = test[label_name]

# 틀린개수, 정확도
diff_count = abs(y_test - y_predict).sum()  # 17
(len(y_test) - diff_count) / len(y_test) * 100
  • 이전보다 조금 더 나아진 것이 확인됩니다.

 

 

  • 이처럼 데이터셋 그대로 모델 학습 후 예측하는 것보다, 다양한 데이터 전처리 후 모델 학습을 해주면 더 좋은 성능을 낸다는 것을 알게되었습니다.

 

 

 


 

다음 수업을 바탕으로 실습하였습니다.

 

프로젝트로 배우는 데이터사이언스

부스트코스 무료 강의