系統參數設定
## [1] "zh_TW.UTF-8/zh_TW.UTF-8/zh_TW.UTF-8/C/zh_TW.UTF-8/zh_TW.UTF-8"
安裝需要的packages
# echo = T,results = 'hide'
packages = c("dplyr", "tidytext", "stringr", "wordcloud2", "ggplot2",'readr','data.table','reshape2','wordcloud','tidyr','scales')
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)讀進library
library(dplyr)
library(stringr)
library(tidytext)
library(wordcloud2)
library(data.table)
library(ggplot2)
library(reshape2)
library(wordcloud)
library(tidyr)
library(readr)
library(scales)
require(jiebaR)
library(lubridate)這次我們以最近發生的蛋塔之亂事件,分析ptt上網友的相關討論。本次主要針對以下方向分析:
1.在發生蛋塔之亂當下鄉民情緒為何(正面or負面)? 2.蝦皮總共回應兩次補償,第一次是發放一張折扣券,第二次是補償得以錯標價格購入同等數量蛋塔,兩次補償是否有造成話題,以及情緒正負? 3.事件始(3/18)至事件末(3/22左右)情緒分布,以及整起事件對蝦皮是造成正面影響或是負面影響,是否有由負轉正或由正轉負的改變?
ptt有些文章有「蛋塔」關鍵字,但和主題不相關,加上那時候正好爆發鮭魚之亂,常常鮭魚和蛋塔一起出現,篩選文章必須要有「蝦皮」和其他關鍵字。
# 把文章和留言讀進來
MetaData = fread('../data/egg_articleMetaData.csv',encoding = 'UTF-8')
Reviews = fread('../data/egg_articleReviews.csv',encoding = 'UTF-8')
MetaData = MetaData %>% mutate(date_total=paste(artDate,artTime,sep = " "))#由於資料是格林威治時間,和台灣時間相差八小時,時間必須+8
MetaData$date_total = as.POSIXct(MetaData$date_total,format = "%Y/%m/%d %H:%M:%S")
MetaData$date_total = MetaData$date_total + 28800
MetaData$artDate=as_date(MetaData$date_total)
MetaData$artDate= MetaData$artDate%>% as.Date("%Y/%m/%d")
Reviews = Reviews %>% mutate(date_total=paste(artDate,artTime,sep = " "))
Reviews$date_total = as.POSIXct(Reviews$date_total,format = "%Y/%m/%d %H:%M:%S")
Reviews$date_total = Reviews$date_total + 28800
Reviews$artDate=as_date(Reviews$date_total)
Reviews$artDate= Reviews$artDate%>% as.Date("%Y/%m/%d")
# 再篩一次文章 826 篇
keywords = c('蛋塔','蝦皮','肯德基')
toMatch = paste(keywords,collapse="|")
MetaData = with(MetaData, MetaData[grepl(toMatch,sentence)|grepl(toMatch,artTitle),])
# 挑選文章對應的留言
Reviews = left_join(MetaData, Reviews[,c("artUrl", "cmtContent")], by = "artUrl")(1). 文章斷詞
設定斷詞引擎
# 加入自定義的字典
jieba_tokenizer <- worker(user="../dict/user_dict.txt", stop_word = "../dict/stop_words.txt")
# 設定斷詞function
customized_tokenizer <- function(t) {
lapply(t, function(x) {
tokens <- segment(x, jieba_tokenizer)
return(tokens)
})
}# 把文章和留言的斷詞結果併在一起
MToken <- MetaData %>% unnest_tokens(word, sentence, token=customized_tokenizer)
RToken <- Reviews %>% unnest_tokens(word, cmtContent, token=customized_tokenizer)
# 把資料併在一起
data <- rbind(MToken[,c("artDate","artUrl", "word")],RToken[,c("artDate","artUrl", "word")]) (2). 資料基本清理
# 格式化日期欄位
data$artDate= data$artDate %>% as.Date("%Y/%m/%d")
# 過濾特殊字元
data_select = data %>%
filter(!grepl('[[:punct:]]',word)) %>% # 去標點符號
filter(!grepl("['^0-9a-z']",word)) %>% # 去英文、數字
filter(nchar(.$word)>1)
# 算每天不同字的詞頻
# word_count:artDate,word,count
word_count <- data_select %>%
select(artDate,word) %>%
group_by(artDate,word) %>%
summarise(count=n()) %>% # 算字詞單篇總數用summarise
filter(count>3) %>% # 過濾出現太少次的字
arrange(desc(count))## `summarise()` has grouped output by 'artDate'. You can override using the `.groups` argument.
## # A tibble: 1,344 x 3
## # Groups: artDate [5]
## artDate word count
## <date> <chr> <int>
## 1 2021-03-18 蝦皮 580
## 2 2021-03-18 蛋塔 292
## 3 2021-03-18 肯德基 176
## 4 2021-03-20 蝦皮 170
## 5 2021-03-18 衛生紙 153
## 6 2021-03-18 台灣 131
## 7 2021-03-18 取消 130
## 8 2021-03-19 蝦皮 124
## 9 2021-03-18 收到 122
## 10 2021-03-18 訂單 102
## # … with 1,334 more rows
全名Linguistic Inquiry and Word Counts,由心理學家Pennebaker於2001出版 分為正向情緒與負向情緒
讀檔,字詞間以“,”將字分隔
P <- read_file("../dict/liwc/positive.txt") # 正向字典txt檔
N <- read_file("../dict/liwc/negative.txt") # 負向字典txt檔
#字典txt檔讀進來是一整個字串
typeof(P)## [1] "character"
分割字詞,並將兩個情緒字典併在一起
# 將字串依,分割
# strsplit回傳list , 我們取出list中的第一個元素
P = strsplit(P, ",")[[1]]
N = strsplit(N, ",")[[1]]
# 建立dataframe 有兩個欄位word,sentiments,word欄位內容是字典向量
P = data.frame(word = P, sentiment = "positive") #664
N = data.frame(word = N, sentiment = "negative") #1047
# 把兩個字典拼在一起
LIWC = rbind(P, N)
# 檢視字典
head(LIWC)## word sentiment
## 1 一流 positive
## 2 下定決心 positive
## 3 不拘小節 positive
## 4 不費力 positive
## 5 不錯 positive
## 6 主動 positive
在畫出情緒之前,先看看每天的發文情形,在3/18事件發生後才有較多的討論。
MetaData$artDate= MetaData$artDate %>% as.Date("%Y/%m/%d")
MetaData %>%
group_by(artDate) %>%
summarise(count = n()) %>%
ggplot()+
geom_line(aes(x=artDate,y=count))+
scale_x_date(labels = date_format("%m/%d"))找出文集中,對於LIWC字典是positive和negative的字
算出每天情緒總和(sentiment_count)
# sentiment_count:artDate,sentiment,count
sentiment_count = data_select %>%
select(artDate,word) %>%
inner_join(LIWC) %>%
group_by(artDate,sentiment) %>%
summarise(count=n()) ## Joining, by = "word"
## `summarise()` has grouped output by 'artDate'. You can override using the `.groups` argument.
畫出每天的情緒總分數,可以看到事件發生時,正負情緒都很高昂,事件慢慢落幕,負面情緒高於正片情緒。
## [1] "2021-03-18" "2021-03-22"
sentiment_count %>%
ggplot()+
geom_line(aes(x=artDate,y=count,colour=sentiment))+
scale_x_date(labels = date_format("%m/%d"),
limits = as.Date(c('2021-03-18','2021-03-22'))
)+
# 加上標示日期的線
geom_vline(aes(xintercept = as.numeric(artDate[which(sentiment_count$artDate == as.Date('2021-03-18'))
[1]])),colour = "red") 將情緒分數標準化後再畫一次圖,可以發現雖然正負面情緒有波動,在3/21過後,負面情緒才慢慢下降。
sentiment_count %>%
# 標準化的部分
group_by(artDate) %>%
mutate(ratio = count/sum(count)) %>%
# 畫圖的部分
ggplot()+
geom_line(aes(x=artDate,y=ratio,colour=sentiment))+
scale_x_date(labels = date_format("%m/%d"),
limits = as.Date(c('2021-03-18','2021-03-22'))
)+
# 加上標示日期的線
geom_vline(aes(xintercept = as.numeric(artDate[which(sentiment_count$artDate == as.Date('2021-03-18'))
[1]])),colour = "red")我們挑出幾個情緒高點的日期 觀察每日情緒分數,約從18號開始議題被大量討論,18達到議題高峰,之後就慢慢下降。
# 查看每天的情緒分數排名
sentiment_count %>%
select(count,artDate) %>%
group_by(artDate) %>%
summarise(sum = sum(count)) %>%
arrange(desc(sum))## # A tibble: 5 x 2
## artDate sum
## <date> <int>
## 1 2021-03-18 1156
## 2 2021-03-20 354
## 3 2021-03-19 278
## 4 2021-03-21 14
## 5 2021-03-22 10
挑出有興趣的日期,畫出文字雲看看都在討論甚麼主題。
先從2021-03-18的情緒高點看起,呼應上面負面的情緒分析,……
# 畫出文字雲
word_count %>%
filter(!(word %in% c("蛋塔"))) %>%
filter(artDate == as.Date('2021-03-18')) %>%
select(word,count) %>%
group_by(word) %>%
summarise(count = sum(count)) %>%
arrange(desc(count)) %>%
filter(count>25) %>% # 過濾出現太少次的字
wordcloud2()## Adding missing grouping variables: `artDate`
看前後兩天的討論情況
2021-03-19的文字雲,往後看19的文字雲,發現此時負面批評詞彙依舊很多,且出現「律師」、「票券」、「交易」等相關詞彙,代表已經牽涉到法律問題。
算出所有字詞的詞頻(sentiment_sum),找出情緒代表字
# sentiment_sum:word,sentiment,sum
sentiment_sum <-
word_count %>%
inner_join(LIWC) %>%
group_by(word,sentiment) %>%
summarise(
sum = sum(count)
) %>%
arrange(desc(sum)) %>%
data.frame() ## Joining, by = "word"
## `summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
sentiment_sum %>%
top_n(30,wt = sum) %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
theme(text=element_text(size=14,family = 'STHeitiTC-Light'))+
coord_flip()另外一種呈現方式
par(family=("Heiti TC Light"))
sentiment_sum %>%
acast(word ~ sentiment, value.var = "sum", fill = 0) %>%
comparison.cloud(
colors = c("salmon", "#72bcd4"), # positive negative
max.words = 50)另外,也可以依據不同日期觀察情緒代表字的變化
sentiment_sum_select <-
word_count %>%
filter(artDate == as.Date('2021-03-18')) %>%
inner_join(LIWC) %>%
group_by(word,sentiment) %>%
summarise(
sum = sum(count)
) %>%
arrange(desc(sum)) %>%
data.frame() ## Joining, by = "word"
## `summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
sentiment_sum_select %>%
top_n(30,wt = sum) %>%
ungroup() %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment 0317",
x = NULL) +
theme(text=element_text(size=14,family = 'STHeitiTC-Light'))+
coord_flip()之前的情緒分析大部分是全部的詞彙加總,接下來將正負面情緒的文章分開,看看能不能發現一些新的東西。接下來歸類文章,將每一篇文章正負面情緒的分數算出來,然後大概分類文章屬於正面還是負面。
# 依據情緒值的正負比例歸類文章
article_type =
data_select %>%
inner_join(LIWC) %>%
group_by(artUrl,sentiment) %>%
summarise(count=n()) %>%
spread(sentiment,count,fill = 0) %>% #把正負面情緒展開,缺值補0
mutate(type = case_when(positive > negative ~ "positive",
TRUE ~ "negative")) %>%
data.frame() ## Joining, by = "word"
## `summarise()` has grouped output by 'artUrl'. You can override using the `.groups` argument.
## # A tibble: 2 x 2
## type count
## * <chr> <int>
## 1 negative 95
## 2 positive 81
事件爆發時,正負面情緒數量差不多,在3/19號之後,負面文章增加較多。
#
article_type_date = left_join(article_type[,c("artUrl", "type")], MetaData[,c("artUrl", "artDate")], by = "artUrl")
article_type_date %>%
group_by(artDate,type) %>%
summarise(count = n()) %>%
ggplot(aes(x = artDate, y = count, fill = type)) +
geom_bar(stat = "identity", position = "dodge")+
scale_x_date(labels = date_format("%m/%d"),
limits = as.Date(c('2021-03-17','2021-03-22'))
)## `summarise()` has grouped output by 'artDate'. You can override using the `.groups` argument.
## Warning: Removed 1 rows containing missing values (geom_bar).
把正面和負面的文章挑出來,並和斷詞結果合併。
# negative_article:artUrl,word
negative_article <-
article_type %>%
filter(type=="negative")%>%
select(artUrl) %>%
left_join(data_select[,c("artUrl", "word")], by = "artUrl")
# positive_article:artUrl,word
positive_article <-
article_type %>%
filter(type=="positive")%>%
select(artUrl) %>%
left_join(data_select[,c("artUrl", "word")], by = "artUrl")畫出正負面文章情緒貢獻度較高的關鍵字
# 負面情緒關鍵字貢獻圖
negative_article %>%
inner_join(LIWC) %>%
group_by(word,sentiment) %>%
summarise(
sum = n()
)%>%
arrange(desc(sum)) %>%
data.frame() %>%
top_n(30,wt = sum) %>%
ungroup() %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to negative sentiment",
x = NULL) +
theme(text=element_text(size=14,family = 'STHeitiTC-Light'))+
coord_flip()## Joining, by = "word"
## `summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
# 正面情緒關鍵字貢獻圖
positive_article %>%
inner_join(LIWC) %>%
group_by(word,sentiment) %>%
summarise(
sum = n()
)%>%
arrange(desc(sum)) %>%
data.frame() %>%
top_n(30,wt = sum) %>%
ungroup() %>%
mutate(word = reorder(word, sum)) %>%
ggplot(aes(word, sum, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to positive sentiment",
x = NULL) +
theme(text=element_text(size=14,family = 'STHeitiTC-Light'))+
coord_flip()## Joining, by = "word"
## `summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
從正負面情緒圖觀察發現,正面情緒關鍵字主要有「成功」、「優惠」、「謝謝」,代表搶到優惠券心裡很開心,負面情緒較高的文章比較常出現「錯誤」、「垃圾」、「不爽」等可能是沒有搶到優惠券的酸葡萄心理。
最後總結一下之前提出的問題:
1.在發生蛋塔之亂當下鄉民情緒為何(正面or負面)?
蛋塔之亂始於3/18清晨,終於3/22,有鄉民於3/18深夜發文指出蛋塔標錯價因此許多人跟著下單,但在3/18凌晨兩點左右蝦皮便下架該商品,並在3/18早上宣布以發放折扣券欲以慈結束此事件,因此該天(3/18)之負面情緒達到最高且同時為討論度最高的一天,並於3/22之後熱度近乎歸零。
2.蝦皮總共回應兩次補償,第一次是發放一張折扣券,第二次是補償得以錯標價格購入同等數量蛋塔,兩次補償是否有造成話題,以及情緒正負?
蝦皮第一次補償的消息發布於3/18,第二次補償之消息發於3/19,由正負情緒發文折線圖得知,第一次因緊跟著話題熱度最高的一天,因此有造成話題,但第二次補償並未提高話題討論。
而兩天的情緒皆為負面大於正面。
可能顯示消費者對於第一次蝦皮處理方式的補償不是很滿意,因此負面情緒較多;而雖然第二次補償時明顯有誠意許多,但是話題熱度已經消退,並沒有對該次補償有較熱烈的討論。
3.事件始(3/18)至事件末(3/22左右)情緒分布,以及整起事件對蝦皮是造成正面影響或是負面影響,是否有由負轉正或由正轉負的改變?
依據「負面情緒之文章數」以及「正負情緒比例折線圖」等圖表,我們可以得知從事件發生到結束,消費者對於蝦皮之情緒感受多為負面,且負面情緒關鍵字中出現「可惡」之評價,因此可以確定此事件對於蝦皮有一定的負面影響。 而通過情緒比例折線圖也可得知:整件事情除卻一開始消費者買到標錯價的商品時有正面的情緒,其餘一律為負面情緒,即便蝦皮提出兩種解決方案,皆無明顯的情緒逆轉的狀況發生,因此在蛋塔之亂中只有「情緒由正轉負」而無「情緒由負轉正」之現象發生。
4.情緒分數、情緒代表字?
蛋塔之亂第一天(3/18)的正面情緒約略大於負面,但在之後便維持負面大於正面直到事情結束。
正面情緒代表字為「成功」、「嘻嘻」、「分享」等,多為感謝鄉民提供消息的用字。
負面情緒代表字為「八卦」、「可惡」、「垃圾」等,由此可知消費者對於蝦皮的處理方式感到不滿。😡