LoginSignup
40
52

More than 5 years have passed since last update.

R+RMeCabで感情分析

Last updated at Posted at 2017-08-20

感情分析

テキスト分析の1つに、単語の感情表現がポジティブなのかネガティグなのかを判定する方法がある。テキスト中に出現する単語の感情特性を調べることで、テキスト全体がポジティブな内容を表現しているのか、ネガティグな内容なのかを判定することが可能かもしれない(単純に考えれば)。

こうした分析を Sentiment Analysyis(感情分析)という。

本稿では R 上で日本語テキストの感情分析を行なう方法を解説する。
ここでは歴代首相の所信表明演説をデータとして分析を試行するが、何らかの結論を導くことではなく、Rで感情分析を実行する上で想定される様々な操作を紹介するのが目的である。

感情分析のためには、単語ごとに感情特性値を割り当てた表が必要となるが、いくつかの辞書が公開されている。これらの辞書では、単語ごとに感性評価をプラスないしマイナス、あるいは正負いずれかの数値を対応させていることが多い。そこで、文章中に出現した単語の感性評価値を集計することで、そのテキストの内容が肯定的なのか否定的なのかを知るヒントになる。

ただし、たとえば「面白くない」という単語は「面白い」と「ない」に分解されるが、このフレーズ全体はマイナスな評価になる。ところが、単純にこのフレーズを評価すると「面白い」と「ない」のそれぞれ別々に完成評価の値を与えてしまう。例えば以下で利用する辞書では、それぞれの完成評価値は以下のようになっている。

> dic %>% filter(V1 %in% c("面白い") )
      V1         V2     V3       V4
1 面白い おもしろい 形容詞 0.989199
> dic %>% filter(V1 %in% c("ない") )
    V1   V2     V3        V4
1 ない ない 形容詞 -0.999882
2 ない ない 助動詞 -0.999997

「面白くない」の評価として、それぞれの値を合算すると相殺されて、ほぼ 0 となってしまう。このように、正しくテキストを評価しようとすると文脈や係り受けを考慮することが必須である。この点を強調した上で、以下、形態素解析の結果と、感性評価辞書との照合する方法を紹介しよう。

辞書の読み込み

まず感情表現を定義した辞書をダウンロードしてRに読み込む。

ここでは東京工業大学の高村研究室で公開されている PN Table を利用させてもらう。

公開されている辞書はいわゆる Shift-Jis でエンコードされているのだが、ここでは筆者の環境(Linux)での文字コードである UTF-8 に変換する。なお fileEncoding = "CP932" はファイルの文字コード指定だが、後の処理のためにエンコードを明示的に encoding = "UTF-8"と指定している(後者の指定は不要だと思うが dplyr がエラーを起こすことがある)。

dic <- read.table("http://www.lr.pi.titech.ac.jp/~takamura/pubs/pn_ja.dic", 
   sep = ":", stringsAsFactors = FALSE, 
   fileEncoding = "CP932", encoding = "UTF-8")

辞書の冒頭部分を示す。

 head(dic)
#        V1       V2     V3       V4
#1   優れる すぐれる   動詞 1.000000
#2     良い     よい 形容詞 0.999995
#3     喜ぶ よろこぶ   動詞 0.999979
#4   褒める   ほめる   動詞 0.999979
#5 めでたい めでたい 形容詞 0.999645
#6     賢い かしこい 形容詞 0.999486

ここで必要なのは単語名と感情特性値だけなので 2 列だけを抽出したオブジェクトをあらたに dic2 として用意する。さらに単語列については後で単語・文書行列と照合する都合上、後者にあわせて列名を TERM と変更する。

library(dplyr)
library(magrittr)
dic2 <- dic %>% select(V1, V4) %>% rename(TERM = V1)
dic2 %<>% distinct(TERM, .keep_all = TRUE)

ただし、実は感情特性値辞書では読み仮名が重要な意味を持つのだが、ここでは簡単のため重複する行は削除している。以下のように複数の特性値が与えられている場合、行の順番で上にある特性値が使われる。もとの辞書で確認してみよう。

dic %>% filter(V1 == "海")
#  V1   V2   V3        V4
#1 海 うみ 名詞 -0.337843
#2 海 かい 名詞 -0.958041

対象テキスト

戦後の総理大臣の所信表明演説で使用された単語それぞれの感情特性値を求めてみよう。

GitHub 上に yuukimiyo氏 が、過去の総理大臣所信表明演説データをまとめて公開されているので利用させてもらう。

ダウンロードしたファイル群がカレントディレクトリ以下の data/prime/utf8 フォルダにまとめられているとする。

RMeCab で単語・文書行列を生成する。この辺りはRによるテキストマイニング入門を参照してください。ここでは抽出する単語を名詞と動詞に限定し、さらに品詞細分類が"数", "サ変接続","接尾","非自立" と判定されている単語はすべて削除した。

なおデフォルトの列名はテキストファイル名になっているので、これも切り詰めて短かくする。

library(RMeCab)
## ここでは名詞と形容詞に限定して抽出する
prime <- docDF("data/prime/utf8/",pos = c("名詞","形容詞"),type = 1)
## 品詞細分類によるフィルタリング
prime %<>% filter(!POS2 %in% c("数", "サ変接続","接尾","非自立"))
## 列名は短縮化
library(stringr)
colnames(prime)  %<>% str_replace("_general-policy-speech.txt", "")
colnames(prime)  %<>% str_replace("(\\d{4})\\d{4}_(\\d{3})", "\\1_\\2")
#####  dplyr 0.7以降ならば以下でも
## prime %<>% rename_at(vars(matches("_general-policy-speech.txt")), funs(str_replace(., "_general-policy-speech.txt", "")))
## prime %<>% rename_at(vars(matches("\\d{4}\\d{4}_\\d{3}")), funs(str_replace(., "(\\d{4})\\d{4}_(\\d{3})", "\\1_\\2")))

単語・文書行列と感情特性辞書の照合

単語・文書行列である prime の単語列(TERM) を、辞書 (dic) の同名の列と照合し、特性値を新規列として追加する。辞書に存在しない単語は特性値が NA になる。

## 辞書との照合
prime2 <- prime %>% left_join(dic2)

もっともネガティブな単語と、もっともポジティブな単語を確認してみる。

prime2 %>% select(TERM, V4) %>% arrange(V4) %>% head(10)
#       TERM        V4
#1      悪い -1.000000
#2      ない -0.999882
#3    苦しい -0.999788
#4      寒い -0.999383
#5  下らない -0.999355
#6      狭い -0.999342
#7    険しい -0.999327
#8    悲しみ -0.999241
#9    悲しい -0.999102
#10     弊害 -0.998975
#
prime2 %>% select(TERM, V4) %>% arrange(desc(V4)) %>%
     head(10)
#     TERM       V4
#1    良い 0.999995
#2    功績 0.999104
#3      賞 0.998943
#4  嬉しい 0.998871
#5    喜び 0.998861
#6      徳 0.998745
#7    才能 0.998699
#8    適切 0.998406
#9    適切 0.998406
#10   幸い 0.997862

ポジティブな単語とネガティブな単語の割合を確認してみよう。

prime2 %>% summarize( sum (V4 > 0, na.rm = T))
#  sum(V4 > 0, na.rm = T)
#1                    366
prime2 %>% summarize( sum (V4 < 0, na.rm = T))
#  sum(V4 < 0, na.rm = T)
#1                   2585

ネガティブな単語の数が圧倒的に多い。ここでの試行は文脈を一切考慮していないのだが、あるいは総理大臣の所信表明演説というのは、危機や課題を煽るという側面があるのかもしれない。

なお抽出された単語のうち2000語程度には特性値が割り当てられてない(つまり NA となっている)。

特性値がもっとも低い演説と、高い演説

所信表明演説ごとに単語の出現回数の合計にその特性値を乗じ、その値がもっとも低い(つまりネガティブな)所信表明と、高い(つまりポジティブな)演説を確認してみる。

## 各行(単語)の出現頻度にその特性値を乗じてしまう
prime2 %<>% mutate_at(vars(matches("\\d{4}_")), funs (. * V4))

出力を以下に示すが、もっともネガティブと評価されたのは、民主党(当時)の鳩山由紀夫氏の演説で、もっとも(というか比較的)ポジティブなのは岸信介氏の演説であった。

## 列(演説)ごとに特性値の合計を求めた、その最小値(最もネガティグ)
## 最小値(最もネガティブ)
prime2 %>% summarize_at(vars(matches("\\d{4}_")), 
    sum, na.rm = T) %>% {
     score <- min(.)
     file  <- which.min(.)
     c(score, file)
 }
#                         2009_173_hatoyama-yukio 
#              -567.2011                 74.0000 

## 最大値(最もポジティブ)
prime2 %>% summarize_at(vars(matches("\\d{4}_")), 
    sum, na.rm = T) %>% {
     score <- max(.)
     file  <- which.max(.)
     c(score, file)
 }
#                           19570227_26_kishi-nobusuke 
#                 -33.8733                    5.00000 

文脈を無視した単純な集計であるが、それぞれの演説のスコアを少しだけ確認してみる。まずは岸元首相である。

prime2 %>% select(TERM, scores = matches("19570227_26_kishi-nobusuke")) %>% 
  arrange(scores) %>% head(20)
#   TERM    scores
#1  政治 -3.391612
#2  国民 -3.377144
#3  内閣 -2.507480
#4  責任 -1.754946
#5  石橋 -1.706068
#6  世界 -1.571984
#7  国会 -1.362568
#8  経済 -1.346894
#9  方針 -1.197560
#10 民族 -1.146350
#11 国際 -1.100146
#12 重い -0.934531
#13 結果 -0.905837
#14 国家 -0.782736
#15 主義 -0.765714
#16 歴史 -0.741408
#17 政府 -0.719487
#18 予算 -0.709204
#19 段階 -0.702633
#20 思い -0.678658

岸氏の演説ではポジティブな単語は9種類しか出現していない。

prime2 %>% select(TERM, scores = matches("19570227_26_kishi-nobusuke")) %>%
  filter (scores> 0) %>% arrange(scores) %>%
   head(20)
#    TERM     scores
#1     今 0.00530685
#2   当時 0.01510930
#3   機運 0.11110200
#4   青年 0.13756300
#5   平和 0.34187600
#6   信念 0.98295800
#7   全力 0.98616200
#8 正しい 0.99383600
#9   福祉 0.99521700

というか岸氏の演説はそもそも演説じたいが非常に短かい。最初の形態素解析の結果 (prime) で語数を確認してみよう(ただし、ここでは名詞と形容詞だけを抽出しているのだが)。

prime %>% select(TERM, scores = matches("19570227_26_kishi-nobusuke")) %>%
   filter (scores > 0) %>% count
# A tibble: 1 x 1
#      n
#  <int>
#1    77
## 参考までに鳩山由紀夫氏での名詞と形容詞の数を示す
prime %>% select(TERM, scores = matches("2009_173_hatoyama")) %>%
    filter(scores > 0) %>% count
# A tibble: 1 x 1
#      n
#  <int>
#1   694 

続いて鳩山首相である。まず否定的な単語を20語を確認してみる。

prime2 %>% select(TERM, scores = matches("2009_173_hatoyama")) %>% 
  filter(scores < 0) %>% arrange(scores) %>% 
    head(20) 
#   TERM     scores
#1  政治 -33.916120
#2  地域 -27.482506
#3  国民 -21.529293
#4  経済 -20.203410
#5  社会 -18.145908
#6  世界 -13.361864
#7    国 -11.165280
#8  問題 -10.034570
#9  ない  -9.998820
#10 全体  -9.620200
#11 制度  -7.611098
#12 必要  -7.393992
#13 医療  -6.965388
#14 行政  -6.355104
#15   日  -6.325011
#16 国家  -6.261888
#17 重要  -6.040170
#18 子供  -5.893128
#19 事業  -5.650000
#20   核  -5.550820

どうも政治がらみの単語はマイナスの評価になるようだが、感性辞書で確認してみると次のようになっている。

dic %>% filter(V1 %in% c("政治","国民","国") )
#    V1       V2   V3        V4
#1 国民 くにたみ 名詞 -0.422143
#2   国     くに 名詞 -0.558264
#3   国     こく 名詞 -0.612145
#4 国民 こくみん 名詞 -0.616755
#5 政治   せいじ 名詞 -0.847903

鳩山由紀夫元首相は演説において最初から最後まで「国民の皆さま」というフレーズを多用しているが、本稿ではこれらの値を単純に合計しているため、マイナスの値が大きくなっていることがわかる。

続いて、鳩山演説におけるポジティブな単語を確認しよう。

prime2 %>% select(TERM, scores = matches("2009_173_hatoyama")) %>% 
  filter(scores > 0) %>% arrange(scores) %>%
   tail(20)
#       TERM    scores
#35 チャンス  0.977160
#36     強力  0.977674
#37     誇り  0.978966
#38 生易しい  0.980027
#39     利便  0.981406
#40     奨学  0.982610
#41     友好  0.984706
#42     若者  0.986356
#43   優しい  0.987973
#44   正しい  0.993836
#45     福祉  0.995217
#46     早い  1.879402
#47       愛  1.968540
#48     公平  1.979672
#49     喜び  1.997722
#50     価値  2.691330
#51       絆  3.585984
#52     全力  3.9

実は、鳩山氏の所信表明演説ではポジティブな言葉も多用されているのであった。ちなみに鳩山演説でポジティブな単語の数は 55 個出現していた。以下、やや冗長だが、すべて確認してみよう。

prime2 %>% select(TERM, scores = matches("2009_173_hatoyama")) %>%
 filter(scores > 0) %>% count()
# A tibble: 1 x 1
#      n
#  <int>
#1    54
prime2 %>% select(TERM, scores = matches("2009_173_hatoyama")) %>% 
    filter(scores > 0) 
#       TERM      scores
#1  チャンス  0.97716000
#2      不信  0.03600030
#3    不退転  0.56207200
#4      主役  0.96655100
#5      人材  3.98819600
#6        今  0.04245480
#7      今日  0.26450000
#8      価値  2.69133000
#9      保健  0.34925500
#10   優しい  0.98797300
#11     先般  0.00253267
#12     全力  3.94464800
#13     公平  1.97967200
#14       利  0.93575400
#15     利便  0.98140600
#16     効果  0.93820500
#17     勇気  0.44665600
#18     友好  0.98470600
#19     友愛  0.22215090
#20     喜び  1.99772200
#21     国境  0.01841160
#22   奥深い  0.96639700
#23     奨学  0.98261000
#24     実効  0.07672280
#25     尊厳  0.38152000
#26     平和  0.85469000
#27     強力  0.97767400
#28       志  0.06754340
#29     急務  0.33508800
#30     意欲  0.96544800
#31 意気込み  0.20333100
#32       愛  1.96854000
#33     懸案  0.08140020
#34   新しい 12.42589600
#35     早い  1.87940200
#36     景気  0.88891000
#37     有名  0.81253000
#38   正しい  0.99383600
#39     正確  0.97461000
#40     民需  0.00406311
#41     活力  0.71996700
#42     無料  0.11757300
#43     無血  0.04974880
#44 生易しい  0.98002700
#45     真剣  0.00522682
#46     福祉  0.99521700
#47     究極  0.97567800
#48       絆  3.58598400
#49     維新  0.17313140
#50     自明  0.14774200
#51     若者  0.98635600
#52     衛生  0.96903800
#53     誇り  0.97896600
#54     高齢  0.10394820

ちなみに歴代の総理大臣所信表明演説中に出現したポジティブな単語数をカウントしてみる。トップ30を以下に示す。

 prime2 %>% select(matches("\\d{4}")) %>% 
   gather(key = WORK, value=V4) %>% 
   group_by (WORK) %>% filter (V4 > 0)  %>% 
    count() %>% arrange(desc(n)) %>% print(n=30)
# A tibble: 82 x 2
# Groups:   WORK [82]
#                          WORK     n
#                        <chr> <int>
# 1       2000_150_mori-yoshiro    58
# 2     2009_173_hatoyama-yukio    54
# 3         2006_165_abe-shinzo    49
# 4  1986_107_nakasone-yasuhiro    48
# 5  1994_130_murayama-tomiichi    48
# 6          2010_174_kan-naoto    48
# 7       2000_149_mori-yoshiro    46
# 8    19631018_44_ikeda-hayato    45
# 9  1983_100_nakasone-yasuhiro    45
#10 2004_161_koizumi-jyunichiro    44
#11         1989_114_uno-sosuke    43
#12      1990_119_kaifu-toshiki    43
#13  1995_134_murayama-tomiichi    43
#14       1998_143_obuchi-keizo    43
#15       1994_129_hata-tsutomu    42
#16  1996_139_hashimoto-ryutaro    42
#17  1985_103_nakasone-yasuhiro    39
#18  1993_127_hosokawa-morihiro    39
#19  1994_131_murayama-tomiichi    39
#20  1997_141_hashimoto-ryutaro    39
#21     19661215_53_sato-eisaku    38
#22  1993_128_hosokawa-morihiro    38
#23       2007_168_fukuda-yasuo    38
#24     2012_181_noda-yoshihiko    38
#25     19651013_50_sato-eisaku    37
#26      1991_121_kaifu-toshiki    37
#27          2010_176_kan-naoto    37
#28     2011_178_noda-yoshihiko    37
#29     19650730_49_sato-eisaku    36
#30    19801003_93_suzuki-zenko    36
# ... with 52 more rows
> 

森喜朗元首相の演説にポジティブな単語が多く出現しているらしい。意外なのか、成る程なのか。。。

それぞれの演説で感情特性が判定された語のうちの、ポジティブな単語の割合を割合を求めてみる。

```{r}
## ポジティブな単語数が占める割合
## 2つ一時オブジェクトを生成
## ポジティブな単語数のデータフレームを作成
 prime2 %>% select(matches("\\d{4}")) %>%
   gather(key = WORK, value=V4) %>% group_by (WORK) %>% 
filter (V4 > 0)  %>% count()  -> x
## ネガティブな単語数のデータフレームを生成
prime2 %>% select(matches("\\d{4}")) %>% 
   gather(key = WORK, value=V4) %>% group_by (WORK) %>% 
   filter (V4 < 0)  %>% count()  -> y
## 2 つの_データフレームを結合して割合を求める
x %>% full_join(y, by = "WORK") %>%
   mutate(RATIO = n.x / (n.x + n.y)) %>% 
   arrange(desc(RATIO) )
# A tibble: 82 x 4
# Groups:   WORK [82]
#                          WORK   n.x   n.y     RATIO
#                         <chr> <int> <int>     <dbl>
# 1 19561116_25_hatoyama-ichiro    19    86 0.1809524
# 2     19691201_62_sato-eisaku    33   170 0.1625616
# 3     19661215_53_sato-eisaku    38   196 0.1623932
# 4  19590625_32_kishi-nobusuke    19   101 0.1583333
# 5  19570227_26_kishi-nobusuke     9    49 0.1551724
# 6 19531130_18_yoshida-shigeru    14    77 0.1538462
# 7     19671205_57_sato-eisaku    32   177 0.1531100
# 8     19650730_49_sato-eisaku    36   204 0.1500000
# 9      1990_119_kaifu-toshiki    43   265 0.1396104
#10         1989_114_uno-sosuke    43   267 0.1387097
# ... with 72 more rows

*注*ちなみにすぐ上の処理では一時オブジェクトを2つ生成したが、これを避けるには以下のようする。

 prime2 %>% select(matches("\\d{4}")) %>% gather(key = WORK, value=V4) %>%
   group_by (WORK) %>% {
     x <- filter (., V4 > 0)  %>% count()
     y <- filter (., V4 < 0) %>% count()
    xy <- full_join(x, y, by = "WORK") %>% mutate(RATIO = n.x / (n.x + n.y))  
   } %>%  arrange(desc(RATIO) )

歴代演説でのポジティブ語とネガティブ語

最後に、歴代もっとも頻出したポジティブ語とネガティブ語をグラフで示す。

prime$SUM <- rowSums(prime[, 4:85])
prime %>% left_join(dic2) %>% select(TERM, SUM, V4) %>%  filter(V4 > 0) %>% arrange(desc(SUM)) %>% slice(1:10) -> positive
prime %>% left_join(dic2) %>% select(TERM, SUM, V4) %>%  filter(V4 <0 ) %>% arrange(desc(SUM)) %>% slice(1:10) -> negative

library(ggplot2)

rbind(positive, negative) %>% mutate(TERM = reorder(TERM, SUM) ) %>% ggplot(aes(TERM, SUM, fill = V4 > 0 )) +  geom_bar(stat = "identity") + coord_flip()

ggplot.png

Python での応用

Pythonで実行する方法を StatsBeginner: 初学者の統計学習ノート で解説されている。
【Python】MeCabと極性辞書を使ったツイートの感情分析入門
こちらも参照すると良いだろう。

40
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
52