ในส่วนที่แล้ว เราได้โจทย์ให้คาดการณ์ว่าผู้โดยสารคนไหนจะรอดชีวิตจากเหตุเรือไททานิคล่ม โดยใช้ การจัดหมวดหมู่ (Classification) และโมเดลที่นิยมอย่าง Logistic Regression, Resolution Tree, Random Forest, Help Vector Machine (SVM), Okay-Nearest Neighbors (KNN)
ในส่วนนี้เราจะทำการเตรียมข้อมูล (Information Preparation) เพื่อให้พร้อมสำหรับการสร้างโมเดล โดยผมจะรวมทั้ง การทำความสะอาดข้อมูล (Information Cleansing), การวิเคราะห์ข้อมูลเบื้องต้น (Exploratory Information Evaluation, EDA), และ การสร้างฟีเจอร์ (Characteristic Engineering) เข้าไว้ด้วยกัน
ขั้นแรกมาโหลด Libraries ที่จะใช้ใน R กันก่อนครับ
library(tidyverse)
library(ggthemes)
library(patchwork)
library(rmarkdown)
library(knitr)
library(caret)
library(kernlab)
library(gbm)
library(glmnet)
library(pROC)
library ที่สำคัญจริงๆจะมีสองตัว ส่วนตัวอื่นจะเป็นตัวเสริมที่ทำให้การทำงานดีขึ้นครับ
- tidyverse: เป็นตัวรวมหลาย bundle ที่ช่วยให้การทำงานกับข้อมูลใน R ง่ายขึ้น ทั้งจัดการ แปลง และวิเคราะห์ข้อมูล โดยในนั้นจะประกอบด้วย bundle อย่าง
ggplot2
,dplyr
,tidyr
,readr
,purrr
,tibble
,stringr
, และforcats
- caret: ช่วยสร้างและประเมินโมเดลการเรียนรู้ของเครื่อง ทำให้การทำ Machine Studying ง่ายขึ้น
ทำการโหลดข้อมูลจาก Kaggle เข้าสู่โปรแกรม โดยจะมีทั้งหมด 3 ไฟล์ ได้แก่ ชุดฝึกซ้อม (practice.csv), ชุดทดสอบ (take a look at.csv), ชุดคำตอบ (gender_submission.csv)
practice <- learn.csv("Titanic/practice.csv", stringsAsFactors = FALSE, na.strings = c("NA", ""))
take a look at <- learn.csv("Titanic/take a look at.csv", stringsAsFactors = FALSE, na.strings = c("NA", ""))
take a look at$Survived <- NA
titanic <- rbind(practice, take a look at)
str(titanic)
จากข้อมูล ผมได้สร้างตัวแปร Survived
เป็นค่าว่างในชุดทดสอบ และรวมข้อมูลทั้งหมดเพื่อทำการตรวจสอบโครงสร้างของข้อมูลใน DataFrame
จะได้ข้อมูลสรุปตามนี้
ทำการตรวจสอบค่าที่หายไป (Lacking Information) ด้วยฟังชั่น sapply() โดยผมได้ทำเป็น 2 แบบ นับปกติและทำเป็นเปอร์เซ็นต์
sapply(titanic, operate(x) {sum(is.na(x))})
spherical(sapply(titanic, operate(x) { sum(is.na(x)) }) / 1309 * 100, 2)
จากการวิเคราะห์นี้ เราจะเห็นได้ว่าคอลัมน์ที่มีข้อมูลหายไปมากที่สุดคือ Cabin (77.46%) รองลงมาคือ Age (20.09%) และ Embarked(0.15%)
การจัดการข้อมูลที่หายไป:
- สำหรับ Cabin ข้อมูลหายไปเยอะมาก การทำ Imputation อาจไม่ได้ข้อมูลที่ตรงความเป็นจริงมากนัก ดังนั้นจะละเว้นคอลัมน์นี้ในการวิเคราะห์และสร้างโมเดล
- ส่วน Survived, Pclass, Title, Intercourse, SibSp, Parch, Ticket ไม่มีข้อมูลหายไปในคอลัมน์ สามารถใช้ในการวิเคราะห์ได้เลย
แต่ก่อนจะไปวิเคราะกันต้องทำการเปลี่ยนชนิดข้อมูลบางคอลัมน์กันก่อน
titanic$Intercourse <- as.issue(titanic$Intercourse)
titanic$Survived <- as.issue(titanic$Survived)
titanic$Pclass <- as.ordered(titanic$Pclass)
เหตุผลในการแปลงข้อมูลเป็น issue:
การแปลงข้อมูลเป็น issue ใน R ช่วยให้เราจัดการกับข้อมูลประเภทหมวดหมู่ได้ง่ายขึ้น ไม่ว่าจะเป็นการสรุปข้อมูลหรือการสร้างโมเดลที่สามารถเข้าใจความสัมพันธ์ของค่าหมวดหมู่ได้ดีกว่าเดิม และการทำเป็น ordered issue ทำให้ R รู้ว่าค่าของชั้นที่นั่งมีลำดับจากดีไปแย่ (1 < 2 < 3) ซึ่งดีสำหรับการวิเคราะห์ข้อมูลที่มีลำดับความสำคัญ
ในส่วนนี้เราจะมาสำรวจและวิเคราะว่าข้อมูลในแต่ละตัวแปลบอกอะไรเราบ้าง
Survived
เริ่มด้วยตัวแปลที่สำคัญที่สุดกันก่อนเลย ในชุดข้อมูลของเรามีผู้โดยสารรอดชีวิตกี่คนและเสียชีวิตกี่คน
##Survived
ggplot(titanic[!is.na(titanic$Survived),],
aes(x=Survived,fill=Survived, label=..depend..))+
geom_bar(stat='depend',alpha=0.9, colour = "black", width = 0.6) +
geom_text(stat='depend', place = position_stack(vjust = 0.5), dimension = 6, colour = "white") +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
title = "Survival Charges of Titanic",
x = "Survived",
y = "Quantity"
) +
theme_bw()+
theme(
plot.title = element_text(hjust = 0, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
จากข้อมูลผู้โดยสาร 1309 ราย ในชุดฝึกซ้อมมีข้อมูลตัวแปร Survived 891 ราย จะเห็นว่าจำนวนผู้โดยสารที่ไม่รอดชีวิตมีมากกว่าผู้รอดชีวิต โดยจำนวนผู้ไม่รอดชีวิตมากกว่าผู้รอดชีวิตประมาณ 1.6 เท่า
intercourse
ggplot(titanic[!is.na(titanic$Survived),],
aes(x=Intercourse, fill=Survived, label=..depend..)) +
geom_bar(stat='depend', alpha=0.9, colour="black", width=0.6, place=position_dodge(width=0.7)) +
geom_text(stat='depend', place=position_dodge(width=0.7), vjust=1.4, dimension=6, colour="white") +
scale_fill_discrete(labels=c("0"="Not Survived", "1"="Survived")) +
labs(
title = "Survival Charges by intercourse",
x="Intercourse",
y="Quantity"
) +
theme_bw() +
theme(
plot.title=element_text(hjust=0, face="daring", dimension=18),
axis.title.x=element_text(face="daring", dimension=13),
axis.title.y=element_text(face="daring", dimension=13),
axis.textual content=element_text(dimension=12)
)
จะเห็นว่าเพศมีผลต่อการรอดชีวิตอย่างมาก ผู้โดยสารที่เป็นผู้หญิงมีโอกาสรอดมากกว่าผู้ชายชัดเจน ในกราฟที่เห็น ผู้หญิงที่รอดชีวิตมี 233 คน แต่ที่ไม่รอดมีแค่ 81 คน ส่วนผู้ชายที่รอดชีวิตมีแค่ 109 คน แต่ไม่รอดถึง 468 คน แสดงว่าผู้หญิงมีโอกาสรอดมากกว่าผู้ชายหลายเท่า ที่เป็นแบบนี้อาจเพราะในสถานการณ์ฉุกเฉินมักจะให้ความสำคัญกับการช่วยผู้หญิงและเด็กก่อน
ต่อมาผมจะลองทำเป็นเปอร์เซ็นต์ไว้สำหลับวิเคราะเพิ่มเติม
titanic_per_sex <- titanic %>%
filter(!is.na(Survived)) %>%
depend(Intercourse, Survived) %>%
group_by(Intercourse) %>%
mutate(proportion = n / sum(n) * 100)
ggplot(titanic_per_sex, aes(x = Intercourse, y = proportion, fill = Survived, label = paste0(spherical(proportion, 1), "%"))) +
geom_bar(stat = "identification", alpha = 0.9, colour = "black", width = 0.6, place = position_fill()) +
geom_text(place = position_fill(vjust = 0.5), dimension = 6, colour = "white") +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
title = "Survival Charges by intercourse(%)",
x = "Intercourse",
y = "Proportion"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
จากรูปจะเห็นความแตกต่างชัดเจนว่าตัวแปรเพศมีผลต่ออัตราการรอดชีวิตอย่างมาก
age
# กรองข้อมูลที่ไม่มีค่า NA ใน Age และ Survived
titanic_filtered_na <- titanic %>%
filter(!is.na(Survived), !is.na(Age))
mean_ages <- titanic_filtered_na %>%
group_by(Survived) %>%
summarise(mean_age = imply(Age))ggplot(titanic_filtered_na, aes(x = Age, fill = Survived, label = ..depend..)) +
geom_histogram(aes(y = ..density..), alpha = 0.5, place = "identification", bins = 30) +
geom_density(alpha = 0.2, aes(colour = as.issue(Survived))) +
geom_vline(information = mean_ages, aes(xintercept = mean_age, colour = as.issue(Survived)), linetype = "dashed", linewidth = 1) +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
scale_color_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
title = "Survival Charges by Age and Survival",
x = "Age",
y = "Density"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
ถ้าดูผ่านๆกราฟนี้จะเห็นว่าเด็กและคนอายุน้อยมีโอกาสรอดชีวิตมากกว่าคนที่อายุมาก โดยเฉพาะเด็กอายุ 0–10 ปีที่มีอัตรารอดชีวิตสูง ส่วนคนอายุ 20–40 ปีมีทั้งรอดชีวิตและไม่รอดชีวิตใกล้เคียงกัน และคนที่อายุมากกว่า 40 ปีขึ้นไปมีโอกาสไม่รอดชีวิตมากกว่า
แต่เนื่องจากกราฟนี้เป็นปริมาณอาจทำให้ไม่เห็นภาพชัดเจน ผมเลยจะลองทำเป็นร้อยละสำหรับมาวิเคราะอีกอัน
# สร้างกลุ่มอายุ
titanic_filtered_na <- titanic_filtered_na %>%
mutate(AgeGroup = reduce(Age, breaks = seq(0, max(Age, na.rm = TRUE) + 10, by = 10), proper = FALSE, embrace.lowest = TRUE)) %>%
mutate(AgeGroup = as.character(AgeGroup)) %>%
mutate(AgeGroup = gsub("[|]|)", "", AgeGroup)) %>%
mutate(AgeGroup = gsub(",", "-", AgeGroup))
survival_rates_by_age <- titanic_filtered_na %>%
group_by(AgeGroup) %>%
summarise(
whole = n(),
survived = sum(as.numeric(as.character(Survived))),
survival_rate = survived / whole
)
ggplot(survival_rates_by_age, aes(x = AgeGroup, y = survival_rate, fill = AgeGroup)) +
geom_bar(stat = "identification", alpha = 0.7) +
geom_text(aes(label = scales::p.c(survival_rate)), vjust = -0.5, dimension = 4) +
scale_y_continuous(labels = scales::p.c) +
labs(
title = "Survival Charges by Age Group(%)",
x = "Age Group",
y = "Survival Charge"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0.5, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
ดูจากกราฟนี้จะมีความชัดเจนยิ่งขึ้น เด็กอายุ 0–10 ปีมีโอกาสรอดชีวิตสูงสุดถึง 61.3% ซึ่งสูงกว่าช่วงอายุอื่นๆ อาจเพราะให้ความสำคัญในการช่วยเหลือเด็กและคนที่อายุน้อยในสถานการณ์ฉุกเฉิน ส่วนกลุ่มอายุอื่น ๆ จะมีโอกาสรอดชีวิตประมาณ 30–40% โดยไม่ได้แตกต่างอย่างมีนัยสำคัญ (ไม่รวมอายุ 70–90 ปี)
class
ggplot(titanic[!is.na(titanic$Survived),],
aes(x=Pclass, fill=Survived, label=..depend..)) +
geom_bar(stat='depend', alpha=0.9, colour="black", width=0.6, place=position_dodge(width=0.7)) +
geom_text(stat='depend', place=position_dodge(width=0.7), vjust=1.4, dimension=6, colour="white") +
scale_fill_discrete(labels=c("0"="Not Survived", "1"="Survived")) +
labs(
title = "Survival Charges by class",
x="class",
y="Quantity"
) +
theme_bw() +
theme(
plot.title=element_text(hjust=0, face="daring", dimension=18),
axis.title.x=element_text(face="daring", dimension=13),
axis.title.y=element_text(face="daring", dimension=13),
axis.textual content=element_text(dimension=12)
)
สิ่งที่เห็นชัด ๆ เลยคือ ผู้โดยสารชั้นหนึ่งมีโอกาสรอดชีวิตมากกว่าชั้นอื่น ๆ เยอะมาก ในขณะที่ชั้นสามมีอัตราการไม่รอดชีวิตสูงสุด แปลว่าชั้นโดยสารมีผลต่อโอกาสรอดชีวิต การให้ความสำคัญกับผู้โดยสารชั้นหนึ่งในสถานการณ์วิกฤตเลยช่วยเพิ่มโอกาสรอดชีวิตได้เยอะ
แต่แค่นี้คงยังไม่พอ ผมลองรวม Class และ Intercourse เข้าด้วยกัน กราฟที่ได้ออกมาทำให้เห็นแนวโน้มได้ชัดเจนขึ้น
# คำนวณเปอร์เซ็นต์สำหรับแต่ละกลุ่ม Pclass และ Intercourse
titanic_per_Pclass <- titanic %>%
filter(!is.na(Survived)) %>%
depend(Intercourse, Pclass, Survived) %>%
group_by(Pclass, Intercourse) %>%
mutate(proportion = n / sum(n) * 100)
ggplot(titanic_per_Pclass, aes(x = Pclass, y = proportion, fill = Survived, label = paste0(spherical(proportion, 1), "%"))) +
geom_bar(stat = "identification", alpha = 0.9, colour = "black", width = 0.6, place = position_dodge(width = 0.6)) +
geom_text(place = position_dodge(width = 1), vjust = -0.5, dimension = 4, colour = "black") +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
title = "Survival Charges by Class and Intercourse",
x = "Class",
y = "Proportion"
) +
facet_grid(. ~ Intercourse) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
) +
coord_cartesian(ylim = c(0, 100))
จากกราฟจะเห็นแนวโน้มอย่างชัดเจนเลยว่า เพศและชั้นโดยสารมีบทบาทสำคัญต่ออัตราการรอดชีวิตของผู้โดยสารบนเรือไททานิค แนวโน้มแสดงให้เห็นว่าผู้หญิงและผู้โดยสารในชั้นหนึ่งมีโอกาสรอดชีวิตสูงกว่าผู้ชายและผู้โดยสารในชั้นสามอย่างชัดเจน ทั้งนี้อาจเป็นเพราะการช่วยเหลือผู้หญิงและเด็กก่อน และการให้ความสำคัญกับผู้โดยสารชั้นสูงในสถานการณ์ฉุกเฉิน
Embarked
ggplot(titanic[!is.na(titanic$Survived),], aes(x = Embarked, fill = Survived)) +
geom_bar(stat = "depend", alpha = 0.9, colour = "black", width = 0.6, place = position_stack()) +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
x = "Embarked",
y = "Depend"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0.5, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
titanic_per_embar <- titanic %>%
filter(!is.na(Survived)) %>%
group_by(Embarked, Survived) %>%
summarise(depend = n(), .teams = 'drop') %>%
group_by(Embarked) %>%
mutate(proportion = depend / sum(depend) * 100)str(titanic_per_embar)
ggplot(titanic_per_embar, aes(x = Embarked, y = depend, fill = Survived, label = paste0(spherical(proportion, 1), "%"))) +
geom_bar(stat = "identification", alpha = 0.9, colour = "black", width = 0.6, place = position_fill()) +
geom_text(place = position_fill(vjust = 0.5), dimension = 5, colour = "white") +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
x = "Embarked",
y = "Proportion"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0.5, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
จากกราฟนี้เราจะเห็นได้ว่าผู้โดยสารที่ขึ้นเรือจากท่า S (Southampton) มีจำนวนมากที่สุด และในกลุ่มนี้มีจำนวนผู้เสียชีวิตสูงอย่างชัดเจน ขณะที่ผู้โดยสารจากท่า C (Cherbourg) มีอัตราการรอดชีวิตและไม่รอดชีวิตใกล้เคียงกัน ส่วนท่า Q (Queenstown) มีจำนวนผู้โดยสารน้อยที่สุดและมีอัตราการรอดชีวิตพอๆกัน
ตอนเกิดเหตุทุนคนก็อยู่บนเรือเหมือนกัน คำถามคือ ทำไมการขึ้นเรือจากแต่ละท่าถึงทำให้มีโอกาสรอดชีวิตต่างกันได้?
ggplot(titanic[!is.na(titanic$Survived),], aes(x = Pclass, fill = as.issue(Survived))) +
geom_bar(stat = "depend", alpha = 0.9, colour = "black", width = 0.6, place = position_stack()) +
scale_fill_discrete(labels = c("0" = "Not Survived", "1" = "Survived")) +
labs(
title = "Survival Charges by Embarked and sophistication",
x = "Embarked",
y = "Depend"
) +
theme_bw() +
facet_grid(. ~ Embarked) +
theme(
plot.title = element_text(hjust = 0.5, face = "daring", dimension = 18),
axis.title.x = element_text(face = "daring", dimension = 13),
axis.title.y = element_text(face = "daring", dimension = 13),
axis.textual content = element_text(dimension = 12)
)
สิ่งที่ทำให้การขึ้นเรือต่างที่มีนัยสำคัญการรอดชีวิตคือ Class ครับ ผู้โดยสารที่มาจากท่า Cherbourg ส่วนใหญ่เป็นผู้โดยสารชั้นหนึ่งและชั้นสาม โดยผู้โดยสารชั้นหนึ่งมีโอกาสรอดชีวิตสูงกว่าท่าที่อื่น เลยทำให้อัตราการรอดชีวิตโดยรวมสูง สลับกับท่า Southampton มีจำนวนผู้โดยสารชั้นสามสูงมาก จึงทำให้อัตราการรอดชีวิตโดยรวมต่ำว่าท่าอื่นๆ