倪匡小說:「偷天換日」
以本課習得之文字分析技巧,分析倪匡擅長的短篇小說,是否能反映故事情節及架構。 本文主角「賽觀音」為組織(中共)極高層遺孀,在97歲臨終前欲託咐一個隱暪了一生的大秘密。 在抗戰時中共組織為確保代代有菁英人才,早有指定第2、第3代接班人的培訓計畫。在流亡期間將高幹子女秘密集中托養,將安排送出國深造,學成後安排國內各層面歷練,最後依表現成為接班人選之一。 賽觀音負責其中年齡段為2~3歲組,一共有203位幼童,並隱匿在某罕見山區,怎料突然半夜土石流將女兵及孩童所在的古廟無情沖走不留痕跡,只剩賽觀音一人獨存。 賽觀意在無奈上吊謝罪之際,被土匪頭軍師娘子救下…
1.為何賽觀音最後可以不死?為何組織文件沒有記載?
2.賽觀音的大秘密與什麼有關?
packages = c("dplyr", "tidytext","jiebaR","stringr","wordcloud","wordcloud2","ggplot2", "tidyr", "scales","curl","readr","NLP","ggraph","igraph","reshape2","widyr")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
require(dplyr)
require(tidytext)
require(jiebaR)
require(stringr)
require(wordcloud)
require(wordcloud2)
require(ggplot2)
require(tidyr)
require(scales)
require(curl)
require(readr)
require(NLP)
require(ggraph)
require(igraph)
require(reshape2)
require(widyr)
orginalDF = read.table("steal.txt",header= F , sep="\n", fileEncoding='UTF-8' , stringsAsFactors=F)
steal_vector <- unlist(strsplit(orginalDF$V1,"[,。?!]"), use.names=FALSE)
#處理斷行
chapter_steal <- data_frame(text=steal_vector) %>%
mutate(line=c(1:nrow(.))) %>%
mutate(chapter = cumsum(str_detect(.$text, regex("第.*部:"))))
#處理章節
chapter_steal
## # A tibble: 6,906 x 3
## text line chapter
## <chr> <int> <int>
## 1 ︻第一部:美女︼ 1 1
## 2 那天晚上 2 1
## 3 和白素在外面忙了一天回家 3 1
## 4 車子停在門口 4 1
## 5 白素先進屋子 5 1
## 6 我將車子停好一些 6 1
## 7 就聽到屋子裡傳來紅綾的叫聲 7 1
## 8 紅綾一面叫 8 1
## 9 一面還在說些甚麼 9 1
## 10 可是在因為聲響太吵耳 10 1
## # ... with 6,896 more rows
#新增結巴處理器,載入停用字、保留字詞庫
#反覆調整保留字及停用字stop_words 整理 (ex: 於是)
jieba_tokenizer <- worker(stop_word = "stop_words.txt",user="user_dict.txt")
#定義丟給unnest_tokens的分詞
book_tokenizer <- function(t) {
lapply(t, function(x) {
tokens <- segment(x, jieba_tokenizer)
# 將詞彙長度為1的詞清除
tokens <- tokens[nchar(tokens)>1]
return(tokens)
})
}
tidybook <- chapter_steal %>% unnest_tokens(word,text,token= book_tokenizer)
主要是人物及名稱
tidybook %>%
count(word, sort = TRUE) %>%
filter(n > 50) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
ylab("出現次數") +
coord_flip()
為什麼[一口氣]如此常見? =>倪匡小說的特點,喜誇大的描寫人物的反應
grep(".*一口氣$", steal_vector,value=T) %>% head (50)
## [1] "我吸了一口氣"
## [2] "白素咦了一口氣"
## [3] "嘆了一口氣"
## [4] "客人嘆了一口氣"
## [5] "吸了一口氣"
## [6] "我深深地吸了一口氣"
## [7] "於是深深地吸了一口氣"
## [8] "她就嘆了一口氣"
## [9] "吸了一口氣"
## [10] "他吸了一口氣"
## [11] "大大地鬆了一口氣"
## [12] "雖然當時我只是吸了一口氣"
## [13] "賽觀音緩緩地吸了一口氣"
## [14] "嘆了一口氣"
## [15] "於是就深深地吸了一口氣"
## [16] "我不由自主嘆了一口氣"
## [17] "賽觀音深深地吸了一口氣"
## [18] "白素則陡然吸了一口氣"
## [19] "她深深地吸了一口氣"
## [20] "賽觀音輕輕嘆了一口氣"
## [21] "她深深地吸了一口氣"
## [22] "長長地嘆了一口氣"
## [23] "我吸了一口氣"
## [24] "賽觀音才長長吁了一口氣"
## [25] "吸了一口氣"
## [26] "她輕輕嘆了一口氣"
## [27] "賽觀音長長地吸了一口氣"
## [28] "人人都鬆了一口氣"
## [29] "軍師娘子吸了一口氣"
## [30] "軍師娘子緩緩吸了一口氣"
## [31] "她吸了一口氣"
## [32] "於是吸了一口氣"
## [33] "在這時候我和白素不約而同深深地吸了一口氣"
## [34] "這倒令我鬆了一口氣"
## [35] "我當時輕輕嘆了一口氣"
## [36] "急速地吸了一口氣"
## [37] "白素嘆了一口氣"
## [38] "我深深吸了一口氣"
=>此段為小說主體關鍵字,反映出內容是描述與與土匪、部隊有關的歷史過往
tidybook %>%
count(word, sort = TRUE) %>%
filter(n > 30 & n < 50) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
ylab("出現次數") +
coord_flip()
tokens_count <- tidybook %>%
filter(nchar(.$word)>1) %>%
group_by(word) %>%
summarise(sum = n()) %>%
filter(sum>10) %>%
arrange(desc(sum))
tokens_count %>% wordcloud2()
steal_positive.txt、steal_negative.txt
p<-read_file(file.path (getwd() , "steal_positive.txt"))
n<-read_file(file.path (getwd() , "steal_negative.txt"))
positive <- strsplit(p, "[,]")[[1]]
negative <- strsplit(n, "[,]")[[1]]
positive <- data.frame(word = positive, sentiments = "positive")
negative <- data.frame(word = negative, sentiemtns = "negative")
colnames(negative) = c("word","sentiment")
colnames(positive) = c("word","sentiment")
LIWC_ch <- rbind(positive, negative)
head(LIWC_ch, 30)
## word sentiment
## 1 一流 positive
## 2 下定決心 positive
## 3 不拘小節 positive
## 4 不費力 positive
## 5 不錯 positive
## 6 主動 positive
## 7 乾杯 positive
## 8 乾淨 positive
## 9 了不起 positive
## 10 享受 positive
## 11 仁心 positive
## 12 仁愛 positive
## 13 仁慈 positive
## 14 仁義 positive
## 15 仁術 positive
## 16 仔細 positive
## 17 付出 positive
## 18 伴侶 positive
## 19 伶俐 positive
## 20 作品 positive
## 21 依戀 positive
## 22 俊美 positive
## 23 俐落 positive
## 24 保證 positive
## 25 保護 positive
## 26 信任 positive
## 27 信奉 positive
## 28 信實 positive
## 29 信心 positive
## 30 信服 positive
計算每個章節情緒值採用LIWC的LEXICON
calsentiment <-tidybook %>%
inner_join(LIWC_ch) %>%
count(chapter = chapter, sentiment)%>%
spread(sentiment, n, fill = 0) %>%
mutate(sentimentx = positive - negative)
head(calsentiment)
## # A tibble: 6 x 4
## chapter negative positive sentimentx
## <int> <dbl> <dbl> <dbl>
## 1 1 46 165 119
## 2 2 96 74 -22
## 3 3 54 109 55
## 4 4 72 49 -23
## 5 5 104 63 -41
## 6 6 72 59 -13
ggplot(calsentiment, aes(chapter, sentimentx,fill = chapter)) +
geom_col(show.legend = FALSE,width = 0.8) +
scale_x_continuous(name = "Chapter",breaks = c(1,2,3,4,5,6,7,8,9,10),
minor_breaks = NULL) +
ylab("情緒差值")+
theme_grey(base_family = 'STKaiti')
可得知這是一篇傾向負向情緒的文章,符合倪匡在本文所傳達的謊言與自私意念。
gsub("︼",")",gsub("︻","(",grep(".*︼$", steal_vector,value=T) ))
## [1] "(第一部:美女)" "(第二部:麻木)" "(第三部:重逢)" "(第四部:歷史)"
## [5] "(第五部:烙印)" "(第六部:男女)" "(第七部:計劃)" "(第八部:巨災)"
## [9] "(第九部:深究)" "(第十部:證據)"
#grep(".*歷史.*", steal_vector,value=T)
#grep(".*土匪.*", steal_vector,value=T)
#grep(".*部隊.*", steal_vector,value=T)
#grep(".*問題.*", steal_vector,value=T)
#grep(".*敘述.*", steal_vector,value=T)
依據tokens_count統計的文字,inner_join情緒字典,查出本書所用到的情緒字眼
result_sentiment <- tokens_count %>%
select(word) %>%
inner_join(LIWC_ch)
result_sentiment
## # A tibble: 31 x 2
## word sentiment
## <chr> <chr>
## 1 問題 negative
## 2 相信 positive
## 3 搖頭 negative
## 4 重要 positive
## 5 漂亮 positive
## 6 漂亮 positive
## 7 可怕 negative
## 8 肯定 positive
## 9 決定 positive
## 10 美麗 positive
## # ... with 21 more rows
將書中的情緒關鍵字做分析
library("reshape2")
par(family="STKaiti")
tokens_count %>%
inner_join(LIWC_ch) %>%
select(word,sentiment,sum) %>%
acast(word ~ sentiment,value.var = "sum", fill = 0) %>%
wordcloud::comparison.cloud(random.order=FALSE,colors = c("indianred3", "blue"),max.words = 108)
#斷詞與整理斷詞結果
# 進行斷詞,並計算各詞彙在各文章中出現的次數
steal_words <- tidybook %>%
count(chapter, word, sort = TRUE)
steal_words
## # A tibble: 8,850 x 3
## chapter word n
## <int> <chr> <int>
## 1 6 賽觀音 109
## 2 6 於放 94
## 3 8 賽觀音 90
## 4 7 賽觀音 86
## 5 4 賽觀音 83
## 6 3 葫蘆生 74
## 7 1 紅綾 72
## 8 7 於放 71
## 9 9 於是 62
## 10 9 白素 58
## # ... with 8,840 more rows
# 計算每篇文章包含的詞數
total_words <- tidybook %>%
group_by(chapter) %>%
summarise(sum = n())
total_words
## # A tibble: 10 x 2
## chapter sum
## <int> <int>
## 1 1 1531
## 2 2 1510
## 3 3 1586
## 4 4 1501
## 5 5 1679
## 6 6 1669
## 7 7 1785
## 8 8 1597
## 9 9 1462
## 10 10 1373
# 合併 mask_words(每個詞彙在每個文章中出現的次數)
# 與 total_words(每篇文章的詞數)
# 新增各個詞彙在所有詞彙中的總數欄位
steal_words <- left_join(steal_words, total_words)
steal_words
## # A tibble: 8,850 x 4
## chapter word n sum
## <int> <chr> <int> <int>
## 1 6 賽觀音 109 1669
## 2 6 於放 94 1669
## 3 8 賽觀音 90 1597
## 4 7 賽觀音 86 1785
## 5 4 賽觀音 83 1501
## 6 3 葫蘆生 74 1586
## 7 1 紅綾 72 1531
## 8 7 於放 71 1785
## 9 9 於是 62 1462
## 10 9 白素 58 1462
## # ... with 8,840 more rows
以每篇文章爲單位,計算每個詞彙在的tf-idf值 =>疑問: 為何不見賽觀音?
steal_words_tf_idf <- steal_words %>%
bind_tf_idf(word, chapter, n) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
group_by(chapter) %>%
top_n(7) %>%
ungroup %>%
arrange(desc(tf_idf))
steal_words_tf_idf
## # A tibble: 74 x 7
## chapter word n sum tf idf tf_idf
## <int> <fct> <int> <int> <dbl> <dbl> <dbl>
## 1 1 紅綾 72 1531 0.0470 0.916 0.0431
## 2 8 軍師娘子 37 1597 0.0232 1.20 0.0279
## 3 3 葫蘆生 74 1586 0.0467 0.511 0.0238
## 4 10 文件 14 1373 0.0102 2.30 0.0235
## 5 7 日軍 17 1785 0.00952 2.30 0.0219
## 6 3 藍絲 22 1586 0.0139 1.20 0.0167
## 7 10 紅綾 24 1373 0.0175 0.916 0.0160
## 8 9 黃蟬 10 1462 0.00684 2.30 0.0157
## 9 1 介紹信 10 1531 0.00653 2.30 0.0150
## 10 3 患者 14 1586 0.00883 1.61 0.0142
## # ... with 64 more rows
各章節的高字頻詞彙
steal_words_tf_idf %>%
ggplot(aes(word, tf_idf, fill = chapter)) +
geom_col(show.legend = FALSE) +
labs(x = "chapter", y = "tf-idf") +
facet_wrap(~chapter, scales = "free") +
coord_flip()
word_pairs <- steal_words %>%
pairwise_count(word, chapter, sort = TRUE)
word_pairs
## # A tibble: 6,734,594 x 3
## item1 item2 n
## <chr> <chr> <dbl>
## 1 於是 賽觀音 10
## 2 一口氣 賽觀音 10
## 3 神情 賽觀音 10
## 4 回答 賽觀音 10
## 5 想到 賽觀音 10
## 6 問題 賽觀音 10
## 7 一眼 賽觀音 10
## 8 人物 賽觀音 10
## 9 告訴 賽觀音 10
## 10 許多 賽觀音 10
## # ... with 6,734,584 more rows
word_cors <- steal_words %>%
group_by(word) %>%
filter(n() >= 7) %>%
pairwise_cor(word, chapter, sort = TRUE)
word_cors
## # A tibble: 17,030 x 3
## item1 item2 correlation
## <chr> <chr> <dbl>
## 1 容易 於放 1.
## 2 興趣 白素 1.
## 3 女兒 母親 1.
## 4 自然 母親 1.
## 5 變成 敘述 1.
## 6 改變 敘述 1.
## 7 環境 離開 1.
## 8 原來 離開 1.
## 9 過去 離開 1.
## 10 身上 離開 1.
## # ... with 17,020 more rows
“關鍵字”相關性高的詞彙,但是都是Na?
word_cors %>%
filter(item1 == "組織") %>%
head(100)
## # A tibble: 100 x 3
## item1 item2 correlation
## <chr> <chr> <dbl>
## 1 組織 土匪 0.764
## 2 組織 敘述 0.764
## 3 組織 經歷 0.764
## 4 組織 變成 0.764
## 5 組織 改變 0.764
## 6 組織 故事 0.667
## 7 組織 離開 0.667
## 8 組織 環境 0.667
## 9 組織 原來 0.667
## 10 組織 過去 0.667
## # ... with 90 more rows
這五個相關性最高詞彙
word_cors %>%
filter(item1 %in% c("母親", "組織","秘密","關係","土匪")) %>%
group_by(item1) %>%
top_n(5) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation)) +
geom_bar(stat = "identity") +
facet_wrap(~ item1, scales = "free") +
coord_flip()+
theme(text = element_text(family = "Heiti TC Light")) #加入中文字型設定,避免中文字顯示錯誤。
從拓樸中可以與文章的主要架構相符,從對母親的負面情緒推衍到過去的秘密。
set.seed(2020)
word_cors %>%
filter(correlation > 0.4) %>%
filter(item1 %in% c("母親", "組織","秘密","關係","土匪")) %>%
graph_from_data_frame() %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = correlation), show.legend = FALSE) +
geom_node_point(color = "lightblue", size = 3) +
geom_node_text(aes(label = name), repel = TRUE, family = "Heiti TC Light") + #加入中文字型設定,避免中文字顯示錯誤。
theme_void()
倪匡的小說從書名可以知道其結論,其使用文字不會有太複雜與獨特的詞彙。文章前半段會舖陳情節,結局相關在後面段會反覆出現,從高頻TF-IDF 與故事情節非常吻合。
從文字分析的過程中,透過對書的情節了解可以相互印證其分析大致相符。從主要的關鍵字之間的共現關係,也可以描繪出具體的文章走向。
作者雖運用大量的口語描述,體現人物間細微的互動,例如輕輕地,從容地等,這些因為與內容無關,都被停用字過濾了,因此真正與故事描述相關的文字,大約只剩全文的 2/3。
對故事理解與文字分析相印證:
1.為何賽觀音最後未死,且組織文件沒有記載?
機密文件雖然存在很多疑團,整件事只由賽觀音報告,沒有其他人佐証,無法找出那群逃亡的幹部和女兵,所以當組織收到這報告,選擇相信,不再繼續調查。因為,若在調查中找出真相,所有相關決定及執行的人員,都有可能在政治風暴中被人批鬥,倒不如當一切都正常,以免有把柄落在其他人手中。所以,歷史不一定全由當權者決定,當中的官員也是有份的。
2.她女兒拿出所謂的文件試圖證明母親說的都是妄想,母女間的情感關係為何?
故事的開端對女兒的描寫頗為正向,而到中後章節,敘述母女間的關係是負面與緊張的。女兒對土匪出身的母親極其反感,卻崇拜根正苗紅的父親。雖然沒有和母親劃清界線,但在日常生活上兩人的不同思想,已產生不少爭吵。貌美女兒生在動亂時代,必受迫害,最後能回復一定的地位,自然對現在的組織有依賴之心,亦害怕再受到逼害,因此拒信母親所言。
3.賽觀音真正想表達的那個大秘密是什麼?與作者的隱喻有何關係?
隱喻某政權崛起、立國初期所抱著「為人民服務」的理念,吸引眾多有志青年擁戴,但後來為什麼會發生那翻天覆地的十年(文化大革命)?或許因為領袖已經不是原來的領袖,菁英的第二代已經不是原來的第二代,而是跟土匪有關。當然,這批203位不乏現今在位的知名政治人物。