3. Pythonによる自然言語処理　5-4. 日本語文の感情値分析［日本語評価極性辞書（名詞編）］

* 日本語の感情値辞書に関する試行の第2弾となります。
* 前回の[「単語感情極性値対応表」](https://qiita.com/y_itoh/items/8e63334058659fe2b4d3)に引き続き、本稿では**[「日本語評価極性辞書（名詞編）」](https://www.cl.ecei.tohoku.ac.jp/index.php?Open%20Resources%2FJapanese%20Sentiment%20Polarity%20Dictionary)**を利用させて頂くこととして、そのパフォーマンスを確認するとともに実用性を検討します。
* 当該の辞書は全 13,314 語を収録し、極性値はポジティブ (+1) とネガティブ (-1) の 2 値分類をベースとして、ポジネガの判定がつかないもの (0) を加えた 3 カテゴリとなっています。



#⑴ 「日本語評価極性辞書（名詞編）」の取得
####１. 辞書データを Colab 上にアップロード
* 当該の公式サイトからあらかじめローカルPCにダウンロードしておいた辞書ファイルを Colaboratory 上にアップロードします。

```python
from google.colab import files
uploaded = files.upload()
```
* アップロード操作用の UI に従ってファイルを選択するとアップロードが開始されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/38f2c427-f7b1-299d-9312-5709952b53b9.png)

####２. 辞書データをデータフレームに読み込む
* 「日本語評価極性辞書（名詞編）」は 1 行ずつ次の形式で登録されています。`語` \t `感情値[p,n,e]` \t `動詞句（事実/感情の属性）[客観, 主観]`

```python
import pandas as pd
# カラム名と値の位置ずれを制御
pd.set_option('display.unicode.east_asian_width', True)

pndic_1 = pd.read_csv('pn.csv.m3.120408.trim', names=['word_pn_oth'])
print(pndic_1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/31ab47a4-b5d2-9fce-db04-f4e3043673f5.png)

####３. データフレームを区切り文字で展開
* `'\t'` を区切り文字として複数列に分割し、引数に `expand=True` を指定することで結果をデータフレームとして取得します。

```python
pndic_2 = pndic_1['word_pn_oth'].str.split('\t', expand=True)
print(pndic_2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/b826e871-33c4-9195-7072-ce7c0b0c5693.png)

####４. 感情極性値のノイズを削除
* 感情極性値は原則として p(ポジティブ), n(ネガティブ), e(ニュートラル) のいずれかをとりますが、（2 値分類の難しさでしょう）要素ごとの出現数をカウントすると実際には次のようなデータの入り方をしています。

```python
senti_score = pd.Series(pndic_2[1])
senti_score.value_counts()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/46f3532e-9f83-db8b-fe1e-1d12c4943039.png)

* そこで p, n, e の 3 カテゴリだけを残し、それ以外はノイズとして削除します。

```python
pndic_3 = pndic_2[(pndic_2[1] == 'p') | (pndic_2[1] == 'e') | (pndic_2[1] == 'n')]
print(pndic_3)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/9668154a-401c-5635-db75-a367b96d80fc.png)


####５. 感情極性値を数値に置換
* 不要な 3 列目のカラムを削除し、感情極性値を p, n, e から数値 [p : +1, n : -1, e : 0] に置き換えます。

```python
# 不要カラムの削除
pndic_4 = pndic_3.drop(pndic_3.columns[2], axis=1)

pndic_4[1] = pndic_4[1].replace({'p':1, 'e':0, 'n':-1})
print(pndic_4)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/5497ec51-6903-fc62-37d5-940443cbde17.png)


####６. データフレームを dict 型に変換
* `dict()` で列 0 をキー、列 1 を値とする辞書型に変換し、感情値を取得する参照元とします。

```python
keys = pndic_4[0].tolist()
values = pndic_4[1].tolist()
dic = dict(zip(keys, values))

print(dic)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/1d33560c-92c6-2730-0737-4b72be65ca4a.png)

#⑵ 分析対象となるテキストの前処理
* 分析対象となるテキストをはじめとして、以降の処理は前回の[「単語感情極性値対応表」](https://qiita.com/y_itoh/items/8e63334058659fe2b4d3)を踏襲して比較対照したいと思います。

####１. テキストを指定
```python
text = 'スパゲッティの全国の輸入量が、ことし10月までに過去最多となり、税関は新型コロナウイルスの感染拡大の影響で増えた、いわゆる『巣ごもり需要』が背景にあるでのはないかとしています。横浜税関によりますと、全国の港や空港から輸入されたスパゲッティの量はことし10月末の時点でおよそ14万2000トンでした。これは、3年前の1年間の輸入量を4000トンほど上回り、過去最多となりました。また、マカロニもことし10月までの輸入量が1万1000トン余りに上り、過去最多だった4年前の1年間の輸入量とほぼ並んでいるということです。'

lines = text.split("。")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/4ec6a4ad-0801-c515-6e20-bbd6932c07c0.png)

####２. 形態素解析のインスタンスを生成
* MeCab をインストールします。

```python
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7
```
* MeCab をインポートし、出力モードを "-Ochasen" としてインスタンスを生成します。
* 一連の処理には関係しませんが、例として 1 行目の形態素解析結果を示します。

```python
import MeCab
mecab = MeCab.Tagger("-Ochasen")

# 1行目の形態素解析結果を例示
print(mecab.parse(lines[0]))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/0cae3770-ba27-cbe0-4ccb-7b2eb1400679.png)

####３. 形態素解析に基づいて文章単位にリスト化
* 当該の辞書は「名詞編」ですので、それ以外の単語に感情値はつきませんが、その辺りも観察することとして「単語感情極性値対応表」と同様に名詞・形容詞・動詞・副詞の4カテゴリを抽出します。

```python
word_list = []
for l in lines:
    temp = []
    for v in mecab.parse(l).splitlines():
        if len(v.split()) >= 3:
            if v.split()[3][:2] in ['名詞','形容詞','動詞','副詞']:
                temp.append(v.split()[2])
    word_list.append(temp)

# 空の要素を削除
word_list = [x for x in word_list if x != []]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/400d9400-1f9a-cb76-2851-1bfd7d7ccebd.png)


#⑶ 感情極性値による文章のポジネガ判定
####１. 感情極性値の取得
* 文章ごとに属する単語とその感情極性値を取得してデータフレームに出力してみます。

```python
result = []
# 文単位の処理
for sentence in word_list:
    temp = []
    # 語単位の処理
    for word in sentence:
        word_score = []
        score = dic.get(word)
        word_score = (word, score)
        temp.append(word_score)       
    result.append(temp)

# 文毎にデータフレーム化して表示
for i in range(len(result)):
    print(lines[i], '\n', pd.DataFrame(result[i], columns=["word", "score"]), '\n')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/61fb164c-5fca-7568-0b92-cc92b2713e8e.png)

* 全 4 行の結果を左から順に下表に示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/9bc53a86-aede-148a-6716-e2746f95629c.png)

* NaN は登録されていない単語で、前述のとおり名詞を収録した辞書なので当然の結果です。こうした未登録の問題は、いかに収録語数の多い辞書でも往々にして生じるものです。
* 感情極性値をもつ単語をみると、まず 1 行目の「ウイルス」「感染」がネガティブというのは頷けます。一方「量」「需要」は文脈によってネガティブとなる可能性も否めませんが、スパゲッティの特需を伝えるこのニュース記事においてはポジティブでマッチしています。なお、「過去」がネガティブというのは、後ろ向きとか後退的なイメージが伴うからでしょうか。
* さて、文章ごとのポジネガを判定するという目的において、名詞に限定された寡少な要素でどのような結果が求められるか見ていきます。

####２. 文章ごとの感情極性値の平均値
```python
# 文単位の平均値を計算
mean_list = []
for i in result:
    temp = []
    for j in i:
        if not j[1] == None:
            temp.append(float(j[1]))
    mean = (sum(temp) / len(temp))
    mean_list.append(mean)

# データフレーム化して表示
print(pd.DataFrame(mean_list, columns=["mean"], index=lines[0:4]))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/22bd7947-1790-c092-58d4-cde3f8234402.png)

* まず **1 行目（感情極性値 : -0.142857）**は、文章の前半で「需要の増加」というポジティブな事象が語られ、それには「ウイルスの感染拡大」というネガティブな背景があるという文脈になっています。つまり文章の後半で感情が反転し、判定もややネガティブの判定となっています。
* 次いで **2 行目（同 : 1.000000）**は「輸入量が14万2000トン」という需要増加の具体的な説明です。経済という視点からスパゲッティ単体を考えたとき、確かに好材料なのであって、判定も全くのポジティブとなっています。
* さらに **3 行目（同 : 0.000000）**は「輸入量は過去最多」という客観的事実ですからポジティブでもなくネガティブでもないということで、判定も全くのニュートラルとなっています。
* そして **4 行目（同 : 0.250000）**で話題はマカロニに転じまして、こちらも「輸入量がスパゲッティに次ぐ好調ぶり」ということで、判定もまたスパゲッティには及ばないもののポジティブとなっています。
* 意外なほど各々の文脈とマッチしている印象を受けます。しかし、なにぶん名詞のみで要素数が少なく、しかもポジネガは +1 か -1 の 2 値しかとりませんので、場合によっては文脈から大きく外れてしまう可能性もありましょう。


#⑷ 「日本語評価極性辞書（名詞編）」の検証
####１. ポジネガの構成比を確認する
* 「日本語評価極性辞書（名詞編）」から作成した dict 型データについて、ポジティブ/ネガティブ 及びニュートラルの割合を確認します。

```python
# ポジティブの語数
keys_pos = [k for k, v in dic.items() if v == 1]
cnt_pos = len(keys_pos)
# ネガティブの語数
keys_neg = [k for k, v in dic.items() if v == -1]
cnt_neg = len(keys_neg)
# ニュートラルの語数
keys_neu = [k for k, v in dic.items() if v == 0]
cnt_neu = len(keys_neu)

print("ポジティブ の割合:", ('{:.3f}'.format(cnt_pos / len(dic))), "(", cnt_pos, "語)")
print("ネガティブ の割合:", ('{:.3f}'.format(cnt_neg / len(dic))), "(", cnt_neg, "語)")
print("ニュートラル の割合:", ('{:.3f}'.format(cnt_neu / len(dic))), "(", cnt_neu, "語)")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/0cb25be6-cfad-4384-a766-78f598ffec67.png)

* ポジティブの感情極性値を持つ語は全体の 25.3% となっており、かたやネガティブは 37.4% とやはりネガティブ比率が高めになっていますが、前回[「単語感情極性値対応表」](https://qiita.com/y_itoh/items/8e63334058659fe2b4d3)ほどの極端な偏りはありません。
* また「単語感情極性値対応表」ではほとんど見られなかったニュートラル（52,671語中13語）の割合が、こちら「日本語評価極性辞書（名詞編）」ではネガティブと同率の 37.4% で共に全体の 1/3 強を占めています。もっとも「単語感情極性値対応表」は配点が -1 から +1 の実数値をとりますので、限りなくニュートラルに近いポジティブ/ネガティブに弁別されている為とも考えられます。

####２. 重複している語を確認する
* 辞書のデータをデータフレームから dict 型に変換する前後の要素数を比較します。

```python
print("dict型に変換前の要素数 :", len(pndic_4))
print("dict型に変換後の要素数 :", len(dic), "\n")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/9300455c-961a-0513-356c-3a2b9ad229bc.png)

* dict 型に変換した時点で 3 語少なくなっていますが、これは次のとおり変換前の一意な要素数に一致し、すなわち重複していた語数に当たります。

```python
pndic_list = pndic_4[0].tolist()
print("dict型に変換前の一意な要素数 :", len(set(pndic_list)))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/4abae62b-8860-eaac-cd12-027175e5123d.png)

* 重複していた 3 語の内容を見てみます。Pythonの標準ライブラリ `collections` の `Counter()` クラスをつかって出現回数が多い順に要素を取得します。

```python
import collections
print(collections.Counter(pndic_list))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/33a0c10c-691f-dae3-f83a-86dd66f9de28.png)

* 「規律」「信用」「帳消し」の 3 語ですが、元の「日本語評価極性辞書（名詞編）」には次のように登録されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502109/c76ca0d0-3cd0-f44b-1f09-641d0cd43a22.png)

* 「規律」はニュートラル/ポジティブの別があるとして 2 項目の登録となっています。穿った見方をすると「規律が乱れる」とか「規律が厳しい」といった場合はネガティブな文脈ともなり得ます。
* また「信用」「帳消し」はいずれもポジティブで、大まかに言えば行為としてのそれか、あるいは状態・存在としてのそれかという分類になっているようです。しかし、感情分析に用いる観点からすれば、どれもポジティブならば 1 項目で代表している方が望ましいといえます。
* [3. Pythonによる自然言語処理　5-5. 日本語文の感情値分析［日本語評価極性辞書（用言編）］](https://qiita.com/y_itoh/items/7c528a04546c79c5eec2)