Github Code: Hoon0427/RPubs
File Download: Titanic Data

Titanic 생존 분석

0. 패키지 불러오기&데이터 가져오기

library(tidyverse) #Tidyverse 패키지
library(ggplot2) #시각화 패키지
library(plotly)  #반응형 시각화 패키지
library(rpart) # 의사결정 나무
library(rpart.plot) # 의사결정 나무 시각화 
library(caret) # 데이터 처리 패키지
## Loading required package: lattice
## 
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
## 
##     lift
library(e1071)   #혼동행렬 패키지
library(randomForest)  #Random Forest 패키지
training_set <- read.csv("train.csv")
test_set <- read.csv("test.csv")


1. 타이타닉 생존예측을 하기위한 도메인 지식


타이타닉 생존예측을 하기위해 최소한의 도메인 지식을 사용해보자.
타이타닉이 침몰한 해는 1912년, 그리고 그 당시에는 lady first 라는 개념이 전반적으로 팽배했으며, 그러한 이유로 침몰중에 승무원들은 어린아이와 여자부터 먼저 챙겼다고 한다. 또한, 부자, 일반인, 가난한 사람 등 여러 부류의 사람이 있었으며, 이는 각각 1,2,3 등급의 객실에 탑승했었던 것으로 보인다.
이러한 도메인 지식은 간단한 인터넷검색과 영화 ’타이타닉’에서 볼 수 있으며, 극 중 남주인 레오나르도 디카프리오는 3등급 손님이었으며, 역시 영화에서 사망으로 표현된다.


2. 탐색적 데이터 분석(EDA)

1) 데이터 구조 파악


데이터의 구조를 파악해보겠습니다. 데이터의 구조는 다음과 같습니다.

str(training_set)
## 'data.frame':    891 obs. of  12 variables:
##  $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Survived   : int  0 1 1 1 0 0 0 0 1 1 ...
##  $ Pclass     : int  3 1 3 1 3 3 1 3 3 2 ...
##  $ Name       : Factor w/ 891 levels "Abbing, Mr. Anthony",..: 109 191 358 277 16 559 520 629 417 581 ...
##  $ Sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 2 2 2 1 1 ...
##  $ Age        : num  22 38 26 35 35 NA 54 2 27 14 ...
##  $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
##  $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
##  $ Ticket     : Factor w/ 681 levels "110152","110413",..: 524 597 670 50 473 276 86 396 345 133 ...
##  $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
##  $ Cabin      : Factor w/ 148 levels "","A10","A14",..: 1 83 1 57 1 1 131 1 1 1 ...
##  $ Embarked   : Factor w/ 4 levels "","C","Q","S": 4 2 4 4 4 3 4 4 4 2 ...


데이터의 구조 파악하기

  • PassengerID : 승객 고유번호
  • Survived : 생존여부 0=No/ 1=Yes
  • Pclass : 1등급, 2등급, 3등급
    ** int형으로 되어있기에 factor형으로 변경필요
  • Name : 승객명
    ** factor형으로 되어있기에 character형으로 변경필요
  • Sex : 성별
  • SibSp: 함께 탑승한 배우자 또는 형제의 수
  • Ticket : 티켓번호
    ** factor형으로 되어있기에 character형으로 변경필요
  • Fare : 티켓요금
  • Cabin : 선실번호
    ** factor형으로 되어있기에 character형으로 변경필요
  • Embarked : 탑승한 곳 각각 “C”, “Q”, “S”는 탑승한 곳을 뜻하는 것으로 예상
training_set$Pclass <- as.factor(training_set$Pclass)
training_set$Name <- as.character(training_set$Name)
training_set$Ticket <- as.character(training_set$Ticket)
training_set$Cabin <- as.character(training_set$Cabin)

str(training_set)
## 'data.frame':    891 obs. of  12 variables:
##  $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Survived   : int  0 1 1 1 0 0 0 0 1 1 ...
##  $ Pclass     : Factor w/ 3 levels "1","2","3": 3 1 3 1 3 3 1 3 3 2 ...
##  $ Name       : chr  "Braund, Mr. Owen Harris" "Cumings, Mrs. John Bradley (Florence Briggs Thayer)" "Heikkinen, Miss. Laina" "Futrelle, Mrs. Jacques Heath (Lily May Peel)" ...
##  $ Sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 2 2 2 1 1 ...
##  $ Age        : num  22 38 26 35 35 NA 54 2 27 14 ...
##  $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
##  $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
##  $ Ticket     : chr  "A/5 21171" "PC 17599" "STON/O2. 3101282" "113803" ...
##  $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
##  $ Cabin      : chr  "" "C85" "" "C123" ...
##  $ Embarked   : Factor w/ 4 levels "","C","Q","S": 4 2 4 4 4 3 4 4 4 2 ...


원하는대로 자료형이 바뀐 것을 확인할 수 있습니다. 이와 같이 test_set도 자료형을 바꿔주겠습니다.

test_set$Pclass <- as.factor(test_set$Pclass)
test_set$Name <- as.character(test_set$Name)
test_set$Ticket <- as.character(test_set$Ticket)
test_set$Cabin <- as.character(test_set$Cabin)

str(test_set)
## 'data.frame':    418 obs. of  11 variables:
##  $ PassengerId: int  892 893 894 895 896 897 898 899 900 901 ...
##  $ Pclass     : Factor w/ 3 levels "1","2","3": 3 3 2 3 3 3 3 2 3 3 ...
##  $ Name       : chr  "Kelly, Mr. James" "Wilkes, Mrs. James (Ellen Needs)" "Myles, Mr. Thomas Francis" "Wirz, Mr. Albert" ...
##  $ Sex        : Factor w/ 2 levels "female","male": 2 1 2 2 1 2 1 2 1 2 ...
##  $ Age        : num  34.5 47 62 27 22 14 30 26 18 21 ...
##  $ SibSp      : int  0 1 0 0 1 0 0 1 0 2 ...
##  $ Parch      : int  0 0 0 0 1 0 0 1 0 0 ...
##  $ Ticket     : chr  "330911" "363272" "240276" "315154" ...
##  $ Fare       : num  7.83 7 9.69 8.66 12.29 ...
##  $ Cabin      : chr  "" "" "" "" ...
##  $ Embarked   : Factor w/ 3 levels "C","Q","S": 2 3 2 3 3 3 2 3 1 3 ...


test_set$Age[is.na(test_set$Age)] <- mean(test_set$Age, na.rm = T)


sapply(test_set, function(x){
  sum(is.na(x))
})
## PassengerId      Pclass        Name         Sex         Age       SibSp 
##           0           0           0           0           0           0 
##       Parch      Ticket        Fare       Cabin    Embarked 
##           0           0           1           0           0


2)데이터 요약정보 파악


데이터 요약 정보를 파악한 결과 다음과 같습니다.

summary(training_set)
##   PassengerId       Survived      Pclass      Name               Sex     
##  Min.   :  1.0   Min.   :0.0000   1:216   Length:891         female:314  
##  1st Qu.:223.5   1st Qu.:0.0000   2:184   Class :character   male  :577  
##  Median :446.0   Median :0.0000   3:491   Mode  :character               
##  Mean   :446.0   Mean   :0.3838                                          
##  3rd Qu.:668.5   3rd Qu.:1.0000                                          
##  Max.   :891.0   Max.   :1.0000                                          
##                                                                          
##       Age            SibSp           Parch           Ticket         
##  Min.   : 0.42   Min.   :0.000   Min.   :0.0000   Length:891        
##  1st Qu.:20.12   1st Qu.:0.000   1st Qu.:0.0000   Class :character  
##  Median :28.00   Median :0.000   Median :0.0000   Mode  :character  
##  Mean   :29.70   Mean   :0.523   Mean   :0.3816                     
##  3rd Qu.:38.00   3rd Qu.:1.000   3rd Qu.:0.0000                     
##  Max.   :80.00   Max.   :8.000   Max.   :6.0000                     
##  NA's   :177                                                        
##       Fare           Cabin           Embarked
##  Min.   :  0.00   Length:891          :  2   
##  1st Qu.:  7.91   Class :character   C:168   
##  Median : 14.45   Mode  :character   Q: 77   
##  Mean   : 32.20                      S:644   
##  3rd Qu.: 31.00                              
##  Max.   :512.33                              
## 


  • Pclass 는 1등급이 216명, 2등급이 184 명, 3등급이 491명으로 구성되어 있다.

  • 성별은 남자가 314명, 여자가 577명으로 분포되어 있다.

  • 나이는 최솟값이 0.42 세, 최댓값이 80 세로 되어있다. 그리고 평균은 29.7세, 1분위수는 20.12세, 3분위수는 38세, NA가 177명 인것으로 봐서 당시 나이가 제대로 파악되지 않았음을 알 수 있다.

  • 함께 탑승한 형제 또는 배우자의 수는 최대 8명 그리고 평균적으로 0.5명인 것으로 보인다.

  • 함께 탑승한 부모 또는 자녀의 수는 최대 6명이고 평균이 0.38명으로 보인다.


3)결측치 파악


결측치를 파악해보자.

sum(is.na(training_set))
## [1] 177


결측치가 177개나 됩니다. 결측치는 Age에서 177개가 있습니다. 조금 더 구체적으로 컬럼별 결측치를 파악하겠습니다.

sapply(training_set, function(x){
  sum(is.na(x))
})
## PassengerId    Survived      Pclass        Name         Sex         Age 
##           0           0           0           0           0         177 
##       SibSp       Parch      Ticket        Fare       Cabin    Embarked 
##           0           0           0           0           0           0


이로써 정확히 Age에만 결측치가 있는 것을 확인할 수 있다. 나이는 결측치를 처리하는데 있어서 여러가지 방법이 있는데, 그 중 대표적인 것들이 NA제거, 평균값으로 대치, 행,열 제거가 있습니다. 나이에 대해서는 평균값으로 대치할 경우 생존여부에 영향을 끼칠 수 있으므로 행을 제거하는 방법을 사용하겠습니다.

training_set<-na.omit(training_set)
sum(is.na(training_set))
## [1] 0


결측치가 제거되었음을 확인할 수 있습니다.


4)나이 데이터 전처리


나이 데이터를 factor형식으로 10살 단위로 끊어 정제해보겠습니다. 60살이 넘으면 over60이라는 팩터를 구분을 지어놨습니다. 그리고 시각화를 하면, 어느정도 정규성을 띄는 것을 볼 수 있습니다.

training_set <- training_set %>% 
  mutate(Ages = case_when(
    Age < 10 ~ "Under 10",
    Age < 20 ~ "10 ~ 20",
    Age < 30 ~ "20 ~ 30",
    Age < 40 ~ "30 ~ 40",
    Age < 50 ~ "40 ~ 50",
    Age < 60 ~ "50 ~ 60",
    TRUE ~ "over 60"
  )) 

training_set$Ages <- 
  factor(training_set$Ages,
         levels = c("Under 10", "10 ~ 20", "20 ~ 30", "30 ~ 40", "40 ~ 50", "50 ~ 60", "over 60"))

data_cleanging <- training_set %>% 
  group_by(Ages) %>% 
  summarise(Ages_count = n())

ggplot(data_cleanging, aes(x = Ages, y = Ages_count, fill=Ages)) +
  geom_col() +
  geom_text(aes(label=(Ages_count)), vjust=3, hjust = 0.5,color="black", size=4) +
  theme(axis.text.x = element_text(size=10)) +
  theme(axis.text.y = element_text(size=10))


3. 데이터 시각화


타이타닉의 데이터를 시각화를 통해 파악해보겠습니다. 앞서 도메인 지식을 통해 어느정도는 남자와 여자가, 또는 객실의 등급에 따라서, 나이에 따라 생존 유무가 달라지는 것을 확인할 수 있었지만, 시각화를 통해 조금 더 직관적으로 알아보겠습니다.


1) 성별에 따른 생존여부


성별에 따른 생존여부를 시각화해보겠습니다. 왼쪽 막대 그래프가 사망자의 남녀 분포이고, 오른쪽의 막대 그래프가 생존자의 막대 그래프입니다. 그래프만 봐도 알 수 있듯이, 사망자 중에서는 남자가 많은 비율을 차지하는것을 볼 수 있습니다.

ggplot_data<- ggplot(training_set, aes(x=Survived, fill = Sex)) +
  geom_bar() +
  ggtitle("성별에 따른 생존 여부") +
  theme_bw()

ggplotly(ggplot_data, height = 500, width=800)


2) Pclass에 따른 생존여부


Pclass에 따른 생존여부에서 사망자의 수는 Pclass등급에 따라 어느정도 차이를 보이고 있으며, 왼쪽 생존자 막대 그래프에서는 등급에 따른 큰 차이를 보이지 않고 있습니다.

ggplot_data <- ggplot(training_set, aes(x = Survived, fill = Pclass)) +
  geom_bar() +
  ggtitle(" Pclass에 따른 생존 여부 ") +
  theme_bw()

ggplotly(ggplot_data, height = 500, width = 800)


3) 나이에 따른 생존여부


나이에 따른 생존여부를 확인하기위해 시각화를 한 자료에 근거하면, Under10과 over60에서 비교적 적은 사망자수를 확인할 수 있는 반면, 20~50대 사망자가 많은것으로 보아, 어린아이들과 노인들에 대한 선조치가 이루어졌을 것을 짐작할 수 있습니다.

ggplot_data <- training_set %>% 
  ggplot(aes(x = Survived, fill = Ages)) +
  geom_bar() +
  ggtitle(" 나이에 따른 생존 여부 ") +
  theme_bw() 

ggplotly(ggplot_data, height = 500, width = 800)


4) SibSp에 따른 생존여부


혼자 탑승한 승객의 사망자수가 가장 높게 나타났으나, 단순하게 1인 승객의 사망률이 높게 나타난것일 수 있으므로 유의미한 해석은 아닐 것 같습니다.

ggplot_data <- training_set %>% 
  ggplot(aes( x = Survived, fill = factor(SibSp))) +
  geom_bar() +
  ggtitle( "같이 탑승한 배우자 또는 형제에 따른 생존여부") +
  theme_bw()

ggplotly(ggplot_data, height = 500, width = 800)


5) Parch에 따른 생존여부


함께 탑승한 부모 또는 자녀의 수에 따른 생존여부를 시각해보았습니다. 예상했듯이 큰 의미는 없습니다.

ggplot_data <- training_set %>% 
  ggplot(aes( x = Survived, fill = factor(Parch))) +
  geom_bar() +
  ggtitle( "함께 탑승한 부모 또는 자녀의 수에 따른 생존여부") +
  theme_bw()


ggplotly(ggplot_data, height = 500, width = 800)


4. 모델생성


데이터에 대한 파악, 자료형 변환을 마쳤고, 시각화까지 모두 학인해보았습니다. 이제 트레이닝 셋으로 모델을 만들어서 테스트 셋을 예측해보겠습니다. 종속변수는 Survived, 독립변수는 Sex와 Pclass, age로 하겠습니다. 그 전에 Survived의 자료형을 factor로 바꿔주겠습니다.

training_set$Survived <- as.factor(training_set$Survived)
str(training_set)
## 'data.frame':    714 obs. of  13 variables:
##  $ PassengerId: int  1 2 3 4 5 7 8 9 10 11 ...
##  $ Survived   : Factor w/ 2 levels "0","1": 1 2 2 2 1 1 1 2 2 2 ...
##  $ Pclass     : Factor w/ 3 levels "1","2","3": 3 1 3 1 3 1 3 3 2 3 ...
##  $ Name       : chr  "Braund, Mr. Owen Harris" "Cumings, Mrs. John Bradley (Florence Briggs Thayer)" "Heikkinen, Miss. Laina" "Futrelle, Mrs. Jacques Heath (Lily May Peel)" ...
##  $ Sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 2 2 1 1 1 ...
##  $ Age        : num  22 38 26 35 35 54 2 27 14 4 ...
##  $ SibSp      : int  1 1 0 1 0 0 3 0 1 1 ...
##  $ Parch      : int  0 0 0 0 0 0 1 2 0 1 ...
##  $ Ticket     : chr  "A/5 21171" "PC 17599" "STON/O2. 3101282" "113803" ...
##  $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
##  $ Cabin      : chr  "" "C85" "" "C123" ...
##  $ Embarked   : Factor w/ 4 levels "","C","Q","S": 4 2 4 4 4 4 4 4 2 4 ...
##  $ Ages       : Factor w/ 7 levels "Under 10","10 ~ 20",..: 3 4 3 4 4 6 1 3 2 1 ...


1) Decision tree


Decision tree란, 나무가지치듯 이진분류를 사용하여 데이터를 분류하는 기법입니다.


의사결정 나무 결과 해석

  • 남자는 6.5세 기준으로 생존사망 분류가 뚜렷합니다.
  • 여자는 Pclass 3과 1,2로 크게 분류가 되었습니다. # Pclass 3의 여자는 5.5, 12, 38.5 살을 기준으로 분류되었습니다.
# 의사결정나무 모델 사용
rpart_m <- rpart(Survived ~ Pclass + Age + Sex, data = training_set)

# 의사결정나무 시각화
prp(rpart_m, type=4, extra=2, digits=3)

# test set 확인
rpart_p <- predict(rpart_m, newdata=test_set, type = "class")


2) Random Forest


랜덤포레스트는 의사결정나무 모델의 상위버전이라고 할 수 있습니다. 여러개의 의사결정나무모델을 사용하여 정확도를 높이는 앙상블 기법 중 하나입니다.


RandomForest 결과해석

  • 변수의 중요도를 확인하니, Sex, Pclass, Age 순으로 된 도표를 확인할 수 있습니다.
# RandomForest 모델 생성
rf_m <- randomForest(Survived ~ Pclass + Age + Sex, data = training_set)

# importance
rf_info <- randomForest(Survived ~ Sex + Age + Pclass , data = training_set, importance = T)

# 데이터의 중요도 확인 
importance(rf_info)
##               0        1 MeanDecreaseAccuracy MeanDecreaseGini
## Sex    44.26519 57.01307             53.26653         82.21721
## Age    17.41469 16.78564             24.13932         20.54890
## Pclass 23.51503 28.04113             28.98233         34.09888
#데이터의 중요도 시각화
varImpPlot(rf_info)

# test 결과 확인
rf_p <- predict(rf_m, newdata=test_set, type="class")


5. 제출데이터 및 구성결과 확인


생성한 모델을 제출양식에 맞춰 Data Frame으로 제출해보겠습니다. 제출 양식은 처음에 다운받은 gender_submision.csv에서 확인할 수 있습니다. 그리고 제출한 결과 스코어는 다음과 같습니다.

  • Decision Tree Score : 0.73684
  • Random Forest Score : 0.75598

두 모델 모두 73%, 75%로 비슷한 정확도를 보였지만, Random Forest의 결과가 조금 더 좋았습니다.

# 의사결정 나무 제출 데이터 
Titanic_rpart <- data.frame(PassengerID = test_set$PassengerId, Survived = rpart_p)

write.csv(Titanic_rpart, file = "Titanic_rpart_submit.csv", row.names = FALSE)

# 랜덤포레스트 제출 데이터 
Titanic_rf <- data.frame(PassengerID = test_set$PassengerId, Survived = rf_p)

write.csv(Titanic_rf, file = "Titanic_rf_submit.csv", row.names = FALSE)


6. 총평


이렇게 캐글에서 타이타닉 생존자 예측을 시도했습니다. Pclass, Age, Sex 세 가지만 가지고 모델을 돌렸으며, 신뢰도는 70%정도가 나왔습니다. 조금 더 높힐 수 있는 방법으로는 test_set의 NA값을잘 처리하거나, Data 선정을 조금 더 잘 해야하는 방법이 있겠습니다.