본문 바로가기

[입문] 데이터 사이언스? 그게 뭔가요?

데이터 사이언스 입문자를 위한 [시각화 / 예측]

타이타닉 승객 데이터셋을 이용한 데이터 시각화 / 예측

이전 글에서 이어집니다.

전처리 단계에서 새로 추가된 컬럼 설명

  • FamilySize -> 배에 탑승한, 나를 포함한 우리 가족의 총 인원수 입니다.

  • Nationality -> 탑승한 항구로부터 유추한 탑승객의 국적 입니다.

  • Nationality_FR / Nationality_UK -> 원-핫-인코딩한 국적 입니다.

  • male / female -> 원-핫-인코딩한 성별 입니다.

  • Fare_Low

  • Fare_Med -> 수치형 자료인 Fare를 3 개의 그룹으로 나누었습니다.

  • Fare_High

  • Class_Low

  • Class_Mid -> Pclass를 원-핫-인코딩 하였습니다.

  • Class_High

Load Dataset

모든 데이터 분석의 시작은 데이터를 읽어오는 것입니다.

파일의 경로를 지정하는 방법에 주의하셔야 합니다.

만일 read_csv를 실행할 때 (FileNotFoundError)라는 이름의 에러가 난다면 경로가 제대로 지정이 되지 않은 것입니다.

다음의 링크에서 경로를 지정하는 법을 다루고 있습니다.

# 판다스의 read_csv로 titanic_modified.csv 파일을 읽어옵니다.
# 읽어온 데이터를 data 라는 이름의 변수에 할당합니다.
# index_col="PassengerId" => PassengerId 항목을 인덱스로 사용하겠다는 의미입니다.

import pandas as pd
data = pd.read_csv("data/titanic_modified.csv", index_col="PassengerId")
# 새로 불러온 변수 data 의
# 가로, 세로 크기와
# 첫 5 개 행을 확인합니다.

print(data.shape)
data.head()
(891, 24)
  Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin ... Nationality_FR Nationality_UK male female Class_High Class_Mid Class_Low Fare_Low Fare_Med Fare_High
PassengerId                                          
1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN ... False True True False False False True True False False
2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 ... True False False True True False False False False True
3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN ... False True False True False False True True False False
4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 ... False True False True True False False False True False
5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN ... False True True False False False True True False False

5 rows × 24 columns

탐색적 데이터 분석

# 데이터 시각화 도구인 matplotlib를 불러와 이를 줄여 plt라고 사용합니다.
import matplotlib.pyplot as plt

# data 의 Age 컬럼만 뽑아, 승객들의 나이 분포를 히스토그램으로 보려고 합니다.
# plt.hist() 로 히스토그램을 그리고, 승객들의 나이 정보를 data['Age'] 로 지정해 넣어줍니다.
# y 축은 인원 수를 의미하고, x 축은 우리가 지정한 'Age' 입니다.  
plt.hist(data['Age'])

히스토그램을 겹쳐 그리는 방법

# 생존한 경우와 그렇지 않은 경우 두 가지를 한 눈에 들어오게 만들어보죠.
# .loc을 이용하여 survived 와 perished 두 가지 경우를 따로 분류해 저장합니다.
# 분포를 볼 기준이 되는 x 축을 x_axis 라는 변수에 지정해둡니다.
survived = data.loc[data['Survived'] == 1]
perished = data.loc[data['Survived'] == 0]
x_axis = 'Age'

# 겹쳐 그리는 것이므로 생존인 경우의 나이와 사망한 경우의 나이를 같이넣어줍니다.
# stacked=True 는 겹쳐 그리는 옵션을 켠 것입니다.  
plt.hist([survived[x_axis], perished[x_axis]], stacked=True)
plt.legend(['Survived', 'Perished'])

# 특정 조건을 걸어서, 해당 조건을 만족하는 경우
# 생존 / 사망자의 히스토그램 분포를 보겠습니다.

# data.loc[] 을 이용하여 UK 사람만 뽑아낸 이후 이를 UK 변수에 넣고
UK = data.loc[data['Nationality'] == 'UK']

# 히스토그램에 들어갔던 데이터인 변수 data 대신 UK 를 넣어줍니다. 
survived = UK.loc[UK['Survived'] == 1]
perished = UK.loc[UK['Survived'] == 0]
x_axis = 'Age'

# 겹쳐 그리는 것이므로 생존인 경우의 나이와 사망한 경우의 나이를 같이넣어줍니다.
# stacked=True 는 겹쳐 그리는 옵션을 켠 것입니다. 
plt.hist([survived[x_axis], perished[x_axis]], stacked=True)
plt.legend(['Survived', 'Perished'])

시각화의 함수화

# 그래프가 들어가는 데이터만 다르고 절차가 동일하기 때문에
# 반복을 줄이기 위해 그래프를 그려주는 함수를 만들어줍니다.

# data_in 과 x_axis 를 함수의 입력값으로 받습니다.
# 보고자 하는 데이터를 data_in 이름으로 함수 안에 넣을 겁니다.
# 히스토그램에서 x 축의 기준이 되는 컬럼이 x_axis 변수 입니다.

def plotter(data_in, x_axis):
    # 산 자와 죽은 자를 각각 S, D 변수에 분리해 넣습니다.
    S = data_in.loc[data_in['Survived'] == 1]
    D = data_in.loc[data_in['Survived'] == 0]

    # 겹쳐 그리는 것이므로 생존인 경우와 사망한 경우를 x_axis를 정해 같이넣어줍니다.
    # stacked=True 는 겹쳐 그리는 옵션을 켠 것입니다. 
    plt.hist([S[x_axis], D[x_axis]], stacked=True)
    plt.legend(['Survived', 'Perished'])
# 보고자 하는 경우를 data.loc[] 을 이용하여 미리 뽑아둡니다.

# FR과 UK는 국적이 정해졌을 때 생존자의 분포를,
# male과 female은 성별이 정해졌을 때 생존자의 분포를 보기 위함입니다.

FR = data.loc[data['Nationality'] == 'FR']
UK = data.loc[data['Nationality'] == 'UK']
male = data.loc[data['Sex'] == 'male']
female = data.loc[data['Sex'] == 'female']
# UK 국적을 대상으로 Fare 를 x축으로 하여 분포를 확인합니다.
plotter(UK, 'Fare')

# UK 국적을 대상으로 Age 를 x축으로 하여 분포를 확인합니다.
plotter(UK, 'Age')

# FR 국적을 대상으로 Age 를 x축으로 하여 분포를 확인합니다.

# UK 와 FR 의 바로 들어오는 차이점은 y 축의 크기가 다르다는 것입니다.
# -> FR 국적의 사람들이 훨씬 적음을 알 수 있습니다.
# 나이대별 생존인원 / 전체인원의 비율도 어느정도 차이가 있는 것 같아 보입니다.
plotter(FR, 'Age')

# male 성별을 대상으로 Age 를 x축으로 하여 분포를 확인합니다.
plotter(male, 'Age')

# female 성별을 대상으로 Age 를 x축으로 하여 분포를 확인합니다.

# 이번에도 y축의 크기가 다르기 때문에 female 이 인원수가 더 적음을 알 수 있습니다.
# 나이대별 생존인원 / 전체인원의 비율도 차이가 있는 것 같아 보입니다.
plotter(female, 'Age')

생존과 가장 연관있는 특징은 무엇일까? - 상관계수 도출하기

# 행을 사람으로, 컬럼을 해당 사람의 특징으로 하는 데이터프레임인 data 의
# 모든 컬럼들에 대해, .corr()를 이용하여 서로서로 컬럼들 끼리의 상관계수 행렬을 구해줍니다.
# 수치를 해석하는 방법은, 행/열의 이름을 보고 1, -1, 0 어디에 가까운지를 확인합니다.
# 같이 증가하는 경향이라면 1, 하나가 증가할 때 하나가 감소하면 -1, 상관이 없으면 0 에 가깝습니다.
# 흔히 양/음의 상관관계 라고 이야기할 때 이 수치를 기준으로 말합니다.
corr = data.corr()
corr
  Survived Pclass Age SibSp Parch Fare FamilySize Nationality_FR Nationality_UK male female Class_High Class_Mid Class_Low Fare_Low Fare_Med Fare_High
Survived 1.000000 -0.338481 -0.069809 -0.035322 0.081629 0.257307 0.016639 0.168240 -0.174718 -0.543351 0.543351 0.285904 0.093349 -0.322308 -0.285349 0.131712 0.236428
Pclass -0.338481 1.000000 -0.331339 0.083081 0.018443 -0.549500 0.065997 -0.243292 0.251139 0.131900 -0.131900 -0.885924 -0.188432 0.916673 0.563381 -0.215576 -0.530062
Age -0.069809 -0.331339 1.000000 -0.232625 -0.179191 0.091566 -0.248512 0.032024 -0.040804 0.084153 -0.084153 0.319916 0.006589 -0.281004 -0.060586 -0.019972 0.118398
SibSp -0.035322 0.083081 -0.232625 1.000000 0.414838 0.159651 0.890712 -0.059528 0.061970 -0.114631 0.114631 -0.054582 -0.055932 0.092548 -0.390253 0.266324 0.200727
Parch 0.081629 0.018443 -0.179191 0.414838 1.000000 0.216225 0.783111 -0.011069 0.013725 -0.245489 0.245489 -0.017633 -0.000734 0.015790 -0.397959 0.323111 0.131383
Fare 0.257307 -0.549500 0.091566 0.159651 0.216225 1.000000 0.217138 0.269335 -0.273614 -0.182333 0.182333 0.591711 -0.118557 -0.413333 -0.483538 -0.021316 0.748496
FamilySize 0.016639 0.065997 -0.248512 0.890712 0.783111 0.217138 1.000000 -0.046215 0.049211 -0.200988 0.200988 -0.046114 -0.038594 0.071142 -0.465538 0.343444 0.202827
Nationality_FR 0.168240 -0.243292 0.032024 -0.059528 -0.011069 0.269335 -0.046215 1.000000 -0.992724 -0.082853 0.082853 0.296423 -0.125416 -0.153329 -0.180016 0.007366 0.256888
Nationality_UK -0.174718 0.251139 -0.040804 0.061970 0.013725 -0.273614 0.049211 -0.992724 1.000000 0.090223 -0.090223 -0.305181 0.127763 0.158965 0.185077 -0.003087 -0.270492
male -0.543351 0.131900 0.084153 -0.114631 -0.245489 -0.182333 -0.200988 -0.082853 0.090223 1.000000 -1.000000 -0.098013 -0.064746 0.137143 0.222830 -0.089730 -0.203300
female 0.543351 -0.131900 -0.084153 0.114631 0.245489 0.182333 0.200988 0.082853 -0.090223 -1.000000 1.000000 0.098013 0.064746 -0.137143 -0.222830 0.089730 0.203300
Class_High 0.285904 -0.885924 0.319916 -0.054582 -0.017633 0.591711 -0.046114 0.296423 -0.305181 -0.098013 0.098013 1.000000 -0.288585 -0.626738 -0.550347 0.159468 0.590527
Class_Mid 0.093349 -0.188432 0.006589 -0.055932 -0.000734 -0.118557 -0.038594 -0.125416 0.127763 -0.064746 0.064746 -0.288585 1.000000 -0.565210 0.002322 0.107350 -0.156173
Class_Low -0.322308 0.916673 -0.281004 0.092548 0.015790 -0.413333 0.071142 -0.153329 0.158965 0.137143 -0.137143 -0.626738 -0.565210 1.000000 0.472292 -0.224766 -0.381698
Fare_Low -0.285349 0.563381 -0.060586 -0.390253 -0.397959 -0.483538 -0.465538 -0.180016 0.185077 0.222830 -0.222830 -0.550347 0.002322 0.472292 1.000000 -0.764298 -0.397894
Fare_Med 0.131712 -0.215576 -0.019972 0.266324 0.323111 -0.021316 0.343444 0.007366 -0.003087 -0.089730 0.089730 0.159468 0.107350 -0.224766 -0.764298 1.000000 -0.287509
Fare_High 0.236428 -0.530062 0.118398 0.200727 0.131383 0.748496 0.202827 0.256888 -0.270492 -0.203300 0.203300 0.590527 -0.156173 -0.381698 -0.397894 -0.287509 1.000000
# 생존과 직접적으로 연관이 있는 컬럼들만 알아보기 위해
# 상관계수 행렬을 'Survived' 를 키로 하는 값만 가져와 corr_s 변수에 저장합니다. 
corr_s = corr['Survived']

# .sort_values()를 이용해 값들을 보기 편하게 정렬해 표현합니다.
corr_s.sort_values()

# 양수이면 해당 값과 생존 값이 같이 움직인다는 것입니다.(생존 값은 0 또는 1)
# 음수이면 해당 값과 생존 값이 반대로 움직인다는 것입니다.
male             -0.543351
Pclass           -0.338481
Class_Low        -0.322308
Fare_Low         -0.285349
Nationality_UK   -0.174718
Age              -0.069809
SibSp            -0.035322
FamilySize        0.016639
Parch             0.081629
Class_Mid         0.093349
Fare_Med          0.131712
Nationality_FR    0.168240
Fare_High         0.236428
Fare              0.257307
Class_High        0.285904
female            0.543351
Survived          1.000000
Name: Survived, dtype: float64

히트맵 방식으로 시각화하기

# heatmap - 2차원의 정보를 열분포 형태의 그림으로 확인합니다.
# 데이터 시각화 도구인 seaborn 을 불러와 이를 줄여 sns라고 사용합니다.
import seaborn as sns

# sns.heatmap()으로 히트맵 시각화를 하고, 여기에 상관계수 행렬인 corr을 넣어줍니다.
sns.heatmap(corr)

# 시각화의 전체 사이즈는 7, 5로 설정합니다.
plt.gcf().set_size_inches(7, 5)
# 밝을수록 양의 상관관계, 어두울수록 음의 상관관계 입니다.

생존자를 예측하기

# Survived 를 제외한 컬럼들은, 전부 Survived를 설명하는 변수들 입니다.
# 맞추고자 하는 대상을 label 이라고 하고, 이를 설명하는 변수들을 feature 라고 합니다.

# all_features 변수에 전처리 단계에서 추가한 컬럼들과 Age 를 리스트로 넣고
all_features = ["Class_High", "Class_Mid", "Class_Low", 
                "Fare_High", "Fare_Med", "Fare_Low",
                "Nationality_UK", "Nationality_FR",
                "Age", "FamilySize", "male", "female"]

# label 변수에는 우리가 맞추고자 하는 'Survived' 를 넣습니다.
label = ['Survived']
# 슬라이싱을 적용하여 데이터를 특정 기점으로 잘라줍니다.
# 잘랐을 때, feature(특징) 모음의 경우 컬럼의 개수가 동일해야 합니다.

split = 400

# X_train -> 학습할 데이터의 특징들의 모음
# Y_train -> 학습할 데이터의 레이블
X_train = data[:split][all_features]
y_train = data[:split][label]

# X_test -> 예측할 데이터의 특징 모음
# Y_test -> 예측할 데이터의 레이블
X_test = data[split:][all_features]
y_test = data[split:][label]
# 머신러닝 도구인 sklearn 으로부터 DecisionTreeClassifier 를 불러옵니다.
# DecisionTreeClassifier는 의사결정나무를 이용하는 알고리즘 입니다.
from sklearn.tree import DecisionTreeClassifier

# model 은 어떤 알고리즘으로 학습할 것인지를 나타냅니다.
# max_depth=3 는 의사결정 단계의 깊이를 의미하는데, 직접 바꿔보면서 알아보겠습니다.
model = DecisionTreeClassifier(max_depth=5)
model
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=None, splitter='best')
# model.fit -> 선택한 알고리즘에 학습할 데이터를 넣어서 학습시킨다는 의미입니다.
# model.score -> 예측한 결과인 predict 와 실제 결과를 비교하여 점수를 환산합니다. 
# 점수는 mean-average 방식, 맞추면 1 틀리면 0 으로 두고 모든 결과에 대해 평균

model = model.fit(X_train, y_train)
model.score(X_train, y_train)
0.855
# model.predict -> 학습한 알고리즘에 예측할 데이터를 넣어서 결과를 예측하는 것입니다.
# 정답인 Survived 와 비교하기 위해, 예측한 결과를 'Predict' 키에 값으로 넣어줍니다.

y_test['Predict'] = model.predict(X_test)
y_test.head()
  Survived Predict
PassengerId    
401 1 0
402 0 0
403 0 1
404 0 0
405 0 1
# 예측결과와 정답이 같으면 True, 다르면 False로 하는 값을
# 'validity' 를 키로 하여 넣어줍니다.
y_test['validity'] = (y_test['Predict'] == y_test['Survived'])

# True 는 1 로 취급하므로, .sum() 으로 True들을 더하면 정답의 개수가 됩니다.
correct = y_test['validity'].sum()

# len() 은 몇 개 들었는지 확인하는 것이었는데요
# 여기에서는 True/False 가리지않고 몇 개 들어있는지 확인하기 위해 사용합니다.
total = len(y_test['validity'])

# 정답의 개수를 전체 인원수로 나누어서, mean-average 로 정확도를 구해줍니다.
pct = correct / total
pct
0.8085539714867617

Feature Importance 추출

# model.fit 으로 학습을 하게 되면, 학습 과정에서 판별을 위해 사용한 
# 특징들의 중요도를 의미하는 feature importance를 추출할 수 있습니다.

# model.feature_importances_ 로 추출하여 rank 변수에 넣어줍니다.
rank = model.feature_importances_
print(rank)

# 묶음 형태로 나오게 되는데, 이 순서는 학습시 넣어준 feature 순서대로 입니다.
print(all_features)

# 이를 보기 좋게 정렬하는 방법 두 가지를 소개합니다.
[0.0136221  0.         0.07452883 0.0051696  0.00924445 0.
 0.         0.         0.15119727 0.07985843 0.         0.66637932]
['Class_High', 'Class_Mid', 'Class_Low', 'Fare_High', 'Fare_Med', 'Fare_Low', 'Nationality_UK', 'Nationality_FR', 'Age', 'FamilySize', 'male', 'female']
# pd.Dataframe() 으로, 새로운 데이터프레임을 만들 수 있습니다.
# 데이터는 feature 중요도 점수, 인덱스(순서) 는 feature 정보
# 그리고 columns에 넣어준 데이터의 컬럼 이름을 'score' 로 지정합니다. 
scoring = pd.DataFrame(
    data = rank,
    index = all_features, 
    columns = ['score'])

# 'score' 를 기준으로 정렬하고, 내림차순으로 정렬한다는 의미입니다.
scoring.sort_values(by='score', ascending=False)
  score
female 0.666379
Age 0.151197
FamilySize 0.079858
Class_Low 0.074529
Class_High 0.013622
Fare_Med 0.009244
Fare_High 0.005170
Class_Mid 0.000000
Fare_Low 0.000000
Nationality_UK 0.000000
Nationality_FR 0.000000
male 0.000000
# 두 번째 방법은 순수 파이썬 문법을 사용하는 것입니다.

# sorted() 는 내용물들을 정렬해주는 기능을 수행합니다.
# zip() 은 묶음을 두 개 넣어주면 순서가 같은 것끼리 묶어줍니다.

# zip() 에 점수와 feature를 넣어주면, 같은 순서끼리 묶어주고
# 이를 sorted() 에서 점수의 순서대로 내림차순 정렬해줍니다.
sorted(zip(rank, all_features), reverse=True)
[(0.6663793228128022, 'female'),
 (0.15119726660076893, 'Age'),
 (0.07985842938788616, 'FamilySize'),
 (0.07452882775445713, 'Class_Low'),
 (0.013622102842177863, 'Class_High'),
 (0.009244454394472421, 'Fare_Med'),
 (0.005169596207435185, 'Fare_High'),
 (0.0, 'male'),
 (0.0, 'Nationality_UK'),
 (0.0, 'Nationality_FR'),
 (0.0, 'Fare_Low'),
 (0.0, 'Class_Mid')]
# 머신러닝 도구인 sklearn 으로부터 export_graphviz 를 불러옵니다.
# export_graphviz 는 의사결정나무를 시각화하는 기능이 있습니다.
# 코드를 실행하면 tree.txt 에 의사결정나무 정보가 저장됩니다.
from sklearn.tree import export_graphviz

dotfile = open("tree.txt", 'w')

export_graphviz(model,
                feature_names=all_features,
                class_names='Survived',
                out_file=dotfile)

dotfile.close()
'''
만든 tree 파일을 메모장으로 열어
안의 내용을 아래 링크에 붙여넣기

http://webgraphviz.com/

'''
'\n만든 tree 파일을 메모장으로 열어\n안의 내용을 아래 링크에 붙여넣기\n\nhttp://webgraphviz.com/\n\n'
'''
그래프 해석법

max_depth 는 의사결정의 단계(깊이) 를 의미합니다.
10 으로 바꿔서 확인하면, 더 깊이있는 그림을 볼 수 있습니다.

의사결정나무는 한번 분기 때마다 두 개의 영역으로 구분합니다.
구분하는 기준은 지니 계수가 감소하는 방향 입니다.

지니 계수는?
균일하게 들어있을수록 1, 한 쪽으로 치우쳐질수록 0 입니다.
어떤 기준을 잡았을 때 Survived 가 한 값만 들어있다면, 값이 0 입니다.
'''
'\n그래프 해석법\n\nmax_depth 는 의사결정의 단계(깊이) 를 의미합니다.\n10 으로 바꿔서 확인하면, 더 깊이있는 그림을 볼 수 있습니다.\n\n의사결정나무는 한번 분기 때마다 두 개의 영역으로 구분합니다.\n구분하는 기준은 지니 계수가 감소하는 방향 입니다.\n\n지니 계수는?\n균일하게 들어있을수록 1, 한 쪽으로 치우쳐질수록 0 입니다.\n어떤 기준을 잡았을 때 Survived 가 한 값만 들어있다면, 값이 0 입니다.\n'
# max_depth 가 3 인 경우

# max_depth 가 5 인 경우