如何用Python和R對《權力的遊戲》故事情節做情緒分析?
想知道一部沒看過的影視劇能否符合自己口味,卻又怕被劇透?沒關係,我們可以用情緒分析來了解故事情節是否足夠跌宕起伏。本文一步步教你如何用Python和R輕鬆愉快完成文本情緒分析。一起來試試吧。
煩惱
追劇是個令人苦惱的事情。
就拿剛剛播完第7季的《權力的遊戲》來說,每周等的時候那叫一個煎熬,就盼著周一能提早到來。
可是最後一集播完,你緊張、興奮、激動和過癮之後呢?是不是又覺得很失落?
因為——下面我該看什麼劇啊?
現在的影視作品,不是太少,而是太多。如果你有選擇困難症,更會有生不逢時的感覺。
Netflix, Amazon和豆瓣等推薦引擎可以給你推薦影視作品。但是它們的推薦,只是把觀眾劃分成了許多個圈子。你的數據,如果足夠真實準確的話,可能剛好和某一個圈子的特性比較接近,於是就給你推薦這個圈子更喜歡的作品。
但是這不一定靠譜。有可能你的觀影和評價信息分散在不同的平台上。不完整、不準確的觀影數據,會導致推薦的效果大打折扣。
即便有了推薦的影視劇,它是否符合你的口味呢?畢竟看劇也是有機會成本的。放著《絕命毒師》不看,去看了一部爛劇,你的生命中的數十小時就這樣被浪費了。
可除了從頭到尾看一遍,又如何能驗證一部劇是否是自己喜歡的呢?
你可能想到去評論區看劇評。那可是個危險區域,因為隨時都有被劇透的風險。
你覺得還是利用社交媒體吧,在萬能的朋友圈問問好友。有的好友確實很熱心,但有的時候,也許會過於熱心。
例如下面這位(圖片來自於網路):
你可能抓狂了,覺得這是個不可能完成的任務,就如同英諺所云:
You can』t have your cake and eat it too.
真的是這樣嗎?不一定。在這個大數據泛濫,數據分析工具並不稀缺的時代,你完全可以利用技術幫自己選擇優秀的影視作品。
故事情節的文本,你可以到互聯網上找劇本,或者是字幕。當然,不是讓你把劇本從頭讀到尾,那樣還不如直接看劇呢。你需要用技術來對文本進行分析。
情緒
我們提到的這個技術,叫做情緒分析(emotional analysis)。它和情感分析(sentiment analysis)有相似之處。都是通過對內容的自動化分析,來獲得結果。
情感分析的結果一般分為正向(positive)和負向(negative),而情緒分析包含的種類就比較多了。
加拿大國家研究委員會(National Research Council of Canada)官方發布的情緒詞典包含了8種情緒,分別為:
- 憤怒(anger)
- 期待(anticipation)
- 厭惡(disgust)
- 恐懼(fear)
- 喜悅(joy)
- 悲傷(sadness)
- 驚訝(surprise)
- 信任(trust)
有了這些情緒的標記,你可以輕鬆地對一段文本的情緒變化進行分析。
這時候,你可以回憶起中學語文老師講作文時說過的那句話:
文如看山不喜平。
故事情節會伴隨著各種情緒的波動。通過分析這些情緒的起伏,我們可以看出故事的基調是否符合自己的口味,情節是否緊湊等。這樣,你可以根據自己的偏好,甚至是當前的心境,來選擇合適的作品觀看了。
我們需要用到Python和R。這兩種語言在目前數據科學領域裡最受歡迎。Python的優勢在於通用,而R的優勢在於統計學家組成的社區。這些統計學家真是高產,也很酷,經常製造出令人驚艷的分析包。
咱們這裡就用Python來做數據清理,然後用R做情緒分析,並且把結果可視化輸出。
準備
數據
我們首先需要找到的是來源數據。作為例子,我們選擇了《權利的遊戲》第三季的第9集,名字叫做」The Rains of Castamere」。
你可以到這個網址下載這一集的劇本。
你只需要全選頁面拷貝,然後打開一個文本編輯器,把內容粘貼進去。好了,現在你就有可供分析的文本了。
請建立一個工作目錄。後面的操作都在這個目錄里進行。例如我的工作目錄是~/Downloads/python-r-emotion。
把剛剛獲得的文本文件放到這個目錄中。
Python
我們需要用到Jupyter Notebook,請安裝Anaconda套裝。具體的安裝方法請參考《 如何用Python做詞雲 》一文。
R
到這個網址下載R基礎安裝包。你會看到R的下載位置有很多。
我建議你選擇中國的鏡像,這樣連接速度更快。清華大學的鏡像就不錯。
請根據你的操作系統平台選擇其中對應的版本下載。我選擇的是macOS版本,下載得到pkg文件。雙擊就可以安裝。
安裝了基礎包之後,我們繼續安裝集成開發環境RStudio。下載地址為這裡。
還是依據你的操作系統情況,選擇對應的安裝包。macOS安裝包為dmg文件。雙擊打開後,把其中的RStudio.app圖標拖動到Applications文件夾中,安裝就完成了。
好了,現在你就有了R的運行環境了。
清理
我們首先需要清理文本數據,完成以下這兩個任務:
- 把與劇情正文無關的內容去除;
- 將數據轉換成R可以直接做情緒分析的結構化數據格式。
到你的系統「終端」(macOS, Linux)或者「命令提示符」(Windows)下,進入我們的工作目錄,執行以下命令。
jupyter notebook
這時候工作目錄下還只有那個文本文件。
我們打開看看內容。
往下翻頁,我們找到了劇本正文正式開始的標記Opening Credits。
翻到文本的結尾,我們可以看到劇本結束的標記End Credits。
我們回到主頁面下,新建一個Python的Notebook。點擊右方的New按鈕,選擇Python 2。
有了全新的Notebook後,我們首先引入需要用到的包。
import pandas as pdimport re
然後讀取當前目錄下的文本文件。
with open("s03e09.txt") as f: data = f.read()
看看內容:
print(data)
結果如下:
數據正確讀入。下面我們依照剛才瀏覽中發現的標記把正文以外的文本內容去掉。
先去掉開頭的非劇本正文內容。
data = data.split(Opening Credits])[1]
再次列印,可以看見現在從正文開頭了。
print(data)
下面我們同樣處理結尾部分。
data = data.split([End Credits)[0]
列印出來試試看。
print(data)
拖動到尾部。
移除了開頭和結尾的多餘內容後,我們來移除空行。這裡我們需要用到正則表達式。
regex = r"^$
"subst = ""data = re.sub(regex, subst, data, 0, re.MULTILINE)
然後我們再次列印。
print(data)
空行都已經成功挪走了。可是我們注意到還有一些分割線組成的行,也需要去除掉。
regex = r"^-+$
"subst = ""data = re.sub(regex, subst, data, 0, re.MULTILINE)
至此,清理工作已經完成了。下面我們把文本整理成數據框,每一行分別加上行號。
利用換行符把原本完整的文本分割成行。
lines = data.split(
)
然後給每一行加上行號。
myrows = []num = 1for line in lines: myrows.append([num, line]) num = num + 1
我們看看前三行的行號是否已經正常添加。
myrows[:3]
一切正常,下面我們把目前的數組轉換成數據框。如果你對數據框的概念不太熟悉,請參考《貸還是不貸:如何用Python和機器學習幫你決策?》一文。
df = pd.DataFrame(myrows)
我們來看看執行結果:
df.head()
數據是正確的,不過表頭不對。我們給表頭重新命名。
df.columns = [line, text]
再來看看:
df.head()
好了,既然數據框已經做好了。下面我們把它轉換成為csv格式,以便於R來讀取和處理。
df.to_csv(data.csv, index=False)
我們打開data.csv文件,可以看到數據如下:
數據清理和準備工作結束,下面我們用R進行分析。
分析
RStudio可以提供一個交互環境,幫我們執行R命令並即時反饋結果。
打開RStudio之後,選擇File->New,然後從以下界面中選擇 R Notebook。
然後,我們就有了一個R Notebook的模板。模板附帶一些基礎使用說明。
我們嘗試點擊編輯區域(左側)代碼部分(灰色)的運行按鈕。
立即就可以看到繪圖的結果了。
另外我們還可以點擊菜單欄上的Preview按鈕,來看整個兒代碼的運行結果。
RStudio為我們生成了HTML文件,我們的文字說明、代碼和運行結果圖文並茂呈現出來。
好了,熟悉了環境後,我們該實際操作運行自己的代碼了。咱們把左側編輯區的開頭說明區保留,把全部正文刪除,並且把文件名改成有意義的名字,例如emotional-analysis。
這樣就清爽多了。
下面我們讀入數據。
setwd("~/Downloads/python-r-emotion/")script <- read.csv("data.csv", stringsAsFactors=FALSE)
讀入的時候一定要注意設置stringsAsFactors=FALSE,不然R在讀取字元串數據的時候,會默認轉換為level,後面的分析就做不成了。讀取之後,在右側的數據區域你可以看到script這個變數,雙擊它,可以看到內容。
數據有了,下面我們需要準備分析用的包。這裡我們需要用到4個包,請執行以下語句安裝。
install.packages("dplyr")install.packages("tidytext")install.packages("tidyr")install.packages("ggplot2")
注意安裝新軟體包這種操作只需要執行一次。可是我們每次預覽結果的時候,文件里所有語句都會被執行一遍。為了避免安裝命令被反覆執行。當安裝結束後,請你刪除或者注釋掉上面幾條語句。
安裝了包,並不意味著就可以直接用其中的函數了。使用之前,你需要執行library語句調用這些包。
library(dplyr)library(tidytext)library(tidyr)library(ggplot2)
好了,萬事俱備。我們需要把一句句的文本拆成單詞,這樣才能和情緒詞典里的單詞做匹配,從而分析單詞的情緒屬性。
在R裡面,可以採用Tidy Text方式來做。執行的語句是unnest_token,我們把原先的句子拆分成為單詞。
tidy_script <- script %>% unnest_tokens(word, text)head(tidy_script) ## line word ## 1 1 first ## 1.1 1 scene ## 1.2 1 shows ## 1.3 1 the ## 1.4 1 location ## 1.5 1 of
這裡原先的行號依然被保留。我們可以看到每一個詞來自於哪一行,這有利於下面我們對行甚至段落單位進行分析。
我們調用加拿大國家研究委員會發布的情緒詞典。這個詞典在tidytext包裡面內置了,就叫做nrc。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% arrange(line) %>% head(10)
我們只顯示前10行的內容:
## Joining, by = "word" ## line word sentiment ## 1 1 rock positive ## 2 1 ancestral trust ## 3 1 giant fear ## 4 1 representing anticipation ## 5 1 stark negative ## 6 1 stark trust ## 7 1 stark negative ## 8 1 stark trust ## 9 4 dangerous fear ## 10 4 dangerous negative
可以看到,有的詞對應某一種情緒屬性,有的詞同時對應多種情緒屬性。注意nrc包裡面不僅有情緒,而且還有情感(正向和負向)。
我們對單詞的情緒已經清楚了。下面我們來綜合判斷每一行的不同情感分別含有幾個詞。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% count(line, sentiment) %>% arrange(line) %>% head(10)
還是只顯示結果的前10行。
## Joining, by = "word" ## # A tibble: 10 x 3 ## line sentiment n ## <int> <chr> <int> ## 1 1 anticipation 1 ## 2 1 fear 1 ## 3 1 negative 2 ## 4 1 positive 1 ## 5 1 trust 3 ## 6 4 fear 1 ## 7 4 negative 1 ## 8 5 positive 1 ## 9 5 trust 1 ## 10 6 positive 1
以第1行為例,包含「期待」的詞有1個,包含「恐懼」的有1個,包含「信任」的有3個。
如果我們以1行為單位分析情感變化,粒度過細。鑒於整個劇本包含了幾百行文字,我們以5行作為一個基礎單位,來進行分析。
這裡我們使用index來把原先的行號處理一下,分成段落。%/%代表整除符號,這樣0-4行就成為了第一段落,5-9行成為第二段落,以此類推。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% count(line, sentiment) %>% mutate(index = line %/% 5) %>% arrange(index) %>% head(10) ## Joining, by = "word" ## # A tibble: 10 x 4 ## line sentiment n index ## <int> <chr> <int> <dbl> ## 1 1 anticipation 1 0 ## 2 1 fear 1 0 ## 3 1 negative 2 0 ## 4 1 positive 1 0 ## 5 1 trust 3 0 ## 6 4 fear 1 0 ## 7 4 negative 1 0 ## 8 5 positive 1 1 ## 9 5 trust 1 1 ## 10 6 positive 1 1
可以看出,第一段包含的情感還真是很豐富。
只是如果讓我們把結果表格從頭讀到尾,那也真夠難受的。我們還是用可視化的方法,把圖繪製出來吧。
繪圖我們採用ggplot包。這個包我們在《 如何用Python做輿情時間序列可視化?》一文中介紹過,歡迎查閱複習。
我們使用geom_col指令,讓R幫我們繪製柱狀圖。對不同的情緒,我們用不同顏色表示出來。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% count(line, sentiment) %>% mutate(index = line %/% 5) %>% ggplot(aes(x=index, y=n, color=sentiment)) %>% + geom_col()
結果是豐富多彩的,可惜看不大清楚。為了區別不同情緒,我們調用facet_wrap函數,把不同情緒拆開,分別繪製。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% count(line, sentiment) %>% mutate(index = line %/% 5) %>% ggplot(aes(x=index, y=n, color=sentiment)) %>% + geom_col() %>% + facet_wrap(~sentiment, ncol=3)
嗯,這張圖看著就舒服多了。
不過這張圖也會給我們造成一些疑惑。按照道理來說,每一段落的內容里,包含單詞數量大致相當。結尾部分情感分析結果裡面,正向和負向幾乎同時上升,這就讓人很不解。是這裡的幾行太長了,還是出了什麼其他的問題呢?
數據分析的關鍵,就是在這種令人疑惑的地方深挖進去。
我們不妨來看看,出現最多的正向和負向情感詞都有哪些。
先來看看正向的。我們這次不是按照行號,而是按照詞頻來排序。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment == "positive") %>% count(word) %>% arrange(desc(n)) %>% head(10) ## Joining, by = "word" ## # A tibble: 10 x 2 ## word n ## <chr> <int> ## 1 lord 13 ## 2 good 9 ## 3 guard 9 ## 4 daughter 8 ## 5 shoulder 7 ## 6 love 6 ## 7 main 6 ## 8 quiet 6 ## 9 bride 5 ## 10 king 5
看到這個詞頻,我們不禁有些失落——看來分析結果是有問題的。許多辭彙都是名詞,而且在《權力的遊戲》故事中,這些詞根本就沒有明確的情感指向。例如lord這個詞,劇中的lord有的正直善良,但也有很多不是什麼好人;king也一樣,雖然Robb和Jon是國王,但別忘了Joffrey也是國王啊。
我們再來看看負向情感辭彙吧。
tidy_script %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment == "negative") %>% count(word) %>% arrange(desc(n)) %>% head(10) ## Joining, by = "word" ## # A tibble: 10 x 2 ## word n ## <chr> <int> ## 1 stark 16 ## 2 pig 14 ## 3 lord 13 ## 4 worm 12 ## 5 kill 11 ## 6 black 9 ## 7 dagger 8 ## 8 shot 8 ## 9 killing 7 ## 10 afraid 4
看了這個結果,就更令人沮喪不已了——同樣的一個lord,竟然既被當成了正向,又被當成了負向辭彙。詞典標註者太不負責任了吧!
別著急。出現這樣的情況,是因為我們做分析時少了一個重要步驟——處理停用詞。對於每一個具體場景,我們都需要使用停用詞表,把那些可能干擾分析結果的詞扔出去。
tidytext提供了默認的停用詞表。我們先拿來試試看。這裡使用的語句是anti_join,就可以把停用詞先去除,再進行情緒詞表連接。
我們看看停用詞去除後,正向情感辭彙的高頻詞有沒有變化。
tidy_script %>% anti_join(stop_words) %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment == "positive") %>% count(word) %>% arrange(desc(n)) %>% head(10) ## Joining, by = "word" ## Joining, by = "word" ## # A tibble: 10 x 2 ## word n ## <chr> <int> ## 1 lord 13 ## 2 guard 9 ## 3 daughter 8 ## 4 shoulder 7 ## 5 love 6 ## 6 main 6 ## 7 quiet 6 ## 8 bride 5 ## 9 king 5 ## 10 music 5
結果令人失望。看來停用詞表裡沒有包含我們需要去除的那一堆名詞。
沒關係,我們自己來修訂停用詞表。使用R中的bind_rows語句,我們就能在基礎的預置停用詞表基礎上,附加上我們自己的停用詞。
custom_stop_words <- bind_rows(stop_words, data_frame(word = c("stark", "mother", "father", "daughter", "brother", "rock", "ground", "lord", "guard", "shoulder", "king", "main", "grace", "gate", "horse", "eagle", "servent"), lexicon = c("custom")))
我們加入了一堆名詞和關係代詞。因為它們和情緒之間沒有必然的關聯。但是名詞還是保留了一些。例如「新娘」總該是和好的情感和情緒相連吧。
用了定製的停用詞表後,我們來看看詞頻的變化。
tidy_script %>% anti_join(custom_stop_words) %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment == "positive") %>% count(word) %>% arrange(desc(n)) %>% head(10) ## Joining, by = "word" ## Joining, by = "word" ## # A tibble: 10 x 2 ## word n ## <chr> <int> ## 1 love 6 ## 2 quiet 6 ## 3 bride 5 ## 4 music 5 ## 5 rest 5 ## 6 finally 4 ## 7 food 3 ## 8 forward 3 ## 9 hope 3 ## 10 hospitality 3
這次好多了,起碼解釋情緒可以自圓其說了。我們再看看那些負向情感辭彙。
tidy_script %>% anti_join(custom_stop_words) %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment == "negative") %>% count(word) %>% arrange(desc(n)) %>% head(10) ## Joining, by = "word" ## Joining, by = "word" ## # A tibble: 10 x 2 ## word n ## <chr> <int> ## 1 pig 14 ## 2 worm 12 ## 3 kill 11 ## 4 black 9 ## 5 dagger 8 ## 6 shot 8 ## 7 killing 7 ## 8 afraid 4 ## 9 fear 4 ## 10 leave 4
比起之前,也有很大進步。
做好了基礎的修訂工作,下面我們來重新作圖吧。我們把停用詞表加進去,並且還用filter語句把情感屬性刪除掉了。因為我們分析的對象是情緒(emotion),而不是情感(sentiment)。
tidy_script %>% anti_join(custom_stop_words) %>% inner_join(get_sentiments("nrc")) %>% filter(sentiment != "negative" & sentiment != "positive") %>% count(line, sentiment) %>% mutate(index = line %/% 5) %>% ggplot(aes(x=index, y=n, color=sentiment)) %>% + geom_col() %>% + facet_wrap(~sentiment, ncol=3)
這幅圖一下子變得清晰,也值得琢磨。
在這一集的結尾,多種情緒混雜交織——歡快的氣氛陡然下降,期待與信任在波動,厭惡在不斷上漲,恐懼與悲傷陡然上升,憤怒突破天際,交雜著數次的驚訝……
你可能會納悶兒,情緒怎麼可能這麼複雜?是不是分析又出問題了?
還真不是,這一集的故事,有個另外的名字,叫做《紅色婚禮》。
收穫
通過本文的學習,希望你已初步掌握了如下技能:
- 如何用Python對網路摘取的文本做處理,從中找出正文,並且去掉空行等內容;
- 如何用數據框對數據進行存儲、表示與格式轉換,在Python和R中交換數據;
- 如何安裝和使用RStudio環境,用R Notebook做互動式編程;
- 如何利用tidytext方式來處理情感分析與情緒分析;
- 如何設置自己的停用詞表;
- 如何用ggplot繪製多維度切面圖形。
掌握了這些內容後,你是否覺得用這麼強大的工具分析個劇本找影視作品,有些大炮轟蚊子的感覺?
討論
除了本文介紹的方法之外,你還知道哪些方便的情緒分析工具與方法?在尋找新劇方面,你有什麼獨家心得體悟?有了情緒分析這個利器,你還可以處理哪些有趣的問題?歡迎留言,記錄下你的思考,分享給大家。我們一起交流討論。
如果你對我的文章感興趣,歡迎點贊,並且微信關注和置頂我的公眾號「玉樹芝蘭」(nkwangshuyi)。
如果本文可能對你身邊的親友有幫助,也歡迎你把本文通過微博或朋友圈分享給他們。讓他們一起參與到我們的討論中來。
推薦閱讀:
※《Aspect-augmented Adversarial Networks for Domain Adaptation》閱讀筆記
※情感分析系統?
※每周論文清單:知識圖譜,文本匹配,圖像翻譯,視頻對象分割
※NLP情感分析|流程概述(一)
※文本數據的情感分析