BNK48 2nd Generation Analysis Using R Tuber

Chaiyasit Bunnag
6 min readMar 27, 2018

--

สวัสดีคั้บทุกคน หลังจากที่ผมได้ทำ Analysis Page Cup E (อ่านบทความได้ ที่นี่ 👈) โดยใช้ Rfacebookไปในบทความที่แล้ว R ยังสามารถดึง API จาก YouTube ได้อีกด้วน โดยใช้ Package ที่ชื่อว่า tuber โดยเป้าหมายของผมในครั้งนี้คือการดึงข้อมูลมาจาก BNK48 Channel เพื่อตอบคำถามเหล่านี้ :

  1. ผู้สมัครใน 2nd Generation คนไหนที่เข้ามา Audition บน Channel BNK48 มีผู้ชม (Views) มากที่สุด
  2. ผู้สมัครใน 2nd Generation คนไหนที่เข้ามา Audition บน Channel BNK48 มีผู้ชมกด Likes ให้มากที่สุด
  3. ผู้สมัครคนไหนที่มี 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 นั่นเอง หน้าตาตามรูปด้านล่างเลยครับ

ให้เรากด Enable APIS AND SERVICES
เข้าไปเลือก API ที่ชื่อว่า YouTube Data API v3 และ YouTube Analytics API เพื่อกด Enable ครับ (กดได้ครั้งละตัวนะ ดังนั้นเราต้องเข้ามากดสองครั้ง)
พอเข้ามาแล้วให้กด Enable ได้เรยย
หลังจากกด Enable เรียบร้อยให้เราเลือก “Credentials” ตรง Panel ซ้ายมือ โดยให้เลือกขอเป็น OAuth client ID เพื่อขอ API Key และ API Secret
ผมเลือก App Type เป็น Web Application > ตั้งชื่อ และตรง Origin URI ให้เราใส่ http://localhost1410 เพื่อให้มันชี้มาที่ host เราครับ
ได้ App ID กับ App Secret มาแร้ววว

หลังจากได้ตัว 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: No
Selection: 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

นัลล๊ากกก พร้อมว๊าปแจ้ : https://www.youtube.com/watch?v=AwKyqyM-0j8&t=11s

#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

ยิ้มทีละลายเลยค้าบบ พร้อมเปิดว้าป : https://www.youtube.com/watch?v=jlS_Ud--J7w&t=46s

#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 (โคดจริง 😢)

ขอบคุณที่ติดตามนะค้าบบ ~~

--

--

Chaiyasit Bunnag

a Data Analyst with no related degrees T^T; an Analogical Thinker