BNK48 2nd Generation Analysis Using R Tuber
สวัสดีคั้บทุกคน หลังจากที่ผมได้ทำ Analysis Page Cup E (อ่านบทความได้ ที่นี่ 👈) โดยใช้ Rfacebook
ไปในบทความที่แล้ว R ยังสามารถดึง API จาก YouTube ได้อีกด้วน โดยใช้ Package ที่ชื่อว่า tuber
โดยเป้าหมายของผมในครั้งนี้คือการดึงข้อมูลมาจาก BNK48 Channel เพื่อตอบคำถามเหล่านี้ :
- ผู้สมัครใน 2nd Generation คนไหนที่เข้ามา Audition บน Channel BNK48 มีผู้ชม (Views) มากที่สุด
- ผู้สมัครใน 2nd Generation คนไหนที่เข้ามา Audition บน Channel BNK48 มีผู้ชมกด Likes ให้มากที่สุด
- ผู้สมัครคนไหนที่มี Engagement Rate (Likes) สูงที่สุด หรืออัตราส่วนจำนวน Likes ต่อยอด Views ของวีดีโอออดิชั่น
สำหรับช่วงเวลาของ Data ที่ดึงมานั้นจะถึงแค่ช่วง 21 มี.ค. 2561 โดย Step การทำ Analysis นั้นคล้ายๆ กับของ Cup E ครับ : ดึงข้อมูล > Clean Data > Visualise
ถ้าพร้อมแล้วเรามาหาคำตอบกันเลยครับ 👼
#ขั้นตอนการดึง API จาก YouTube Channel
พระเอกของเราในการใช้ดึงในที่นี้คือ tuber
โดยมีขั้นตอนดังนี้ครับ :
เปิด RStudio ขึ้นมาแล้วใส่ Script ตามนี้ครับ
install.packages("tuber")
#ลง Package
library(tuber)
#เรียกใช้ Package
ก่อนที่จะดึงเราต้องมี Token (OAuth : Open Authentication)ในการดึงก่อนครับคล้ายๆ กับตอนที่เราไปดึง API มาจาก Facebook โดยเราสามารถขอ Token นี้ได้ที่ลิ้งค์ https://console.developers.google.com/apis (ต้อง Log in โดยใช gmail ก่อนนะครับ) หลังจากเข้ามาแล้วตรงเราจะมาอยู่ตรงหน้า Dashboard ถ้าโล่งไม่ต้องตกใจครับ เพราะเรายังไม่เคยเรียกใช้ API ของ Google นั่นเอง หน้าตาตามรูปด้านล่างเลยครับ
หลังจากได้ตัว ID กับ Secret มาแล้วให้เรากลับไปที่ RStudio พร้อมรัน Script เพื่อยืนยัน Token ตามนี้ครับ :
yt_oauth(app id = "your client ID", app_secret = "your client secret")
หลังจากกด Run ทางฝั่ง Console จะขึ้นข้อความด้านล่างนี้ครับ :
Use a local file (‘.httr-oauth’), to cache OAuth access credentials between R sessions?1: Yes
2: NoSelection: Yes
ให้เราเลือกพิมพ์ Yes (ในฝั่ง Console) แล้วเคาะ Enter ครับ หลังจากนั้นเราจะถูกนำมายัง Browser เพื่อขออนุญาตในการเข้าถึงดังรูปด้านล่างครับ :
#ดึง Data จาก Channel ที่ต้องการ
เราทำการเลือกเป้าหมาย Channel ที่จะดึงโดยใช้คำสั่งนี้ครับ :
bnk48_ytb <- get_all_channel_video_stats(channel_id = "UClIsaGq7vBEW00ASqwQyzPw")
ผมทำการ Store Data ของ Channel BNK48 ไว้ใน Object ที่ชื่อว่า bnk48_ytb ครับ
สำหรับ Id ของ channel สามารถ Copy มาได้จาก Id ที่ต่อท้าย Path /channel ครับ เช่น https://www.youtube.com/channel/UClIsaGq7vBEW00ASqwQyzPw
เมื่อลอง Check Class ของ Object ดูจะพบว่ามันถูกจัดมาให้เป็น Table เรียบร้อย
> class(bnk48_ytb)
[1] "data.frame"
หลังจากนั้นเมื่อใช้คำสั่ง glimpse(bnk48_ytb)
จะพบว่าเรามี Data ในมือดังนี้
Observations: 318
Variables: 9
$ id <fctr> -9yGTFlIkzY, -HLEvHg5a8w, -J4...
$ title <fctr> BNK48 Senpai ep.05 (Part 4), ...
$ publication_date <chr> "2017-03-10", "2017-07-24", "2...
$ viewCount <chr> "187164", "88261", "94490", "5...
$ likeCount <chr> "823", "491", "830", "1845", "...
$ dislikeCount <chr> "21", "10", "12", "28", "75", ...
$ favoriteCount <fctr> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ commentCount <chr> "77", "41", "174", "496", "676...
$ url <chr> "https://www.youtube.com/watch...
# Clean Data (ขนลู๊คคเลยเวลาเจอคำนี้ทีไร 😱)
Column ที่สามารถบ่งบอกได้ว่าวีดีโอไหนคือ Audition ของน้องๆ ที่สมัคร 2nd Generation เข้ามาก็คือ “title” ครับ โดยเราสามารถดูข้อมูลของ Column คร่าวๆ ได้ด้วยคำสั่งนี้ (อย่าลืมลง Package ใหญ่สำหรับทำ Data tidyverse
กันก่อนนะครับ) :
install.packages("tidyverse")
library(tidyverse) # For data cleaning & manipulation> bnk48_ytb %>%
+ select(title) %>%
+ head(10) # Run the Code# Results
title
1 BNK48 Senpai ep.05 (Part 4)
2 BNK48 Show EP03 Break03
3 BNK48 Senpai Special (Part 3)
4 [ BNK48 2nd Generation Candidate ] Entry #77 Alice / BNK48
5 [ BNK48 2nd Generation Candidate ] Entry #02 Jenny / BNK48
6 BNK48 Show EP04 Break04
7 เพื่อนร่วมทาง The Journey Ep.05 Part 3
8 [ BNK48 2nd Generation Candidate ] Entry #74 Mew / BNK48
9 29 Seconds of 29 members #27 : Pupe
10 [ BNK48 2nd Generation Candidate ] Entry #63 Next / BNK48
คราวนี้เราลองมาเช็คกันว่ามี Missing Values (NA) หรือแป่ว?
> any(is.na(bnk48_ytb))
[1] FALSE # FALSE = NO NA
ถ้าขึ้น FALSE หมายความว่า Dataset ของเราไม่มี Missing Value นั่นเองครับ เย้เฮ!! 😎
จากการดู Data ใน Column คร่าวๆ จะสังเกตว่า Identifier ที่บอกว่าวีดีโอนั้นเป็น Audition จะมี Pattern ที่มี Keyword คำว่า BNK48 2nd Generation Candidate ดังนั้นเราจึงเลือก Filter จากคำนี้เลยครับด้วยคำสั่งเน้ :
secondGen <-
bnk48_ytb %>% filter(str_detect(title,"BNK48 2nd Generation Candidate"))
จะสังเกตเห็นว่าหลังจาก Filter ผมได้เอา Data ไปฝากยัง Object ใหม่ที่ชื่อว่า secondGen
หลังจากนั้นเราจะทำการเลือกแค่ Columns ที่เราจะใช้นั่นคือ title, viewCount และ likeCount
secondGen <- secondGen %>% select(title, viewCount, likeCount)
หลังจากนั้นเราต้องการที่จะ Extract ชื่อผู้สมัครออกจาก Title ยาวๆ ด้วยคำสั่งนี้ :
# separate candidate name
secondGen2 <- secondGen %>%
separate(title, into = c("template","candidateName"), sep="#") %>%
separate(candidateName, into= c("candidateName","bnk48"), sep="\\/") %>%
separate(candidateName, into= c("entry","candidateName"), sep=" ") %>%
select(-template, -bnk48)
หลังจาก Extract แล้วผมได้ Assign Data ไปยัง Object ที่ชื่อว่า secondGen2
คราวนี้มาลองดูกันว่าเราได้ Data ที่ Clean หรือยัง
> secondGen2 %>%
+ select(entry, candidateName, viewCount, likeCount) %>%
+ head(10)
entry candidateName viewCount likeCount
1 77 Alice 58012 1845
2 02 Jenny 103851 1967
3 74 Mew 67087 1812
4 63 Next 15512 303
5 01 Pink 47528 332
6 31 Deenee 19006 251
7 08 Amy 27291 572
8 11 Ying 19531 255
9 41 Dear 14544 251
10 45 Matilda 16993 364
แต่ในตอนแรก Columns viewCount
และ likeCount
ดันทะลึ่งมาเป็น character ซึ่งมันจะใช้วิเคราะห์ไม่ได้ ดังนั้นเราต้องเปลี่ยน Class ให้มัน
secondGen2$viewCount <- parse_integer(secondGen2$viewCount)
secondGen2$likeCount <- parse_integer(secondGen2$likeCount)
หลังจากทำการ Parsing แล้วให้ลองเช็ค Class ดูอีกครั้งนึง
> class(secondGen2$viewCount)
[1] "integer"
> class(secondGen2$likeCount)
[1] "integer"
>
เย่เฮ้ เส็ดไปอีกหนึ่งขั้นตอน 😅
#Visualization
#Candidate ที่มียอด Views มากที่สุด (Data ถึงวันที่ 21 มี.ค. 2561)
Code Template
# Candidates top views
secondGen2 %>%
group_by(entry,candidateName) %>%
summarise(sum_viewCount=sum(viewCount)) %>%
arrange(desc(sum_viewCount)) %>%
head(20) %>%
ggplot(.,mapping=aes(x=reorder(candidateName,+sum_viewCount),
y=sum_viewCount))+
geom_col(fill="pink1",col="salmon",alpha=0.5)+
coord_flip()+
theme_minimal()+
labs(x="candidateName",y="views")
Visualize ออกมาได้เป็น …
Top Views : น้อง Millie
#Candidate ที่มียอด Likes มากที่สุด (Data ถึงวันที่ 21 มี.ค. 2561)
Code Template
# Candidates with top likes 20
secondGen2 %>%
group_by(entry,candidateName) %>%
summarise(sum_likeCount=sum(likeCount)) %>%
arrange(desc(sum_likeCount)) %>%
head(20) %>%
ggplot(.,mapping=aes(x=reorder(candidateName,+sum_likeCount),
y=sum_likeCount))+
geom_col(fill="pink1",col="salmon",alpha=0.5)+
coord_flip()+
theme_minimal()+
labs(x="candidateName",y="likes")
และคนที่ได้ Likes มากที่สุดคือ …
Top Likes : น้อง Natherine
#Candidate ที่ได้ Engagement Rate ดีที่สุด
ตอนแรกผมก็กะทำตาราง mutate ออกมาคำนวณ Engagement Rate โดยเอา likeCount / viewCount แต่อยู่ดีๆ ก็นึกสงสัยขึ้นมาว่า เอ ยอด View นี่มันสัมพันธ์กับยอด Likes รึเปล่านะ 😕 จากจุดนี้ผมเลยพลิกแพลงการหาคำตอบโดยใช้ Pearson Correlation ก่อน หลังจากนั้นใช้ Scatter Plot เพื่อดู Trend รันโดย Code ชุดด้านล่างครับ :
> format(cor.test(secondGen2$likeCount,
+ secondGen2$viewCount), scientific = F)
statistic
"16.17102"
parameter
"92"
p.value
"0.0000000000000000000000000001232934"
estimate
"0.8600853"
จะเห็นว่าค่าความสัมพันธ์ estimate
สูงถึง 0.86 และแน่นอนผลที่ได้ก็ Significant โดยได้ p-value < 0.05 คราวนี้มาลองดูบ้างว่าเมื่อพล็อตออกมาจะได้หน้าตา Chart อย่างไร
Scatter Plot Code Template
# correlation views & likes
secondGen2 %>%
group_by(entry, candidateName) %>%
summarise(likes = likeCount,
views = viewCount) %>%
ggplot(., aes(views, likes)) +
geom_point(size = 2,col = "pink1", alpha = .8) +
geom_point(data = nat, aes(viewCount, likeCount), col = "salmon",
size = 5) +
geom_smooth(method = "loess", se = F, col = "salmon") +
theme_minimal()
ได้ออกมาเป็น …
สังเกตจุดใหญ่ๆ ไหมครับจะเห็นว่ามีผู้สมัครคนนึงเป็น Outlier อย่างมาก โดย Views ของน้องอยู่ในช่วง 4 หมื่นปลาย แต่กลายเป็นว่าได้ Top Likes ซึ่งน้องคนนั้นก็คือ …
น้อง Natherine นั่นเองค้าบบบ 😘
เมื่อพล๊อตออกมาได้แบบนี้ ผมเลยไม่ต้องหา Engagement Rate เลยครับ 555 ส่วนผมเองยังไม่ได้เป็นโอชิใครเปนพิเศษน๊าา อิอิ
#สรุป
จะเห็นได้ว่าทั้งบทความ Cup E และ BNK48 Code ในการรันเพื่อ Clean Data นั้นยาวกว่าตอนวิเคราะห์อีกครับ ซึ่งผมอยากจะเน้นย้ำว่าก่อนที่จะวิเคราะห์ได้นั้นคุณต้องยอมจำนนให้กับขั้นตอนการ Clean ซะดีๆ เพราะถ้ามันไม่ Tidy มันอาจจะส่งผลลัพธ์ที่ Messy เป็นอย่างมากให้กับคุณทีหลังได้นะครับผ๊ม 😅
Happy families are all alike; every unhappy family is unhappy in its own way. — Leo Tolstoy
— — — — — — — — — — — — — — — — — — — — — — — — — -
Tidy datasets are all alike, but every messy dataset is messy in its own way. — Hadley Wickham (โคดจริง 😢)
ขอบคุณที่ติดตามนะค้าบบ ~~