前處理

載入會用到的 package

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidytext)
library(jiebaR)
## Loading required package: jiebaRD
library(gutenbergr)
library(stringr)
library(wordcloud2)
library(ggplot2)
library(tidyr)
library(scales)

下載三國演義的資料集

# 下載 "三國演義" 書籍,並且將text欄位為空的行給清除,以及將重複的語句清除
three <- gutenberg_download(23950) %>% 
  filter(text!="") %>% 
  distinct(gutenberg_id, text)
## Determining mirror for Project Gutenberg from http://www.gutenberg.org/robot/harvest
## Using mirror http://aleph.gutenberg.org
# 先抓出每一回的章節
three_title <- three[str_detect(three$text, regex("^第.*回.*")), ]

#將text欄位的全部文字合併
three_doc = paste0(three$text,collapse = "") 
#以全形或半形句號斷句
docVector = unlist(strsplit(three_doc ,"[。.?!]"), use.names=FALSE) 
three_doc <- tibble(gutenberg_id = 1:length(docVector), text = docVector)
three_doc$gutenberg_id <- 23950

觀察資料我們可以發現,三國演義中每章皆以“第X回”為標題。
ex.第一回:宴桃園豪傑三結義,斬黃巾英雄首立功

# 根據上方整理出來的規則,我們可以使用正規表示式,將句子區分章節
three_doc <- three_doc %>% 
  mutate(chapter = cumsum(str_detect(three_doc$text, regex("^第.*回.*"))))

對文章做斷詞

jieba_three_tokenizer <- worker(user = "three_kingdoms/three.traditional.dict",
                               stop_word = "three_kingdoms/stop_words.txt")

three_tokenizer <- function(t) {
  lapply(t, function(x) {
    tokens <- segment(x, jieba_three_tokenizer)
    return(tokens)
  })
}

three_tokens <- three_doc %>% unnest_tokens(word, text, token = three_tokenizer)

計算詞頻

結果看起來「玄德」、「孔明」、和「曹操」出現的次數最多。

# 計算詞彙的出現次數,如果詞彙只有一個字則不列入計算
three_tokens_count <- three_tokens %>% 
  filter(nchar(.$word)>1) %>%
  group_by(word) %>% 
  summarise(sum = n()) %>% 
  filter(sum>10) %>%
  arrange(desc(sum))

# 印出最常見的20個詞彙
head(three_tokens_count, 20)
## # A tibble: 20 x 2
##    word    sum
##    <chr> <int>
##  1 玄德   1779
##  2 孔明   1644
##  3 曹操    922
##  4 將軍    724
##  5 卻說    644
##  6 丞相    533
##  7 關公    501
##  8 二人    460
##  9 雲長    430
## 10 不可    425
## 11 荊州    408
## 12 張飛    364
## 13 引兵    361
## 14 呂布    355
## 15 商議    341
## 16 軍士    321
## 17 魏延    321
## 18 主公    319
## 19 大喜    310
## 20 孫權    309

文字雲

three_tokens_count %>%  wordcloud2()

一個小小的發現

在三國裡面主要有擔任過丞相的角色有曹操跟孔明,所以可以去看「丞相」這個詞跟「曹操」以及「孔明」的章節分佈。
結果看起來確實「丞相」的分佈與這兩個人都有一點點吻合,而且可以看出在75回以前的丞相指得是曹操,75回之後的丞相指得是孔明。

a <- three_tokens %>% 
  filter(.$word == "曹操") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "曹操")

b <- three_tokens %>% 
  filter(.$word == "丞相") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "丞相")

c <- three_tokens %>% 
  filter(.$word == "孔明") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "孔明")

bind_rows(a, b, c) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「丞相」v.s.「孔明」v.s.「曹操」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))

主要的三個人物出現的章節分佈

因本文中對同一人有多種稱呼,ex.玄德、劉備,因此為了該名人物的次數是正確的, 此處將不同稱呼但為同一人,皆計算為同一人的出場次數。

tokens_shiuan_de <- three_tokens %>% 
  filter(.$word == "玄德" | .$word == "劉備") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "玄德")

tokens_kung_ming <- three_tokens %>% 
  filter(.$word == "孔明" | .$word == "諸葛亮") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "孔明")

tokens_tsau_tsau<- three_tokens %>% 
  filter(.$word == "曹操" | .$word == "孟德") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "曹操")

tokens_guan_gung<- three_tokens %>% 
  filter(.$word == "關公" | .$word == "雲長") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "關公")

tokens_jang_fei<- three_tokens %>% 
  filter(.$word == "張飛" | .$word == "翼德") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "張飛")
major_name_compare_plot <- 
  bind_rows(tokens_shiuan_de, tokens_kung_ming, tokens_tsau_tsau) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「玄德」v.s.「孔明」v.s.「曹操」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))
major_name_compare_plot

桃園三結義

結果可以發現,關羽跟張飛出場的章回數遠比劉備少很多。如果有關羽或是張飛出場的章回通常都是特別著名的幾個事蹟的描述。
例如,關羽主要的出場回數在25回左右,第25回是「第二十五回:屯土山關公約三事,救白馬曹操解重圍」,第26回是「第二十六回:袁本初敗兵折將,關雲長挂印封金」,第27回是「第二十七回:美髯公千里走單騎,漢壽侯五關斬六將」,這幾章也都在章回標題就提及關羽,很明顯就是針對關羽去描述的幾個章回。

lgj_name_compare_plot <- 
  bind_rows(tokens_shiuan_de, tokens_guan_gung, tokens_jang_fei) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「關公」v.s.「玄德」v.s.「張飛」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))
lgj_name_compare_plot

「劉備」、「孔明」、「關羽」的關係

孔明和關羽算是對劉備而言最重要的兩個人,可以發現當孔明出場時,關羽的部分就會少,關羽出場的時候,孔明的部分也稍微比較少。

special_case_name_compare_plot <- 
  bind_rows(tokens_kung_ming, tokens_shiuan_de, tokens_guan_gung) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「孔明」v.s.「玄德」v.s.「關公」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))
special_case_name_compare_plot

各章節長度,以語句數來計算

ch_sentences_three <- three_doc %>% 
  group_by(chapter) %>% 
  summarise(count = n(), type="sentences")

ch_word_three <- three_tokens %>% 
  group_by(chapter) %>% 
  summarise(count = n(), type="words")

three_length_plot <- 
  bind_rows(ch_sentences_three, ch_word_three) %>% 
  group_by(type)%>%
  ggplot(aes(x = chapter, y=count, fill="type", color=factor(type))) +
  geom_line() + 
  geom_vline(xintercept = 71, col='red', size = 0.2) + 
  ggtitle("各章節的句子和詞彙總數") + 
  xlab("章節") + 
  ylab("數量") + 
  theme(text = element_text(family = "Heiti TC Light"))
three_length_plot

第七十一回用了最多的句子和單字,該回是「占對山黃忠逸待勞,據漢水趙雲寡勝眾」。
這章就是在講定軍山之戰,也就是劉備稱霸成為漢中王前的一役。

章回前後比較

三國時期(西元184年~西元280年)共97年,三國演義前一百零三回述說了前51年,
而後的46年只短短的用十七回收場,比較前後的用詞有何差異。
計算 前一百零三回 和 後十七回 的詞彙在全文中出現比率的差異。

frequency <- three_tokens %>% mutate(part = ifelse(chapter<=103, "First 103", "Last 17")) %>%
  filter(nchar(.$word)>1) %>%
  mutate(word = str_extract(word, "[^0-9a-z']+")) %>%
  mutate(word = str_extract(word, "^[^一二三四五六七八九十]+")) %>%
  count(part, word) %>%
  group_by(part) %>%
  mutate(proportion = n / sum(n)) %>% 
  select(-n) %>% 
  spread(part, proportion) %>% 
  gather(part, proportion, `Last 17`)

匯出圖表

ggplot(frequency, aes(x = proportion, y = `First 103`, color = abs(`First 103` - proportion))) +
  geom_abline(color = "gray40", lty = 2) +
  geom_jitter(alpha = 0.1, size = 2, width = 0.3, height = 0.3, na.rm = T) +
  geom_text(aes(label = word), check_overlap = T, family = "Heiti TC Light", na.rm = T) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  scale_color_gradient(limits = c(0, 0.001), low = "darkslategray4", high = "gray75") +
  theme(legend.position="none") +
  labs(y = "First 103", x = "Last 17")

結果發現,後17回雖然也佔了46年的歷史,但主要角色都已經沒有再被提到了,最後的46年只用了17回就寫完了,三國演義這本書整體上有點頭重腳輕的感覺。