想知道一部沒看過的影視劇可否符合本身口味,卻又怕被劇透?不要緊,咱們能夠用情緒分析來了解故事情節是否足夠跌宕起伏。本文一步步教你如何用Python和R輕鬆愉快完成文本情緒分析。一塊兒來試試吧。html
追劇是個使人苦惱的事情。python
就拿剛剛播完第7季的《權力的遊戲》來講,每週等的時候那叫一個煎熬,就盼着週一能提前到來。正則表達式
但是最後一集播完,你緊張、興奮、激動和過癮以後呢?是否是又以爲很失落?編程
由於——下面我該看什麼劇啊?數組
如今的影視做品,不是太少,而是太多。若是你有選擇困難症,更會有生不逢時的感受。bash
Netflix, Amazon和豆瓣等推薦引擎能夠給你推薦影視做品。可是它們的推薦,只是把觀衆劃分紅了許多個圈子。你的數據,若是足夠真實準確的話,可能恰好和某一個圈子的特性比較接近,因而就給你推薦這個圈子更喜歡的做品。微信
可是這不必定靠譜。有可能你的觀影和評價信息分散在不一樣的平臺上。不完整、不許確的觀影數據,會致使推薦的效果大打折扣。網絡
即使有了推薦的影視劇,它是否符合你的口味呢?畢竟看劇也是有機會成本的。放着《絕命毒師》不看,去看了一部爛劇,你的生命中的數十小時就這樣被浪費了。app
可除了從頭至尾看一遍,又如何能驗證一部劇是不是本身喜歡的呢?機器學習
你可能想到去評論區看劇評。那但是個危險區域,由於隨時都有被劇透的風險。
你以爲仍是利用社交媒體吧,在萬能的朋友圈問問好友。有的好友確實很熱心,但有的時候,也許會過於熱心。
例以下面這位(圖片來自於網絡):
你可能抓狂了,以爲這是個不可能完成的任務,就如同英諺所云:
You can't have your cake and eat it too.
真的是這樣嗎?不必定。在這個大數據氾濫,數據分析工具並不稀缺的時代,你徹底能夠利用技術幫本身選擇優秀的影視做品。
故事情節的文本,你能夠到互聯網上找劇本,或者是字幕。固然,不是讓你把劇本從頭讀到尾,那樣還不如直接看劇呢。你須要用技術來對文本進行分析。
咱們提到的這個技術,叫作情緒分析(emotional analysis)。它和情感分析(sentiment analysis)有類似之處。都是經過對內容的自動化分析,來得到結果。
情感分析的結果通常分爲正向(positive)和負向(negative),而情緒分析包含的種類就比較多了。
加拿大國家研究委員會(National Research Council of Canada)官方發佈的情緒詞典包含了8種情緒,分別爲:
有了這些情緒的標記,你能夠輕鬆地對一段文本的情緒變化進行分析。
這時候,你能夠回憶起中學語文老師講做文時說過的那句話:
文如看山不喜平。
故事情節會伴隨着各類情緒的波動。經過分析這些情緒的起伏,咱們能夠看出故事的基調是否符合本身的口味,情節是否緊湊等。這樣,你能夠根據本身的偏好,甚至是當前的心境,來選擇合適的做品觀看了。
咱們須要用到Python和R。這兩種語言在目前數據科學領域裏最受歡迎。Python的優點在於通用,而R的優點在於統計學家組成的社區。這些統計學家真是高產,也很酷,常常製造出使人驚豔的分析包。
我們這裏就用Python來作數據清理,而後用R作情緒分析,而且把結果可視化輸出。
咱們首先須要找到的是來源數據。做爲例子,咱們選擇了《權利的遊戲》第三季的第9集,名字叫作"The Rains of Castamere"。
你能夠到這個網址下載這一集的劇本。
你只須要全選頁面拷貝,而後打開一個文本編輯器,把內容粘貼進去。好了,如今你就有可供分析的文本了。
請創建一個工做目錄。後面的操做都在這個目錄裏進行。例如個人工做目錄是~/Downloads/python-r-emotion
。
把剛剛得到的文本文件放到這個目錄中。
咱們須要用到Jupyter Notebook,請安裝Anaconda套裝。具體的安裝方法請參考《 如何用Python作詞雲 》一文。
到這個網址下載R基礎安裝包。你會看到R的下載位置有不少。
我建議你選擇中國的鏡像,這樣鏈接速度更快。清華大學的鏡像就不錯。
請根據你的操做系統平臺選擇其中對應的版本下載。我選擇的是macOS版本,下載獲得pkg文件。雙擊就能夠安裝。
安裝了基礎包以後,咱們繼續安裝集成開發環境RStudio。下載地址爲這裏。
仍是依據你的操做系統狀況,選擇對應的安裝包。macOS安裝包爲dmg文件。雙擊打開後,把其中的RStudio.app圖標拖動到Applications文件夾中,安裝就完成了。
好了,如今你就有了R的運行環境了。
咱們首先須要清理文本數據,完成如下這兩個任務:
到你的系統「終端」(macOS, Linux)或者「命令提示符」(Windows)下,進入咱們的工做目錄,執行如下命令。
jupyter notebook
複製代碼
這時候工做目錄下還只有那個文本文件。
咱們打開看看內容。
往下翻頁,咱們找到了劇本正文正式開始的標記Opening Credits
。
翻到文本的結尾,咱們能夠看到劇本結束的標記End Credits
。
咱們回到主頁面下,新建一個Python的Notebook。點擊右方的New按鈕,選擇Python 2。
有了全新的Notebook後,咱們首先引入須要用到的包。
import pandas as pd
import 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"^$\n"
subst = ""
data = re.sub(regex, subst, data, 0, re.MULTILINE)
複製代碼
而後咱們再次打印。
print(data)
複製代碼
空行都已經成功挪走了。但是咱們注意到還有一些分割線組成的行,也須要去除掉。
regex = r"^-+$\n"
subst = ""
data = re.sub(regex, subst, data, 0, re.MULTILINE)
複製代碼
至此,清理工做已經完成了。下面咱們把文本整理成數據框,每一行分別加上行號。
利用換行符把本來完整的文本分割成行。
lines = data.split('\n')
複製代碼
而後給每一行加上行號。
myrows = []
num = 1
for 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()
複製代碼
## Joining, by = "word"
複製代碼
結果是豐富多彩的,惋惜看不大清楚。爲了區別不一樣情緒,咱們調用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)
複製代碼
## Joining, by = "word"
複製代碼
嗯,這張圖看着就舒服多了。
不過這張圖也會給咱們形成一些疑惑。按照道理來講,每一段落的內容裏,包含單詞數量大體至關。結尾部分情感分析結果裏面,正向和負向幾乎同時上升,這就讓人很不解。是這裏的幾行太長了,仍是出了什麼其餘的問題呢?
數據分析的關鍵,就是在這種使人疑惑的地方深挖進去。
咱們不妨來看看,出現最多的正向和負向情感詞都有哪些。
先來看看正向的。咱們此次不是按照行號,而是按照詞頻來排序。
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)
複製代碼
## Joining, by = "word"
## Joining, by = "word"
複製代碼
這幅圖一會兒變得清晰,也值得琢磨。
在這一集的結尾,多種情緒混雜交織——歡快的氣氛陡然降低,期待與信任在波動,厭惡在不斷上漲,恐懼與悲傷陡然上升,憤怒突破天際,交雜着數次的驚訝……
你可能會納悶兒,情緒怎麼可能這麼複雜?是否是分析又出問題了?
還真不是,這一集的故事,有個另外的名字,叫作《紅色婚禮》。
經過本文的學習,但願你已初步掌握了以下技能:
掌握了這些內容後,你是否以爲用這麼強大的工具分析個劇本找影視做品,有些大炮轟蚊子的感受?
除了本文介紹的方法以外,你還知道哪些方便的情緒分析工具與方法?在尋找新劇方面,你有什麼獨家心得體悟?有了情緒分析這個利器,你還能夠處理哪些有趣的問題?歡迎留言,記錄下你的思考,分享給你們。咱們一塊兒交流討論。
喜歡請點贊。還能夠微信關注和置頂個人公衆號「玉樹芝蘭」(nkwangshuyi)。
若是你對數據科學感興趣,不妨閱讀個人系列教程索引貼《如何高效入門數據科學?》,裏面還有更多的有趣問題及解法。