Chap 2) Data Analysis with Models

Introduction

• 문제해결(problem solving) 과정

1) 문제와 관련된 data의 수집/탐색/표현

- 구조적/비구조적

- 특징 파악: 독립변수 – x1, x2, ..., xn, 종속변수 – y1, y2

분포도, 평균, 분산, 상관관계(correlation) 등

- 시각화(visualization)

→ ‘탐색적’ 데이터 분석 (exploratory data analysis)

2) 분석 모델(model)

- 적합한 모델을 사용하여 원하는 information을 추출

- 회귀(regression)/분류(classification)/군집(clustering)/인식(recognition) 등

3) 성능(performance) 평가

- 평가 지표: 정확도(accuracy)/정밀도(precision)/재현도(recall)

/특이도(specificity) 등

• 예측 모델: 값 예측

- 회귀(regression): 선형/비선형/다중

• 분류(classification) 모델 (지도 학습): 클래스 예측

- 로지스틱 회귀, Decision-tree, SVM, kNN

• 군집(clustering) 모델 (비지도 학습): 클래스 생성

- 계층적/비계층적

• 인식(recognition) 모델 (지도 학습): 이미지/음성 인식

- 딥러닝(CNN/RNN)

e.g., 예측/분류/군집/인식

수면시간(x1)

예측 모델

분류 모델

체중(y1)

운동시간(x2)	당뇨

혈압(y2)                  비당뇨

칼로리섭취(x3)

data#1         data#2              data#11000

8   [    65     5   [    80         6    [    55

1    ...  110    0    ...  120    ...   3     ...  100

1500   ]        2500   ]            1100    ]

Copyright (c) 2019 [윤기태 – 이것이 데이터분석이다]

https://github.com/yoonkt200/python-data-analysis

• Example #1: 프로 야구 선수의 연봉 예측(regression)

<Step1. 탐색> 프로야구 연봉 데이터 살펴보기

[프로야구 연봉 데이터셋의 기본 정보]

[회귀 분석에 사용할 피처 살펴보기]

<Step2. 예측> : 투수의 연봉 예측 – 다중 선형 회귀

[피처들의 단위 맞춰주기 : 피처 스케일링]

[피처들의 단위 맞춰주기 : one-hot-encoding]

[피처들의 상관관계 분석]

[회귀 분석 적용하기]

<Step3. 평가> : 예측 모델 평가

[어떤 피처가 가장 영향력이 강한 피처일까]

[예측 모델의 평가]

[회귀 분석 예측 성능을 높이기 위한 방법 : 다중 공선성 확인]

[믿을만한 피처로 다시 학습하기]

<Step4. 시각화> : 분석 결과의 시각화

[예상 연봉과 실제 연봉 비교]

<Step1. 탐색> 프로야구 연봉 데이터 살펴보기

# 1) Google Colab 로그인,

# 2) Google Drive 연결(mount)

# yun141.ipynb

# 한글폰트 사용 in Colab

%matplotlib inline

import matplotlib as mpl

import matplotlib.pyplot as plt

import matplotlib.font_manager as fm

!apt-get update -qq

!apt-get install fonts-nanum* -qq

path = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'

font_name = fm.FontProperties(fname=path, size=10).get_name()

print(font_name)

plt.rc('font', family=font_name)

fm._rebuild()

mpl.rcParams['axes.unicode_minus'] = False

# ‘런타임>런타임 다시 시작’ 실행 후 다시 한번 위의 코드를 실행하여 폰트 적용!!! <중요>

# 폰트 확인

import matplotlib.font_manager as fm

sys_font=fm.findSystemFonts()

print(f"sys_font number: {len(sys_font)}")

print(sys_font)

nanum_font = [f for f in sys_font if 'Nanum' in f]

print(f"nanum_font number: {len(nanum_font)}")

nanum_font

#-------------------------------------------

import pandas as  pd

import numpy as np

import matplotlib.pyplot as plt

# Google Drive 연결(mount)

from google.colab import drive

drive.mount('/content/drive')

# read_csv 함수로 데이터를 Dataframe 형태로 불러옵니다.

import os

os.chdir('/content/drive/MyDrive/Colab Notebooks')

pitcher=pd.read_csv('pitcher_stats_2017.csv')

batter=pd.read_csv('batter_stats_2017.csv')

pitcher.columns

batter.columns

pitcher.head

batter.head

# 예측할 대상인 '연봉'에 대한 정보

pitcher['연봉(2018)'].describe()

# fm._rebuild() 실행

fm._rebuild()

# 2018년 연봉 분포를 출력

# bins: 해당 막대 영역(bin) 채우기

plt.title('연봉(2018)')

pitcher['연봉(2018)'].hist(bins=100)

# 연봉의 Boxplot을 출력

pitcher.boxplot(column=['연봉(2018)'])

# [회귀 분석에 사용할 피처 살펴보기] - '선수명'/'팀명' 빠짐

pitcher_features_df = pitcher[['승', '패', '세', '홀드', '블론', '경기', '선발', '이닝', '삼진/9',

'볼넷/9', '홈런/9', 'BABIP', 'LOB%', 'ERA', 'RA9-WAR', 'FIP', 'kFIP', 'WAR',

'연봉(2018)', '연봉(2017)']]

pitcher_features_df

# 피처 각각에 대한 histogram을 출력

def plot_hist_each_column(df):

plt.rcParams['figure.figsize'] = [20, 16]

fig = plt.figure(1)

# df의 column 갯수 만큼의 subplot을 출력: 5

for i in range(len(df.columns)):

ax = fig.add_subplot(5, 5, i+1)

plt.hist(df[df.columns[i]], bins=50)

ax.set_title(df.columns[i])

plt.show()

# 함수 실행

plot_hist_each_column(pitcher_features_df)

<Step2. 예측> : 투수의 연봉 예측 – 다중 선형 회귀

# pandas 데이터를 출력할 때, scientific-notation이 아닌 float 모양으로 출력

pd.options.mode.chained_assignment = None

# [피처들의 단위 맞춰주기 : 피처 스케일링]

# x축의 값 단위가 서로 다름 -  feature scaling(피처들의 단위 맞춰주기)이 필요

# 피처 각각에 대한 scaling을 수행하는 함수를 정의

# z score = (x - x의평균)/x의표준편차

def standard_scaling(df, scale_columns):

for col in scale_columns:

series_mean = df[col].mean()

series_std = df[col].std()

df[col] = df[col].apply(lambda x: (x-series_mean)/series_std)

return df

scale_columns = ['승', '패', '세', '홀드', '블론', '경기', '선발', '이닝', '삼진/9',

'볼넷/9', '홈런/9', 'BABIP', 'LOB%', 'ERA', 'RA9-WAR', 'FIP', 'kFIP', 'WAR', '연봉(2017)']

pitcher_df = standard_scaling(pitcher, scale_columns)

pitcher_df

# 예측할 y 변수: 연봉(2018)

pitcher_df = pitcher_df.rename(columns={'연봉(2018)': 'y'})

pitcher_df.head(5)

# [숫자가 아닌 ‘범주형’ 피처들의 코드화: one-hot-encoding]

# 팀명(9개) 피처를 one-hot encoding으로 변환 - get_dummies()

team_encoding = pd.get_dummies(pitcher_df['팀명'])

pitcher_df = pitcher_df.drop('팀명', axis=1)

pitcher_df = pitcher_df.join(team_encoding)

team_encoding.head(5)

pitcher_df.head()

# [회귀 분석 적용하기]

from sklearn import linear_model

from sklearn.model_selection import train_test_split

from sklearn.metrics import mean_squared_error

from math import sqrt

# 회귀 분석을 위한 ‘학습’/‘테스트’ 데이터셋 분리 – sklearn/train_test_split()

# train:test = 8:2

x = pitcher_df[pitcher_df.columns.difference(['선수명', 'y'])]

y = pitcher_df['y']

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=19)

x_train.shape

x_test.shape

y_train.shape

y_test.shape

# 회귀 모델 학습 실행 – linear_model.LinearRegression()

# 모델 결정 – 선형 회귀

lr = linear_model.LinearRegression()

# 학습 데이터로 학습 실행

model = lr.fit(x_train, y_train)

# 학습된 계수를 출력: x1, x2, ..., x28 (4*7=28)

print(lr.coef_)

<Step3. 평가> : 예측 모델 평가

# [어떤 피처가 가장 영향력이 강한 피처일까]

import statsmodels.api as sm

# statsmodel 라이브러리로도 회귀 분석 수행 가능 - OLS()

x_train = sm.add_constant(x_train)

model = sm.OLS(y_train, x_train).fit()

# 계수에 대한 자세한 분석 제공 – summary()

# 성능 평가 지표: R-squared/ Adj. R-squared – 1에 근접할수록 실제치에 가까움

model.summary()

# 회귀식의 유의성 검정: Prob (F-statistic)<0.05 미만

# 각 피쳐의 P값 <0.05 미만 – 유의미한 피쳐: WAR, 연봉(2017), 한화

import matplotlib as mpl

# 현재 OS 내에 설치된 폰트를 확인

set(sorted([f.name for f in mpl.font_manager.fontManager.ttflist]))

# 한글 출력을 위한 설정

mpl.rc('font', family='NanumBarunGothic')

plt.rcParams['figure.figsize'] = [20, 16]

# 회귀 계수를 리스트로 반환

coefs = model.params.tolist()

coefs_series = pd.Series(coefs)

coefs_series

# 변수명을 리스트로 반환

x_labels = model.params.index.tolist()

x_labels

# 회귀 계수를 출력

ax = coefs_series.plot(kind='bar')

ax.set_title('feature_coef_graph')

ax.set_xlabel('x_features')

ax.set_ylabel('coef')

ax.set_xticklabels(x_labels)

# 크기가 클수록 영향력이 큼 – FIP, WAR, 홈런, 연봉(2017)

# [예측 모델의 평가]

# 다시 sklearn의 LinearRegression()을 수행하여 model 생성

x = pitcher_df[pitcher_df.columns.difference(['선수명', 'y'])]

y = pitcher_df['y']

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=19)

lr = linear_model.LinearRegression()

model = lr.fit(x_train, y_train)

# 회귀 분석 모델 성능을 평가

# train R2 score를 출력

print(model.score(x_train, y_train))

# test R2 score를 출력

print(model.score(x_test, y_test))

# 두 점수의 차이가 작을수록 예측력이 좋은 모델

# 학습 점수 > 테스트 점수 – 과적합(overfitting)

# RMSE(Root Mean Square Error)를 사용하여 회귀 분석 모델을 평가

# train RMSE score를 출력

y_predictions = lr.predict(x_train)

print(sqrt(mean_squared_error(y_train, y_predictions)))

# test RMSE score를 출력

y_predictions = lr.predict(x_test)

print(sqrt(mean_squared_error(y_test, y_predictions)))

# train 데이터셋의 오차 > test 데이터셋의 오차

# 가장 이상적인 경우는 Train score, Test score가 차이가 없는 것

# [피처들의 상관관계 분석]

# 피처간의 상관계수 행렬을 계산

corr = pitcher_df[scale_columns].corr(method='pearson')

show_cols = ['win', 'lose', 'save', 'hold', 'blon', 'match', 'start',

'inning', 'strike3', 'ball4', 'homerun', 'BABIP', 'LOB',

'ERA', 'RA9-WAR', 'FIP', 'kFIP', 'WAR', '2017']

corr

import seaborn as sns

# corr 행렬 히트맵을 시각화

plt.rc('font', family='NanumGothic')

sns.set(font_scale=1.5)

hm = sns.heatmap(corr.values, cbar=True, annot=True, square=True, fmt='.2f',

annot_kws={'size': 15}, yticklabels=show_cols, xticklabels=show_cols)

plt.tight_layout()

plt.show()

# 색깔이 옅을수록 1, 진할수록 –1에 가까움

# [회귀분석 예측 성능을 높이기 위한 방법 : '다중공선성' 확인]

# 회귀 분석은 피처들 간의 '독립성'을 전제 - 변수 간 상관관계가 높은 피처들을 제거

# '다중공선성: VIF(Variance Inflation Factor, 분산팽창요인)으로 평가 -

# variance_inflation_factor()

from statsmodels.stats.outliers_influence import variance_inflation_factor

# for 반복문을 사용하여 피처마다의 VIF 계수를 출력

vif = pd.DataFrame()

vif["VIF Factor"] = [variance_inflation_factor(x.values, i) for i in range(x.shape[1])]

vif["features"] = x.columns

vif.round(1)

# VIF: 10~15보다 크면 다중공선성 문제 발생 판단

# [적절한 피처를 선정하여 다시 학습]

# <선정 과정>

# 1) VIF 계수가 높은 피처들을 우선적으로 제거

#    (단, (FIP, kFIP)와 같이 유사한 두가지 피처중에는 하나만을 제거)

# 2) 다시 공선성을 검증

#    이전 단계에서는 VIF 계수가 높았던 볼넷, 삼진 등의 피처의 VIF 계수가 낮아진 것을 확인

#    (VIF 계수가 비정상적으로 높은 피처를 제거하면, 다른 피처들의 공선성도 자연스레 감소)

# 3) 여전히 VIF 계수가 높은 피처들을 제거

# 4) 남은 피처를 토대로 다시 한 번 회귀분석을 실시

#    (분석 결과에서 p-value가 유의하면서도 영향력이 큰 피처들을 선정)

# 위의 과정을 거쳐 ['FIP', 'WAR', '볼넷/9', '삼진/9', '연봉(2017)'] 피처를 선정

x = pitcher_df[['FIP', 'WAR', '볼넷/9', '삼진/9', '연봉(2017)']]

y = pitcher_df['y']

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=19)

# 모델을 결정하고 학습 실행

lr = linear_model.LinearRegression()

model = lr.fit(x_train, y_train)

# 평가 결과를 출력 - R2 score

print(model.score(x_train, y_train))

print(model.score(x_test, y_test))

# 이전 모델과 비교

# train RMSE score를 출력

y_predictions = lr.predict(x_train)

print(sqrt(mean_squared_error(y_train, y_predictions)))

# test RMSE score를 출력

y_predictions = lr.predict(x_test)

print(sqrt(mean_squared_error(y_test, y_predictions)))

# 이전 모델과 비교

# 피처마다의 VIF 계수를 출력

x = pitcher_df[['FIP', 'WAR', '볼넷/9', '삼진/9', '연봉(2017)']]

vif = pd.DataFrame()

vif["VIF Factor"] = [variance_inflation_factor(x.values, i) for i in range(x.shape[1])]

vif["features"] = x.columns

vif.round(1)

# 다중공선성 문제 제거

<Step4. 시각화> : 분석 결과의 시각화

# [회귀식을 사용한 예측 연봉과 실제 연봉 비교]

# 예측 연봉(2018) 계산

x = pitcher_df[['FIP', 'WAR', '볼넷/9', '삼진/9', '연봉(2017)']]

predict_2018_salary = lr.predict(x)

predict_2018_salary

# 예측된 2018년 연봉을 데이터프레임의 column으로 생성: pitcher_df

pitcher_df['예측연봉(2018)'] = pd.Series(predict_2018_salary)

pitcher_df

# 원래의 데이터 프레임을 다시 로드: 실제값 - pitcher

pitcher=pd.read_csv('pitcher_stats_2017.csv')

pitcher = pitcher[['선수명', '연봉(2017)']]

pitcher

# 계산된 데이터 프레임(pitcher_df, 예측 연봉)에 2018년 '실제 연봉'(pitcher)을 추가

result_df = pitcher_df.sort_values(by=['y'], ascending=False)

result_df.drop(['연봉(2017)'], axis=1, inplace=True, errors='ignore')

result_df = result_df.merge(pitcher, on=['선수명'], how='left')

result_df = result_df[['선수명', 'y', '예측연봉(2018)', '연봉(2017)']]

result_df.columns = ['선수명', '실제연봉(2018)', '예측연봉(2018)', '작년연봉(2017)']

result_df

# 재계약하여 연봉이 변화한 선수만을 대상으로 관찰

result_df = result_df[result_df['작년연봉(2017)'] != result_df['실제연봉(2018)']]

result_df = result_df.reset_index()

result_df = result_df.iloc[:10, :]

result_df.head(10)

# 선수별 연봉 정보(작년 연봉, 예측 연봉, 실제 연봉)를 bar 그래프로 출력

mpl.rc('font', family='NanumGothic')

result_df.plot(x='선수명', y=['작년연봉(2017)', '예측연봉(2018)', '실제연봉(2018)'], kind="bar")

• Example #2: 타이타닉 생존자 분류(classification) - 생존자/비생존자