如何用Python和R對故事情節作情緒分析?

想知道一部沒看過的影視劇可否符合本身口味,卻又怕被劇透?不要緊,咱們能夠用情緒分析來了解故事情節是否足夠跌宕起伏。本文一步步教你如何用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種情緒,分別爲:

  1. 憤怒(anger)
  2. 期待(anticipation)
  3. 厭惡(disgust)
  4. 恐懼(fear)
  5. 喜悅(joy)
  6. 悲傷(sadness)
  7. 驚訝(surprise)
  8. 信任(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的運行環境了。

清理

咱們首先須要清理文本數據,完成如下這兩個任務:

  1. 把與劇情正文無關的內容去除;
  2. 將數據轉換成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"
複製代碼

這幅圖一會兒變得清晰,也值得琢磨。

在這一集的結尾,多種情緒混雜交織——歡快的氣氛陡然降低,期待與信任在波動,厭惡在不斷上漲,恐懼與悲傷陡然上升,憤怒突破天際,交雜着數次的驚訝……

你可能會納悶兒,情緒怎麼可能這麼複雜?是否是分析又出問題了?

還真不是,這一集的故事,有個另外的名字,叫作《紅色婚禮》。

收穫

經過本文的學習,但願你已初步掌握了以下技能:

  1. 如何用Python對網絡摘取的文本作處理,從中找出正文,而且去掉空行等內容;
  2. 如何用數據框對數據進行存儲、表示與格式轉換,在Python和R中交換數據;
  3. 如何安裝和使用RStudio環境,用R Notebook作交互式編程;
  4. 如何利用tidytext方式來處理情感分析與情緒分析;
  5. 如何設置本身的停用詞表;
  6. 如何用ggplot繪製多維度切面圖形。

掌握了這些內容後,你是否以爲用這麼強大的工具分析個劇本找影視做品,有些大炮轟蚊子的感受?

討論

除了本文介紹的方法以外,你還知道哪些方便的情緒分析工具與方法?在尋找新劇方面,你有什麼獨家心得體悟?有了情緒分析這個利器,你還能夠處理哪些有趣的問題?歡迎留言,記錄下你的思考,分享給你們。咱們一塊兒交流討論。

喜歡請點贊。還能夠微信關注和置頂個人公衆號「玉樹芝蘭」(nkwangshuyi)

若是你對數據科學感興趣,不妨閱讀個人系列教程索引貼《如何高效入門數據科學?》,裏面還有更多的有趣問題及解法。

相關文章
相關標籤/搜索