[작성자]
개요 및 분류 알고리즘 소개 : 김채원
상관관계 분석 : 한상휘
생존 여부 예측 프로그램 ver 1 : 김예진
생존 여부 예측 프로그램 ver 2 : 장지훈
생존 여부 예측 프로그램 ver 3 : 강대영
▶ Kaggle Competition : Titanic
타이타닉 데이터의 구성을 이해하고, 기본적인 데이터 전처리를 위해 [Subinium Tutorial - Titanic Begginer ver.] 을 참고해 먼저 전체적인 프로젝트에 대해 이해하는 과정을 거쳤습니다.
타이타닉 프로젝트는 '타이타닉 탑승자의 생존 여부를 예측하는 프로젝트' 입니다.
아래는 타이타닉 데이터를 전처리하고, 이진 분류 알고리즘을 구현해보는 과정입니다.
* Subinium Tutorial을 참고하였습니다.
# 전처리 및 분석에 사용될 라이브러리를 불러오기
# import Library
## 데이터 분석 관련
import pandas as pd
from pandas import Series, DataFrame
import numpy as np
## 데이터 시각화 관련
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid') # matplotlib의 스타일에 관련한 함
## 그래프 출력에 필요한 IPython 명령어
%matplotlib inline
## 머신러닝 모델
## 분류 알고리즘 중에서 선형회귀, 서포트벡터머신, 랜덤포레스트, K-최근접이웃 알고리즘을 사용
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
# 데이터 불러오기
Kaggle에서 제공하는 타이타닉 데이터를 불러옵니다.
타이타닉 데이터는 학습 데이터 (train) 와 테스트 데이터 (test) 두 가지로 나뉘어 있습니다.
train = pd.read_csv("../input/train.csv")
test = pd.read_csv("../input/test.csv")
# 데이터 미리보기
train.head()
# 데이터 구성 파악하기
학습 데이터(train)는 위와 같이 구성되어 있습니다.
데이터의 정보를 확인하기 위해 info() 메서드를 활용합니다.
train_df.info()
print('-'*20)
test_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
--------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Name 418 non-null object
Sex 418 non-null object
Age 332 non-null float64
SibSp 418 non-null int64
Parch 418 non-null int64
Ticket 418 non-null object
Fare 417 non-null float64
Cabin 91 non-null object
Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
위와 같은 결과를 통해,
훈련 데이터는 891개의 데이터와 12개의 특성을, 테스트 데이터는 418개의 데이터와 11개의 특성을 가지고 있다는 것을 알 수 있습니다.
# 전처리 과정
train = train.drop(['PassengerId', 'Name', 'Ticket'], axis=1)
test = test.drop(['Name','Ticket'], axis=1)
데이터를 살펴보았을 때, 이름이나 티켓에서 의미있는 정보를 도출해내기 어렵다고 판단하여 PassengerID와 이름, 티켓 데이터를 삭제합니다. 그러나, 결과물에서는 'PassengerId', 'Survived' 요소가 필요하므로 훈련데이터에서만 삭제합니다.
# Pclass data
train['Pclass'].value_counts()
3 491
1 216
2 184
Name: Pclass, dtype: int64
value_counts() 메서드를 통해 Pclass 데이터의 각 value를 확인합니다.
이때 Pclass는 탑승 등급으로, 연속적인 정보가 아니며 각 차이가 균등하지 않습니다. 따라서 범주형 데이터로 인식하여 인코딩 합니다.
인코딩 코드는 다음과 같습니다.
pclass_train_dummies = pd.get_dummies(train['Pclass'])
pclass_test_dummies = pd.get_dummies(test['Pclass'])
train.drop(['Pclass'], axis=1, inplace=True)
test.drop(['Pclass'], axis=1, inplace=True)
train = train.join(pclass_train_dummies)
test = test.join(pclass_test_dummies)
위와 같이 Pclass의 원본을 없애고, 각각 1,2,3등급으로 범주화하였습니다.
# Sex data
같은 방법으로 성별 데이터를 one-hot-encoding 해줍니다.
sex_train_dummies = pd.get_dummies(train['Sex'])
sex_test_dummies = pd.get_dummies(test['Sex'])
sex_train_dummies.columns = ['Female', 'Male']
sex_test_dummies.columns = ['Female', 'Male']
train.drop(['Sex'], axis=1, inplace=True)
test.drop(['Sex'], axis=1, inplace=True)
train = train_df.join(sex_train_dummies)
test = test_df.join(sex_test_dummies)
# Age data
나이는 연속적인 값이기 때문에, 별도의 전처리는 하지 않으나 Nan 값을 처리해주어야 합니다.
이때 우리 조는 NaN 값을 평균 값으로 채워줬습니다. 코드는 다음과 같습니다.
train["Age"].fillna(train_df["Age"].mean() , inplace=True)
test["Age"].fillna(train_df["Age"].mean() , inplace=True)
# Fare data
Fare는 탑승료로, 마찬가지로 NaN 값만 처리해줍니다.
test["Fare"].fillna(0, inplace=True)
# Cabin data
Cabin 데이터의 경우 NaN값이 대부분이기 때문에, 데이터를 삭제해줍니다.
train_df = train.drop(['Cabin'], axis=1)
test_df = test.drop(['Cabin'], axis=1)
# Embarked data
우선 value_counts() 메서드로 Embarked 데이터를 살펴봅니다.
train['Embarked'].value_counts()
S 644
C 168
Q 77
Name: Embarked, dtype: int64
test['Embarked'].value_counts()
S 270
C 102
Q 46
Name: Embarked, dtype: int64
일부 데이터가 비어있는 것을 알 수 있는데, 대다수의 데이터가 S로 이루어져있기 때문에 비어있는 데이터를 S로 채워줍니다. 이후에는 다른 데이터들과 같은 방식으로 one-hot-encoding을 실행해줍니다.
train["Embarked"].fillna('S', inplace=True)
test["Embarked"].fillna('S', inplace=True)
embarked_train_dummies = pd.get_dummies(train['Embarked'])
embarked_test_dummies = pd.get_dummies(test['Embarked'])
embarked_train_dummies.columns = ['S', 'C', 'Q']
embarked_test_dummies.columns = ['S', 'C', 'Q']
train.drop(['Embarked'], axis=1, inplace=True)
test.drop(['Embarked'], axis=1, inplace=True)
train = train.join(embarked_train_dummies)
test = test.join(embarked_test_dummies)
# 데이터 나누기
X_train = train.drop("Survived",axis=1)
Y_train = train["Survived"]
X_test = test.drop("PassengerId",axis=1).copy()
학습 전 데이터를 나누는 과정이 필요합니다. 성별, 나이 등의 정보를 담은 데이터와 그에 따른 생존 여부를 파악하기 위해, 생존 여부만 따로 Y_train에 할당, 그 이외의 정보들은 X_train에 할당해줍니다.
# 머신러닝 알고리즘 적용
로지스틱 회귀, SVC, 랜덤 포레스트, K-최근접 이웃 알고리즘을 각각 적용해봅니다.
score를 통해 해당 알고리즘이 얼마나 높은 정확도를 가지는지 파악할 수 있습니다.
# Logistic Regression
# Logistic Regression
logreg = LogisticRegression()
logreg.fit(X_train, Y_train)
Y_pred = logreg.predict(X_test)
logreg.score(X_train, Y_train)
0.8058361391694725
# Support Vector Machine (SVC)
# Support Vector Machines
svc = SVC()
svc.fit(X_train, Y_train)
Y_pred = svc.predict(X_test)
svc.score(X_train, Y_train)
0.8888888888888888
# Random Forests
# Random Forests
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
0.9820426487093153
# KNeighborsClassifier
#KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 3)
knn.fit(X_train, Y_train)
Y_pred = knn.predict(X_test)
knn.score(X_train, Y_train)
0.835016835016835
위와 같이 총 4개의 머신러닝 알고리즘을 적용해 예측 정확도를 파악해보았습니다.
Random Forests 모델이 가장 예측 정확도(score)가 높음을 확인할 수 있었습니다.
# 프로젝트 방향성 및 주제 선정
[Subinium Tutorial - Beginner ver.]을 끝낸 후, 우리는 이 데이터를 어떻게 활용해 어떤 프로젝트를 진행할지 주제와 방향성에 대해 논하는 시간을 가졌습니다.
[주제] 이름, 나이, 성별 등 각종 데이터를 입력하면, 그 사람의 생존 여부를 예측해 결과값을 출력하는 프로그램을 만든다.
[방향성]
가. 이름 및 다른 정보들을 입력했을 때, 학습 데이터를 기반으로 그 사람의 생존 여부를 예측하는 코드 작성
나. 각 변수 사이의 상관관계를 분석하여, 상관관계가 있는 데이터를 기준으로 학습 및 예측
다. Subinium Tutorial에서 활용하지 않았던 데이터들을 활용 (SibSp & Parch)
라. 다양한 머신러닝 알고리즘 중, 가장 예측 정확도가 높았던 'RandomForests' 모델을 활용하며, 이진 분류의 원리 파악
# Binary Classification 이진 분류
이진 분류(Binary Classification)란 규칙에 따라 입력된 값을 두 그룹으로 분류하는 작업을 의미합니다.
주로 어떤 대상에 대한 규칙이 참(True)인지 거짓(False)인지를 분류하는데 쓰입니다. 랜덤 포레스트 모델도 이러한 이진 분류에 포함됩니다.
# Decision Tree 의사 결정 나무
의사 결정 트리는, 특정 Feature 에 대한 질문을 기반으로 데이터를 분리하는 방법입니다.
어떤 대상에 대한 정보가 주어졌을 때, 위와 같이 '날 수 있나요?', '지느러미가 있나요?' 와 같은 질문들을 통해 해당 대상을 논리적으로 분리할 수 있습니다.
# RandomForest Model
RandomForest Model 또한 분류 모델입니다. 숲이 나무로 이루어져 있듯이, RandomForest Model은 수많은 의사결정트리(Decision Tree)가 모여서 생성됩니다. 위의 예시에서는, 대상을 세 가지 요소와 함께 한 가지 의사 결정 트리로 결정했습니다. 그러나, 어떤 타겟을 예측하려면 세 가지 요소보다 더 많은 요소를 고려하는 것이 바람직합니다.
예를 들어 건강 위험도를 예측하는 상황이라고 가정합시다. 성별, 키, 몸무게만 보고 위험도를 측정하는 것보다 성별, 키와 몸무게, 운동량, 기초 대사량, 근육량 등 다양한 요소들을 통해 위험도를 측정하는 것이 보다 정확성이 높을 것입니다.
그러나, 수많은 요소와 하나의 의사 결정 트리를 기반으로 타겟을 예측한다면, 트리의 가지가 많아져 오버피팅이 일어날 것입니다.
따라서 위의 그림과 같이 Random Forest에서는 전체 Feature 중 랜덤으로 일부 Feature만 선택해 하나의 결정 트리를 만들고, 같은 방식으로 여러 개의 의사 결정 트리를 만드는 방식으로 구성됩니다.
각각의 의사 결정 트리는 하나의 예측값을 내놓으며, 이 값들중 최다값 혹은 평균값을 최종 예측값으로 정합니다. 즉, 더 많은 요소(Feature)를 고려하기 위해 하나의 거대한 의사 결정 트리를 만드는 것이 아니라, 여러 개의 작은 결정 트리를 만들어 그 값을 합치는 것입니다. 이렇게 여러가지 결과를 합치는 방식을 앙상블(Ensemble)이라고 합니다.
# Random Forest Model 의 핵심, Bagging
Random Forest Model의 핵심은, 각각의 의사결정트리가 서로 독립적이어야 한다는 것입니다. 개별 의사 결정 트리가 서로 상관화되어 있다면, 의사 결정 트리가 아무리 많아도 성능 개선을 기대할 수 없기 때문입니다. 따라서 단순히 숲 이 아니라 무작위 숲 이 되기 위해서는 트리마다 생김새가 다르도록 랜덤성을 가진 나무들로 구성되어야 합니다.
Bagging : bootstrapping + aggregating
bootstrapping : 전체 집합에서 무작위 복원추출을 통해 여러 부분집합을 만드는 행위
Aggregating : '집계한다' 는 광범위한 용어로 평균이나 최빈값 등을 도출하는 행위
이러한 랜덤성을 위해 숲을 이룰 각 나무를 학습하는데 쓰일 데이터셋으로 학습데이터 전체를 사용하지 않고, 그중 일부만 활용합니다. 이를 Bootstrapping이라고 하는데, 전체 데이터셋에서 일부를 복원추출하여 개별 데이터셋을 구성하는 방식입니다. 예를 들어, 100개의 학습 데이터가 있다고할때 그 중 20개의 데이터만 뽑아 하나의 나무를 구성하고 다시 20개의 데이터를 100개에 포함시킨 뒤 또 랜덤하게 20개를 뽑아 다음 나무를 생성하며 이때 각 나무는 서로 다른 모양의 가지와 높이를 구성하게 됩니다.
이렇게 여러 개의 의사 결정 트리를 만든 후, 그 결과값을 집계하여(Aggregating) 최종 결과값을 산출하게 됩니다.
# Random Forest Model 의 중요 매개 변수
- 숲의 크기 : 숲을 구성할 나무의 수를 뜻하는 매개변수로 나무의 수가 증가할수록 일반화 성능이 우수하지만 , 훈련과 검증의 시간은 오래걸린다.
- 각 트리의 높이 : 숲을 구성하는 각 나무들의 최대 깊이를 설정하는 매개변수로 과소적합&과대적합과 밀접한 관계를 가지므로 적절한 높이를 통해 학습하는 것이 중요하다.
- 임의성의 정도와 종류 : 전체 데이터에서 부분 데이터를 추출하는 과정에서 어떤 분포 함수를 적용하는지에 따라 랜덤성에 차이가 있다.
- 각 트리의 특징 벡터 선정 : 실제 각 트리는 부분 데이터 중에서도 모든 특성을 다 학습에 활용하지 않고 각 노드에서 일부 특성만을 고려하여 분기하는데 이때 활용되는 훈련 목적 함수의 종류에 따라 일반화 성능의 차이가 있다.
# 상관관계 분석 - Feature 확인
# survival은 생존여부를 나타냅니다
# pclass은 티켓이 몇등석인지를 나타냅니다 2
# sex는 성별을 나타냅니다1
# Age는 나이를 나타냅니다5
# sibsp은 동행자 중 형제, 배우자의 수를 합한 값을 나타냅니다
# parch는 동행자 중 부모님과 아이들의 수를 나타냅니다3
# ticket은 티켓번호를 나타냅니다
# fare는 티켓 가격을 나타냅니다6
# cabin은 선실번호를 나타냅니다
# embarked는 탑승한 항구를 나타냅니다4
먼저 열에 있는 feature들을 정렬해보았습니다.
그 후 생존과의 상관관계를 도출할 때 의미가 있는 feature들을 선별했습니다.
다음으로 Survival과의 상관관계를 소개할 순서에 맞추어 번호를 할당했습니다.
SibSp, Parch는 같은 가족구성원으로서 한 개의 'Family' feature로 취급하겠습니다.
# 상관관계 분석 - import Library
# 1. 라이브러리 구축 단계
from google.colab import drive
drive.mount('/content/drive')
먼저 기초 라이브러리 구성 단계입니다.
먼저 구글 드라이브에 마운트하여 데이터에 approaching할 준비를 합니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
csv 파일을 다루고 이를 시각화하기 위한 라이브러리들을 불러옵니다.
# 상관관계 분석 - csv 파일 불러오기
# 2. 데이터 가공 단계.
train = pd.read_csv("/content/drive/MyDrive/CODE/train.csv")
test = pd.read_csv("/content/drive/MyDrive/CODE/test.csv")
gender_submission = pd.read_csv("/content/drive/MyDrive/CODE/gender_submission.csv")
pd.read_csv는 csv파일을 pandas dataframe형식으로 바꾸어 데이터를 다루기 쉽게 합니다.
# 상관관계 분석 - Family data 구축
train['Family'] = train['Parch'] + train['SibSp']
'Family': 새로 만든 feature로, 가족 구성원 수입니다. 형제, 배우자와 부모님과 아이들은 모두 가족입니다.
더해서 Family 행에 저장합시다. 혼자일 경우는 0으로 처리했습니다.
가족 구성원 수에 본인을 포함시켜 1을 더하기도 하지만 저 한상휘는 그렇게 하지 않았습니다.
train = train.drop(['Parch','SibSp',], axis= 1)
train = train.drop(['Cabin','Ticket','Name'], axis= 1)
Parch, SibSp을 대신할 Family feature를 생성했으니 재료는 모두 drop합니다.
그리고 선실번호, 티켓번호, 이름데이터도 생존여부와는 상관이 없다 간주하여 drop했습니다.
train.columns
<<실행결과>>
Index(['PassengerId', 'Survived', 'Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'Family'], dtype='object')
columns 출력결과 성공적으로 데이터 처리가 완료되었습니다.
# 상관관계 분석 - 결측값 처리
train.isnull().sum()
<<실행결과>>
PassengerId 0 Survived 0 Pclass 0 Sex 0 Age 177 Fare 0 Embarked 2 Family 0 dtype: int64
다음으로 각각 feature들 중 결측값(NULL)이 얼마나 있는지 확인해봅시다.
isnull()함수를 썼을 때 결측값이 있다면 1로 표시됩니다.
이를 전부 더하면 위와같이 결측값 개수가 나타납니다.
결측값이 있는 feature는 각각 .fillna 혹은 .dropna 함수를 통해 결측값을 다룰 수 있습니다.
#3 상관관계(시각화), 상관계수 탐구
# 상관관계 분석
그래프를 통해 상관관계를 먼저 유추해보고, 구체적인 상관계수를 계산해 확인해봅시다.
상관계수를 계산하는 과정에서는, 분석하는 feature들의 값이 str이 아닌 int 이진법으로 분류되어 있어야 수월합니다.
이진법으로 분류하는 과정은 곧 설명하겠습니다.
첫번째로 성별과 생존여부의 상관관계를 알아보기 위해 그래프를 그려봅시다. 코드는 다음과 같습니다.
# 3-1-1. 성별과 생존여부의 상관관계 알아보기
sns.barplot(x="Sex", y="Survived",data=train, width = 0.3, palette="muted")
<<실행결과>>
그래프를 통해 성별과 생존율의 상관관계를 유추해 볼 수 있습니다.
여자의 생존율이 남자보다 높다는 점을 직관적으로 알 수 있습니다.
# 3-1-2. 성별과 생존여부의 상관계수
상관계수를 계산하기 위해서는 성별 데이터값들을 이진법으로 분류해야 수월합니다. (0은 거짓, 1은 참을 뜻합니다.)
이진법으로 분류하기 위해서 pd.get_dummies()함수를 사용하겠습니다.
이 함수는 범주형 변수를 원-핫 인코딩(one-hot encoding)하여 숫자형 변수로 변환하는 데 사용됩니다.
이렇게 하면 범주형 데이터를 학습 알고리즘이 이해할 수 있는 형태로 변환할 수 있다.
예시를 들어보겠습니다. 아래 그림을 참조해주세요.
성별(Sex) 데이터의 범주(female, male)가 feature로 변환되어 이진법 변환이 성공적으로 이루어졌음을 확인했습니다.
이진법 변환이 완료되면 이제 상관계수를 구할 수 있습니다.
이 과정을 위해 'rep_corr()'라는 함수 하나를 정의하도록 하겠습니다.
범주형 데이터를 다루는 feature를 매개변수로 받아서, 이진법 변환을 거쳐 생존여부와의 상관계수를 구하는 함수입니다.
즉, 범주형 데이터의 상관계수를 단번에 나타내주는 함수입니다.
def rep_corr(col):
dummies = pd.get_dummies(train[col])
dummies_columns = dummies.columns
for i in dummies_columns:
corr = train['Survived'].corr(dummies[i])
print("Correlation of ", i, " : ", corr)
rep_corr('Sex')
결과는 다음과 같습니다. 성별과 생존의 상관관계가 꽤 있음을 알 수 있습니다.
아까 그래프만으로 직관적인 판단이 된 점과 연결됩니다.
rep_corr('Sex')
<<실행결과>>
Correlation of female : 0.5433513806577555
Correlation of male : -0.5433513806577555
# 3-2-1.좌석등급(Pclass)과 생존여부의 상관관계
sns.barplot(x="Pclass", y="Survived",data=train, width = 0.3, palette="muted")
좌석등급과 생존율의 상관관계를 유추해본다면
좋은등급의 좌석에 앉은 사람의 생존율이 더 높다는 사실을 알 수 있습니다.
rep_corr()함수를 통해 상관계수를 계산하면 다음과 같습니다.
그래프에서 유추한 바와 같이 1등석이 생존여부와 가장 높은 양의 상관관계를 가집니다.
즉, 좋은 좌석에 앉은 사람일수록 생존 가능성이 높습니다.
rep_corr('Pclass')
<<실행결과>>
Correlation of 1 : 0.28590376778374327
Correlation of 2 : 0.0933485724119293
Correlation of 3 : -0.3223083573729708
# 3-3.가족수(Family)와 생존여부의 상관관계, 상관계수
sns.barplot(x="Family", y="Survived",data=train, width = 0.3, palette="muted")
<<실행결과>>
가족 수는 적당한 것이 좋아보인다(?)라는 상관관계를 유추해볼 수 있습니다.
실제로 상관계수를 계산해보면 다음과 같습니다.
rep_corr('Family')
<<실행결과>>
Correlation of 0 : -0.20336708569989206
Correlation of 1 : 0.16315683404651507
Correlation of 2 : 0.1438690087710015
Correlation of 3 : 0.12834685051354702
Correlation of 4 : -0.04946616577630409
Correlation of 5 : -0.08096751692553032
Correlation of 6 : -0.01213415320482328
Correlation of 7 : -0.06498765565557572
Correlation of 10 : -0.07023438247444529
# 3-4.탑승항구(Embarked)와 생존여부의 상관관계, 상관계수
sns.barplot(x="Embarked", y="Survived",data=train, width = 0.3, palette="muted",ci=95)
<<실행결과>>
C에서 탑승한 승객의 생존률이 가장 높고, 그 다음은 Q, S 순입니다.
신뢰도 구간을 고려하면 조금 애매하지만, S와 C의 차이는 뚜렷합니다.
상관계수를 구하면 말한 바와 같이 C와 S의 차이가 눈에 띕니다.
rep_corr('Embarked')
<<실행결과>>
Correlation of C : 0.16824043121823268
Correlation of Q : 0.0036503826839719387
Correlation of S : -0.15566027340439306
# 3-5-1.연령대와 생존여부의 상관관계
결측값(NA) dropna : 'fillna' 함수를 통해 결측값을 최빈값, 평균값, 중앙값 등으로 채워넣을 수 있으나,
train.dropna()를 통해 아예 결측값이 있는 행을 지워버릴 수도 있습니다.
매개변수 subset으로 결측값을 확인할 열을 선택적으로 지정할 수 있습니다.
또는 결측값에 -0.5를 넣어 추후 'Unknown'값으로 분류하기 쉽도록 할 수 있습니다. 이렇게 한번 해보겠습니다.
train["Age"]= train["Age"].fillna(-0.5)
sns.barplot(x="Age", y="Survived",data=train, width = 0.3, palette="muted")
<<실행결과>>
그런데 결과를 확인해보면 누가봐도 이상한 그래프입니다. 사람마다 나이가 다양하기 때문에 그렇습니다.
따라서 그래프만으로는 상관관계를 알기 어렵습니다.
또한 이진법으로 분류되지 않아서 상관계수를 계산하고 직관적인 인사이트를 도출하기 어렵습니다.
그래서 나이를 구간별로 나누어 이진법으로 연령대 상태를 나타내 보았습니다.
bins = [-1,0,5,12,19,29,39,49,59,np.inf]
labels=["Unknown","baby","child","Teenager","20s","30s","40s","50s","Senior"]
bins에 나이별로 구간을 정해 labels로 이름을 붙여줄 것입니다.
아까 train["Age"]= train["Age"].fillna(-0.5)에서 '-0.5'로 결측값을 채운 이유가 바로 이겁니다.
결측값에 Unknown을 할당하기 위해서입니다.
train["AgeGroup"]=pd.cut(train["Age"],bins,labels=labels)
pd.cut함수는 연속형 변수를 여러 구간으로 나누어 범주형 변수로 변환하는 데 사용합니다.
'AgeGroup'이라는 feature을 생성해 연속형 변수인 나이를 범주화시켰습니다.
이제 이것을 그래프로 나타내겠습니다.
sns.barplot(x="AgeGroup",y="Survived",data=train)
<<실행결과>>
위에서 다른 연령대는 구분 짓기 애매하지만, baby의 생존률이 높은 점을 알 수 있습니다.
# 3-5-2.연령대와 생존여부의 상관계수
연령대와 생존 여부의 상관계수를 알아보기 위해서도 마찬가지로 데이터값들을 이진법으로 분류해야합니다.
rep_corr() 함수를 사용하겠습니다.
rep_corr('AgeGroup')
<<실행결과>>
Correlation of Unknown : -0.0921965232421721
Correlation of baby : 0.15030438360027237
Correlation of child : -0.008328491474015983
Correlation of Teenager : 0.018958268473926128
Correlation of 20s : -0.039841728489648245
Correlation of 30s : 0.0526249472994931
Correlation of 40s : -0.0012438874307511348
Correlation of 50s : 0.016107697926894943
Correlation of Senior : -0.04085738551378258
역시 아기의 상관계수가 제일 높습니다.
# 3-6-1.티켓가격과 생존여부의 상관관계
sns.barplot(x="Fare", y="Survived",data=train, width = 0.3, palette="muted")
티켓가격도 나이와 마찬가지로 연속형 변수이기 때문에 그래프가 엉망입니다.
따라서 그래프만 보고는 상관관계를 유추할 수 없습니다. 상관계수를 직접 구해봐야합니다.
<<실행결과>>
# 3-6-2.티켓가격과 생존여부의 상관계수
train['Survived'].corr(train['Fare'])
<<실행결과>>
0.2573065223849625
(3-5)의 연령대로 구간을 나눈 것처럼 티켓 가격도 구간을 나누어 분석하면 좋겠지만
그 기준을 정하기 애매하기 때문에 상관계수를 바로 계산했습니다. 그래도 유의미한 결론을 내릴 수 있습니다.
티켓 가격이 비쌀 수록, 생존률이 높다. 즉, 양의 상관관계를 가집니다.
이는 앞에서 설명한 좋은등급의 좌석일 수록 생존률이 높다는 사실과 연결됩니다.
# 생존 예측 프로그램 ver1
예측 결과를 활용한 생존 예측 프로그램 ver1을 만들기.
- 프로그램에 쓰일 데이터 프레임이 생성하기
프로그램에 쓰일 데이터 프레임을 먼저 만들어 줍니다.
기존의 x_test 데이터 프레임을 copy 함수를 이용하여 df에 저장합니다.
Survived 라는 열을 지정하여 예측값인 y_pred를 데이터 프레임에 추가합니다.
df 에서 Name은 object로 저장되어있기 때문에 astype함수를 사용하여 string으로 변환합니다.
탑승자의 이름을 확인할 수 있도록 name_list라는 새로운 변수에 name 열에 있는 값을 리스트로 변환하여 저장합니다.
<출력결과>
Age float64
Fare float64
1 uint8
2 uint8
3 uint8
Female uint8
Male uint8
P1 uint8
P2 uint8
P3 uint8
P4 uint8
P5 uint8
P7 uint8
P11 uint8
P6 uint8
P8 uint8
S uint8
C uint8
Q uint8
Survived int64
Name string
dtype: object
34.5 | 7.8292 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | Kelly, Mr. James |
47.0 | 7.0000 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | Wilkes, Mrs. James (Ellen Needs) |
62.0 | 9.6875 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | Myles, Mr. Thomas Francis |
27.0 | 8.6625 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | Wirz, Mr. Albert |
22.0 | 12.2875 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | Hirvonen, Mrs. Alexander (Helga E Lindqvist) |
5 rows × 21 columns
- 타입 확인 하기
print(type(df["Name"]))
print(type(name_list))
print(type(df["Survived"][0]))
타입이 변환 된 것을 확인할 수 있습니다.
<출력결과>
<class 'pandas.core.series.Series'>
<class 'list'>
<class 'numpy.int64'>
while True :
name = input("탑승자의 이름을 검색하시오. \n")
if name == "q" :
print("생촌 예측 프로그램 ver1을 종료합니다.")
break
if name in name_list :
index = df.loc[df['Name'] == name ].index.item()
survived = df.loc[index,"Survived"]
break
else :
print("탑승자 명단에 없습니다.")
print("탑승자의 이름을 다시 입력하십시오.")
print("생존 예측 프로그램 ver1을 종료하려면 q를 입력하십시오.")
if survived == 0 :
print("해당 탑승자는 사망으로 추정됩니다.")
elif survived == 1:
print("해당 탑승자는 생존으로 추정됩니다. ")
<출력결과>
탑승자의 이름을 검색하시오.
Kelly, Mr. Jame
탑승자 명단에 없습니다.
탑승자의 이름을 다시 입력하십시오.
생존 예측 프로그램 ver1을 종료하려면 q를 입력하십시오.
탑승자의 이름을 검색하시오.
Kelly, Mr. James
해당 탑승자는 사망으로 추정됩니다.
# 생존 예측 프로그램 ver2
예측 결과를 활용한 생촌 예측 프로그램 ver2 만들기
생존 예측 프로그램 ver2는 이름을 제외한 승객의 나이, 성별, 객실 등급 등의 정보를 입력하여 승객의 생존 여부를 예측하는 프로그램입니다.
- 딕셔너리 구축하기
# Random Forests
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
위 코드에서 보이듯 random_forest.fit()함수에 전처리 과정을 마친 X_train데이터와 Y_train데이터를 학습시켰기 때문에 생존을 예측하기 위해 입력하는 데이터의 colname이 동일해야 합니다.
이를 위해 입력값을 info_input이라는 변수에 딕셔너리 형태로 입력받아 info_search라는 학습 데이터와 colname이 동일한 딕셔너리를 만들어 대입하겠습니다.
info_input = {'Age':[],'Fare':[],'Pclass':[],'Family':[],'Embarked':[],'Sex':[]}
info_search = {'Age':[],'Fare':[],'1':[0],'2':[0],'3':[0],
'Family':[],'S':[0],'C':[0],'Q':[0],'Female':[0],'Male':[0]}
- 데이터프레임을 형성할 딕셔너리에 입력값 입력하기
입력값을 받을 변수를 준비했으니 query 변수에 어떤 입력값을 입력해야 하는지에 대한 질문을 list로 지정해주었습니다.
a는 query의 인덱싱을 위한 변수입니다.
a = 0
query = ('탑승 당시 나이를 입력해주세요','표 가격($)을 입력해주세요','객실의 등급을 입력해주세요(1,2,3)',
'가족 구성원 수(명)을 입력해주세요','승선한 항구를 입력해주세요(S,C,Q)','성별을 입력해주세요(Female,Male)')
질문과 입력값을 받을 변수가 준비되었으니 입력을 받아 info_input변수에 입력값을 하나하나 지정해주겠습니다.
for문을 사용하여 info_input의 키를 i 매개변수에 지정해주었고, 입력값이 입력될 때 마다 키의 가치값으로 입력값을 지정해주었습니다.
for i in info_input :
info_input[i] = input(query[a] + '\n')
a += 1
매개변수 k 에 info_input.keys()를 지정해주었습니다.
변수 key에 info_input[k]의 가치값을 지정해주었습니다.
여기서 info_input이 입력받는 자료의 형태를 머신러닝 모델이 학습할 수 있는 형태로 바꿔주겠습니다.
우선 info_input과 info_search의 colname이 겹치는 가치값들은 바로 옮겨줍니다.
info_input과 info_search의 colname이 동일한지 확인하는 방법은 info_input.keys()를 부여받는 매개변수 k가 info_search_keys()에 포함되어있는가 아닌가를 확인하면 됩니다.
colname이 겹치지 않는 데이터들은 info_input에 입력값을 입력할 때 info_search의 colname의 형태로 입력할 수 있게 질문을 작성합니다. info_input의 가치값에는 info_search의 colname이 입력되어 있기 때문에 info_search[key] 인덱싱을 통해 info_input의 가치값을 키로 갖는 딕셔너리에 1의 가치값을 부여합니다.
1의 가치값을 부여하는 이유는 머신러닝 모델이 학습하는 데이터가 이진분류를 통해 분류되었기 때문입니다.
for k in info_input.keys():
key = info_input[k]
if k in info_search.keys():
info_search[k] = info_input[k]
else:
info_search[key] = 1
- 데이터프레임 구축하기 / 결과 도출하기
마지막으로 딕셔너리 형태로 제작한 info_search를 데이터프레임으로 만들어줍니다.
데이터프레임으로 만들어진 info_search를 머신러닝 모델에 학습시켜줍니다.
예측한 결과의 값이 1이라면 생존, 이외의 경우라면 사망입니다.
info_search = pd.DataFrame.from_dict(info_search)
Y_pred = random_forest.predict(info_search)
if Y_pred == 1:
print('축하드립니다 생존하셨습니다~')
else :
print('유감입니다.. 사망하셨습니다 ㅜ^ㅜ')
위 코드를 함수로 만들어 프로그램을 실행시켜보겠습니다.
def SearchSurvival():
info_input = {'Age':[],'Fare':[],'Pclass':[],'Family':[],'Embarked':[],'Sex':[]}
info_search = {'Age':[],'Fare':[],'1':[0],'2':[0],'3':[0],'Family':[],'S':[0],'C':[0],'Q':[0],
'Female':[0],'Male':[0]}
a = 0
query = ('탑승 당시 나이를 입력해주세요','표 가격($)을 입력해주세요','객실의 등급을 입력해주세요',
'가족 구성원 수(명)을 입력해주세요','승선한 항구를 입력해주세요','성별을 입력해주세요')
for i in info_input :
info_input[i] = input(query[a] + '\n')
a += 1
for k in info_input.keys():
key = info_input[k]
if k in info_search.keys():
info_search[k] = info_input[k]
else:
info_search[key] = 1
info_search = pd.DataFrame.from_dict(info_search)
Y_pred = random_forest.predict(info_search)
if Y_pred == 1:
print('축하드립니다 생존하셨습니다~')
else :
print('유감입니다.. 사망하셨습니다 ㅜ^ㅜ')
<<실행 결과>>
SearchSurvival()
$12의 1등급 객실 표를 구매하여 4명의 가족과 함께 Queenzberg 항구에서 승선한 21세 남성의 생존 여부 예측입니다.
탑승 당시 나이를 입력해주세요
21
표 가격($)을 입력해주세요
12
객실의 등급을 입력해주세요
1
가족 구성원 수(명)을 입력해주세요
4
승선한 항구를 입력해주세요
Q
성별을 입력해주세요
Male
유감입니다.. 사망하셨습니다 ㅜ^ㅜ
# 생존 예측 프로그램 ver3
생존자 예측 프로그램 ver3 만들기
def err():
print("오류입니다. 다시 입력해주세요.")
def in_range(col, col_in_korean):
col_min = min(train[col])
col_max = max(train[col])
col_avg = train[col].mean()
while True:
T = float(input(f'{col_in_korean}를 입력하세요.({col_min:.2f} 이상 {col_max:.2f} 이하, 평균 {col_avg:.2f}) :'))
if T < col_min or T > col_max:
err()
else: break
new_input[col] = T
생존자 예측 프로그램 ver3은 새로운 사람의 데이터를 입력받아 그 사람의 생존 여부와 그 확률을 예측하는 프로그램입니다. 그래서 우선 에러 메시지를 출력하는 함수와, 잘못된 값이 입력되었을 때 앞에서 정의한 에러 메시지를 출력하고, 다시 입력을 받는 함수를 정의해줍니다.
in_range 함수를 적용하면 train 데이터의 최솟값과 최댓값 사이의 값만 입력받게 됩니다. 이는 극단적인 값을 입력받을 경우 예측능력이 떨어지기 때문입니다.
new_input = {'PassengerId':0,'Age':0,'Fare':0,'Family':0,'1':0,'2':0,'3':0,'F':0,'M':0,'S':0,'C':0,'Q':0}
new_input['PassengerId'] = list(test['PassengerId'])[-1] + 1
in_range('Age','나이')
in_range('Fare','탑승료')
in_range('Family','가족 수')
new_input은 우리가 입력할 새로운 데이터행입니다. 키에 각 요소들을 넣고 밸류는 전부 0으로 세팅합니다.
PassengerId는 이전 행에서 1을 더한 값으로 정해줍니다.
수치형 데이터인 나이, 탑승료, 가족 수에 대해서는 앞에서 정의한 in_range함수를 사용할 수 있습니다.
while True:
new_pclass = input('몇 등급 객실을 이용했는지 입력하세요.(1, 2, 3중 택1) :')
if new_pclass in ['1','2','3']: break
else : err()
new_input[new_pclass] = 1
while True:
new_sex = input('성별을 입력하세요.(F, M 중 택1) :')
if new_sex in ['F','M']: break
else : err()
new_input[new_sex] = 1
while True:
new_embarked = input('탑승 항구를 입력하세요.(S, C, Q 중 택1) :')
if new_embarked in ['S','C','Q']: break
else : err()
new_input[new_embarked] = 1
test = test.append(new_input, ignore_index = True)
이진 분류한 데이터인 객실 등급, 성별, 탑승 항구는 in_range함수를 적용할 수 없습니다.
따라서 새로운 형태의 while문으로 입력을 받습니다. 이 형태의 while문은 따로 함수화하지 않았습니다.
새로운 입력 데이터의 각 밸류는 0으로 세팅되어있기 때문에, 입력받은 값의 밸류만 1로 바꿔주면 됩니다.
이후 append 함수를 통해 입력받은 데이터를 기존 데이터의 새로운 행으로 추가합니다.
X_train = train.drop("Survived",axis=1)
Y_train = train["Survived"]
X_test = test.drop("PassengerId",axis=1).copy()
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
Y_pro = random_forest.predict_proba(X_test)
if Y_pred[-1] == 1:
print(f'높은 확률로 생존합니다. (생존 확률 {Y_pro[-1][1]*100}%)')
else:
print(f'높은 확률로 사망합니다. (생존 확률 {Y_pro[-1][1]*100}%)')
다음으로 학습을 준비합니다. 생존 여부와 나머지 데이터를 분리하여 RandomForest 방식으로 학습시키고, 예측 결과와 확률을 Y_pred, Y_pro에 각각 저장합니다. 예측 결과가 1일 경우 생존, 0일 경우 사망을 처리됩니다.
<<실행 결과>>
나이를 입력하세요.(0.42 이상 80.00 이하, 평균 28.57) :20
탑승료를 입력하세요.(0.00 이상 512.33 이하, 평균 32.20) :30
가족 수를 입력하세요.(0.00 이상 10.00 이하, 평균 0.90) :3
몇 등급 객실을 이용했는지 입력하세요.(1, 2, 3중 택1) :1
성별을 입력하세요.(F, M 중 택1) :M
탑승 항구를 입력하세요.(S, C, Q 중 택1) :S
높은 확률로 생존합니다. (생존 확률 56.00000000000001%)
예측에서 사용한 RandomForestClassifier에는 많은 파라미터가 있습니다. 예측의 정확성을 위해 n_estimators 이외의 파라미터들도 조작해보겠습니다.
corr_matrix = train.corr(method='pearson')
abs(corr_matrix['Survived']['Age'])
feature = train.columns
corr_dict = {feature:abs(corr_matrix['Survived'][feature]) for feature in train.columns}
corr_dict[0] = 1; corr_dict[1] = 1
random_forest = RandomForestClassifier(n_estimators=100, criterion = 'log_loss', class_weight = corr_dict,
bootstrap = False)
criterion은 예측 방식을 정하는 파라미터입니다. 디폴트 값은 gini이므로 로그 손실 방식으로 바꿔보았습니다.
class_weight는 각 클래스별 가중치를 정해주는 파라미터입니다. 저는 위에서 정의한 survived와 다른 각 변수들의 상관계수를 가중치로 지정해보았습니다.
bootstrap은 샘플 사용 방식을 결정합니다. 디폴트 값은 True로 설정되어있고, 이 경우 무작위로 샘플을 골라 학습하여 과적합을 줄이는 효과가 있습니다. False로 설정할 경우 무작위로 고르는 것이 아니라, 전체 데이터를 사용해 학습하게 됩니다. 비교를 위해 False로 진행해보겠습니다.
X_train = train.drop("Survived",axis=1)
Y_train = train["Survived"]
X_test = test.drop("PassengerId",axis=1).copy()
random_forest.fit(X_train, Y_train)
Y_pred_1 = random_forest.predict(X_train)
위에서 진행한 것과 같은 방식으로 학습을 하고 Y_pred_1에 저장합니다.
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred_2 = random_forest.predict(X_train)
파라미터를 조작하지 않은 학습결과는 Y_pred_2에 저장합니다.
test 데이터는 생존 유뮤에 대한 정답이 없기 때문에 어떤 모델이 더 정확도가 높은지 비교하기 위해서는 train데이터를 써야 합니다. Y_pred_1과 Y_pred_2에 각각의 결과를 저장했으니 train데이터의 정답과 비교해봅시다.
a_1 = 0
for i in range(0,len(Y_pred_1)):
if Y_pred_1[i] != Y_train[i]:
print(i); a_1+=1
print('Discord', a_1)
Y_pred_1과 Y_train의 값이 다를 경우 discord와 해당 값의 인덱스, 불일치 값의 개수를 출력합니다.
17
Discord 1
21
Discord 2
36
Discord 3
107
Discord 4
187
Discord 5
220
Discord 6
226
Discord 7
264
Discord 8
267
Discord 9
283
Discord 10
288
Discord 11
345
Discord 12
400
Discord 13
507
Discord 14
579
Discord 15
826
Discord 16
828
Discord 17
17개가 틀렸네요. 두 번째 예측값도 비교해봅시다.
a_2 = 0
for i in range(0,len(Y_pred_1)):
if Y_pred_2[i] != Y_train[i]:
print(i); a_2+=1
print('Discord',a_2)
17
Discord 1
21
Discord 2
36
Discord 3
107
Discord 4
199
Discord 5
220
Discord 6
226
Discord 7
264
Discord 8
267
Discord 9
283
Discord 10
288
Discord 11
400
Discord 12
507
Discord 13
536
Discord 14
579
Discord 15
826
Discord 16
828
Discord 17
이번에도 17개가 틀렸네요. 혹시 예측결과가 아예 같은건 아닌가 의심이 들죠. 그래서 두 예측 결과 값도 비교해봅시다.
for i in range(0,len(Y_pred_1)):
if Y_pred_1[i] != Y_pred_2[i]:
print(i)
print('Discord')
187
Discord
199
Discord
345
Discord
536
Discord
4개정도 예측 결과가 다르네요. 신기하게도 예측 결과는 다른데 오답의 갯수는 같습니다.
'Study > Kaggle competition' 카테고리의 다른 글
Kaggle competiton #1 : Titanic survivor predictions (0) | 2023.04.12 |
---|---|
Kaggle Competition #4 (1) | 2023.04.04 |
kaggle competition #3 (0) | 2023.04.03 |
Kaggle competition #5 : House Price prediction (0) | 2023.03.23 |