LoginSignup
1
0

More than 1 year has passed since last update.

食べログとSNSの評価を比較してみた [その3]~感情分析~

Last updated at Posted at 2021-10-09

お読みいただきありがとうございます!
本記事は[その3]となります。

以下4記事がまとめて3分で読める!
[3分で読める] 食べログとSNSの評価を比較してみた~ショート編~

簡単なあらすじ(リンク付き!)

4.ツイートデータの感情分析

感情分析

感情分析を行う上で現在、ネット上には様々な辞書が公開されています。
今回はよく目にする、単語感情極性値対応表と日本語評価極性辞書を用いてPN値を計測していきます!

「単語感情極性値対応表」

単語感情極性値対応表」を利用してみます!

コード一覧はこちら
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7

import json
import pandas as pd
import numpy as np
import re
import csv
import MeCab
import matplotlib.pyplot as plt
import traceback
from google.colab import drive 
drive.mount('/content/drive')
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler

# 岩波国語辞書のダウンロード
import urllib.request
import zipfile
# URLを指定
url = "https://storage.googleapis.com/tutor-contents-dataset/6050_stock_price_prediction_data.zip"
save_name = url.split('/')[-1]
# ダウンロードする
mem = urllib.request.urlopen(url).read()
# ファイルへ保存
with open(save_name, mode='wb') as f:
    f.write(mem)
# zipファイルをカレントディレクトリに展開する
zfile = zipfile.ZipFile(save_name)
zfile.extractall('.')

# MeCabインスタンスの作成.引数を無指定にするとIPA辞書になります.
m = MeCab.Tagger('')

# テキストを形態素解析し辞書のリストを返す関数
def get_diclist(text):
    parsed = m.parse(text)      # 形態素解析結果(改行を含む文字列として得られる)
    lines = parsed.split('\n')  # 解析結果を1行(1語)ごとに分けてリストにする
    lines = lines[0:-2]         # 後ろ2行は不要なので削除
    diclist = []
    for word in lines:
        l = re.split('\t|,',word)  # 各行はタブとカンマで区切られてるので
        d = {'Surface':l[0], 'POS1':l[1], 'POS2':l[2], 'BaseForm':l[7]}
        diclist.append(d)
    return(diclist)

# 形態素解析結果の単語ごとのdictデータにPN値を追加する関数
def add_pnvalue(diclist_old, pn_dict):
    diclist_new = []
    for word in diclist_old:
        base = word['BaseForm']        # 個々の辞書から基本形を取得
        if base in pn_dict:
            pn = float(pn_dict[base]) 
        else:
            pn = 'notfound'            # その語がPN Tableになかった場合
        word['PN'] = pn
        diclist_new.append(word)
    return(diclist_new)

# 各ツイートのPN平均値
def get_mean(dictlist):
    pn_list = []
    for word in dictlist:
        pn = word['PN']
        if pn!='notfound':
            pn_list.append(pn)
    if len(pn_list)>0:
        pnmean = np.mean(pn_list)
    else:
        pnmean=0
    return pnmean

df_tw = pd.read_csv('/content/drive/MyDrive/aidemy/ブログ_食べログ/tweet/csv after/(216)銀座ウエスト.csv',
                 usecols=['text', 'name','created_at'])
df_tw = pd.DataFrame(df_tw)
df_tw.index +=1
df_tw = df_tw[["text"]]

# 岩波国語辞書の読み込み
pn_df = pd.read_csv('./6050_stock_price_prediction_data/pn_ja.csv', encoding='utf-8', names=('Word','Reading','POS', 'PN'))
#word_listにリスト型でWordを格納
word_list = list(pn_df['Word'])
#pn_listにリスト型でPNを格納
pn_list   = list(pn_df['PN'])
#pn_dictとしてword_list, pn_listを格納した辞書を作成
pn_dict   = dict(zip(word_list, pn_list))

# means_listという空のリストを作りそこにツイートごとの平均値を求めてください。
means_list = []
for tweet in df_tw["text"]:
    dl_old = get_diclist(tweet)
    dl_new = add_pnvalue(dl_old, pn_dict)
    pnmean = get_mean(dl_new)
    means_list.append(pnmean)
df_tw['pn'] = means_list

結果として下記のようにPN値が取れました!

pn_1.png

このままの状態だと、全体的にネガティブにPN値が偏ってしまっているため標準化を行います。
また、今回食べログと比較するため評価を0~5の間で行うため
正規化にて値を0~5に設定します。

# 標準化
st = StandardScaler()
df_tw["pn_st"] = st.fit_transform(df_tw[["pn"]])

# 正規化(0-5)
sc = MinMaxScaler([0,5])
df_tw["pn_sc"] = sc.fit_transform(df_tw[["pn_st"]])

#並び替え
df_tw = df_tw.sort_values(by='pn_sc', ascending=False)

そして、それぞれが得た評価が以下のようになります。
左から初期PN値、標準化PN値、正規化PN値
x軸にrate(評価)、y軸がcount(数)となっています。
(2.28)銀座ウエスト (1).png

まさかのグラフの形に変化はありませんでした。
しかし、それぞれの平均値を求めると以下のようになり、
標準化したpn_stは限りなく0の値に近いため正しい値と評価しました。

pn = -3.824546e-01
pn_st = -7.812681e-16
pn_sc = 2.280216e+00

ツイート評価

これを全ての店舗に行った結果を以下に記載します。
※最終評価のみ食べログに合わせ、小数点第2位まで

店名 ツイート数 PN PN_ST PN_SC
キル フェ ボン 1914 -3.642925e-01 -5.508191e-16 1.59
ピエール・エルメ 786 -3.908941e-01 8.864834e-16 1.52
ブルーボトルコーヒー 565 -3.901178e-01 -1.493397e-17 1.53
ケンズカフェ東京 567 -3.918743e-01 1.918904e-16 1.53
マリアージュフレール 429 -3.826067e-01 -3.592050e-16 1.55
ひみつ堂 303 -4.918648e-01 -2.139836e-16 1.62
資生堂パーラー 308 -4.918648e-01 -2.139836e-16 1.62
和栗や 265 -4.077040e-01 -1.675808e-17 1.68
アンリ・シャルパンティエ 226 -3.617992e-01 3.320844e-16 1.62
銀座ウエスト 216 -3.824546e-01 -7.812681e-16 2.28

なんということでしょう!
軒並み評価が1点台、Twitterには厳しい評価が多いのでしょうか?
ツイート数によって評価に変化もないので、テキストを取り出して見てみます。

テキストの確認

事例1
「秋だし和栗やのモンブラン食べに行くかー」
こんなにも常連を匂わせるツイートが0点です。。。

{'Surface': '秋', 'POS1': '名詞', 'POS2': '一般', 'BaseForm': '秋', 'PN': -0.9467959999999999}
{'Surface': 'だ', 'POS1': '助動詞', 'POS2': '*', 'BaseForm': 'だ', 'PN': 'notfound'}
{'Surface': 'し', 'POS1': '助詞', 'POS2': '接続助詞', 'BaseForm': 'し', 'PN': 'notfound'}
{'Surface': '和栗', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '和栗', 'PN': 'notfound'}
{'Surface': 'や', 'POS1': '助詞', 'POS2': '並立助詞', 'BaseForm': 'や', 'PN': -0.278384}
{'Surface': 'の', 'POS1': '助詞', 'POS2': '連体化', 'BaseForm': 'の', 'PN': 'notfound'}
{'Surface': 'モンブラン', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': 'モンブラン', 'PN': 'notfound'}
{'Surface': '食べ', 'POS1': '動詞', 'POS2': '自立', 'BaseForm': '食べる', 'PN': -0.9683370000000001}
{'Surface': 'に', 'POS1': '助詞', 'POS2': '格助詞', 'BaseForm': 'に', 'PN': 'notfound'}
{'Surface': '行く', 'POS1': '動詞', 'POS2': '自立', 'BaseForm': '行く', 'PN': -0.961267}
{'Surface': 'か', 'POS1': '助詞', 'POS2': '副助詞/並立助詞/終助詞', 'BaseForm': 'か', 'PN': 'notfound'}
{'Surface': 'ー', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '*', 'PN': 'notfoundn'}

事例2
「和栗やさんのモンブラン本当に美味しかったよねええ」
今度は逆に高得点(3.65)のテキストです。

{'Surface': '和栗', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '和栗', 'PN': 'notfound'}
{'Surface': 'や', 'POS1': '助詞', 'POS2': '並立助詞', 'BaseForm': 'や', 'PN': -0.278384}
{'Surface': 'さん', 'POS1': '名詞', 'POS2': '接尾', 'BaseForm': 'さん', 'PN': -0.598002}
{'Surface': 'の', 'POS1': '助詞', 'POS2': '連体化', 'BaseForm': 'の', 'PN': 'notfound'}
{'Surface': 'モンブラン', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': 'モンブラン', 'PN': 'notfound'}
{'Surface': '本当に', 'POS1': '副詞', 'POS2': '一般', 'BaseForm': '本当に', 'PN': 'notfound'}
{'Surface': '美味しかっ', 'POS1': '形容詞', 'POS2': '自立', 'BaseForm': '美味しい', 'PN': 0.99136}
{'Surface': 'た', 'POS1': '助動詞', 'POS2': '*', 'BaseForm': 'た', 'PN': 'notfound'}
{'Surface': 'よ', 'POS1': '助詞', 'POS2': '終助詞', 'BaseForm': 'よ', 'PN': 'notfound'}
{'Surface': 'ねえ', 'POS1': '助詞', 'POS2': '終助詞', 'BaseForm': 'ねえ', 'PN': 'notfound'}
{'Surface': 'え', 'POS1': 'フィラー', 'POS2': '*', 'BaseForm': 'え', 'PN': 'notfound'}

この2つを比較し、キーポイントとなりそうな点がnotfoundです。
SNSから取り出した文章のため、話言葉が多く、ほとんど認識ができていません。
認識されたものの中で、
高得点のテキストには「'美味しい', 'PN': 0.99136」
低得点のテキストには「'秋', 'PN': -0.9467959999999999」「 '食べる', 'PN': -0.9683370000000001」
があり、評価が正当かと言われるとあまりそのような気がしません。。。
参考までに、ネットの情報では「単語感情極性値対応表」は
ポジ:ネガ=1:9でネガティブに著しい偏りがあるとのことでした。
算出された結果も納得できます。

「日本語評価極性辞書」

続いて「日本語評価極性辞書(名詞偏)」を利用して調査してみます。
参考にした記事を記載しておきます!
3. Pythonによる自然言語処理 5-4. 日本語文の感情値分析[日本語評価極性辞書(名詞編)]

コード一覧はこちら
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7

import json
import pandas as pd
import numpy as np
import re
import csv
import MeCab
import matplotlib.pyplot as plt
import traceback
from google.colab import drive 
drive.mount('/content/drive')
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler

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

pndic_1 = pd.read_csv('/content/drive/MyDrive/aidemy/ブログ_食べログ/tweet/pn.csv.m3.120408.trim', names=['word_pn_oth'])
#区切り文字で展開
pndic_2 = pndic_1['word_pn_oth'].str.split('\t', expand=True)
#感情極性値のノイズを削除
pndic_3 = pndic_2[(pndic_2[1] == 'p') | (pndic_2[1] == 'e') | (pndic_2[1] == 'n')]
#感情極性値を数値に置換
pndic_4 = pndic_3.drop(pndic_3.columns[2], axis=1)
pndic_4[1] = pndic_4[1].replace({'p':1, 'e':0, 'n':-1})

word_list = list(pndic_4[0].tolist())
pn_list   = list(pndic_4[1].tolist())
pn_dict   = dict(zip(word_list, pn_list))

# MeCabインスタンスの作成
m = MeCab.Tagger('')

# テキストを形態素解析し辞書のリストを返す
def get_diclist(text):
    parsed = m.parse(text)     
    lines = parsed.split('\n')  
    lines = lines[0:-2]        
    diclist = []
    for word in lines:
        l = re.split('\t|,',word) 
        d = {'Surface':l[0], 'POS1':l[1], 'POS2':l[2], 'BaseForm':l[7]}
        diclist.append(d)
    return(diclist)

# 形態素解析結果の単語ごとのdictデータにPN値を追加
def add_pnvalue(diclist_old, pn_dict):
    diclist_new = []
    for word in diclist_old:
        base = word['BaseForm']       
        if base in pn_dict:
            pn = float(pn_dict[base]) 
        else:
            pn = 'notfound'            
        word['PN'] = pn
        diclist_new.append(word)
    return(diclist_new)

# 各ツイートのPN平均値
def get_mean(dictlist):
    pn_list = []
    for word in dictlist:
        pn = word['PN']
        if pn!='notfound':
            pn_list.append(pn)
    if len(pn_list)>0:
        pnmean = np.mean(pn_list)
    else:
        pnmean=0
    return pnmean

#ツイートの読み込み
df_tw = pd.read_csv('/content/drive/MyDrive/aidemy/ブログ_食べログ/tweet/csv after/(786)ピエールエルメ.csv',
                 usecols=['text', 'name','created_at'])
df_tw = pd.DataFrame(df_tw)
df_tw.index +=1
df_tw = df_tw[["text"]]

#ツイートごとの平均値
means_list = []
for tweet in df_tw["text"]:
    dl_old = get_diclist(tweet)
    dl_new = add_pnvalue(dl_old, pn_dict)
    pnmean = get_mean(dl_new)
    means_list.append(pnmean)
df_tw['pn'] = means_list

# 標準化
st = StandardScaler()
df_tw["pn_st"] = st.fit_transform(df_tw[["pn"]])

# 正規化(0-5)
sc = MinMaxScaler([0,5])
df_tw["pn_sc"] = sc.fit_transform(df_tw[["pn_st"]])

#並び替え
df_tw = df_tw.sort_values(by='pn_sc', ascending=False)

先ほどと異なる点は、先に辞書をダウンロードして、CSVを呼び出している点です。
得られた結果は下記。
※先ほど同様に「銀座ウエスト」のツイートを使用

左から初期PN値、標準化PN値、正規化PN値
B(3.10)銀座ウエスト.png

先ほど同様、標準化したpn_stは限りなく0の値に近いため正しい値と評価しました。

pn = 2.391093e-01
pn_st = -9.457455e-17
pn_sc = 3.097773e+00

先ほどよりかなりポジティブな評価が増え、比較対象になりそうです!
しかし、今回の結果はグラフ中央の「pn_sc = 2.5」の値が大半を占めています。
調べてみましょう!

テキストの確認

事例1
「秋だし和栗やのモンブラン食べに行くかー」

{'Surface': '秋', 'POS1': '名詞', 'POS2': '一般', 'BaseForm': '秋', 'PN': 0.0}
{'Surface': 'だ', 'POS1': '助動詞', 'POS2': '*', 'BaseForm': 'だ', 'PN': 'notfound'}
{'Surface': 'し', 'POS1': '助詞', 'POS2': '接続助詞', 'BaseForm': 'し', 'PN': 'notfound'}
{'Surface': '和栗', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '和栗', 'PN': 'notfound'}
{'Surface': 'や', 'POS1': '助詞', 'POS2': '並立助詞', 'BaseForm': 'や', 'PN': 'notfound'}
{'Surface': 'の', 'POS1': '助詞', 'POS2': '連体化', 'BaseForm': 'の', 'PN': 'notfound'}
{'Surface': 'モンブラン', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': 'モンブラン', 'PN': 'notfound'}
{'Surface': '食べ', 'POS1': '動詞', 'POS2': '自立', 'BaseForm': '食べる', 'PN': 'notfound'}
{'Surface': 'に', 'POS1': '助詞', 'POS2': '格助詞', 'BaseForm': 'に', 'PN': 'notfound'}
{'Surface': '行く', 'POS1': '動詞', 'POS2': '自立', 'BaseForm': '行く', 'PN': 'notfound'}
{'Surface': 'か', 'POS1': '助詞', 'POS2': '副助詞/並立助詞/終助詞', 'BaseForm': 'か', 'PN': 'notfound'}
{'Surface': 'ー', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '*', 'PN': 'notfound'}

事例2
「和栗やさんのモンブラン本当に美味しかったよねええ」

{'Surface': '和栗', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': '和栗', 'PN': 'notfound'}
{'Surface': 'や', 'POS1': '助詞', 'POS2': '並立助詞', 'BaseForm': 'や', 'PN': 'notfound'}
{'Surface': 'さん', 'POS1': '名詞', 'POS2': '接尾', 'BaseForm': 'さん', 'PN': 'notfound'}
{'Surface': 'の', 'POS1': '助詞', 'POS2': '連体化', 'BaseForm': 'の', 'PN': 'notfound'}
{'Surface': 'モンブラン', 'POS1': '名詞', 'POS2': '固有名詞', 'BaseForm': 'モンブラン', 'PN': 'notfound'}
{'Surface': '本当に', 'POS1': '副詞', 'POS2': '一般', 'BaseForm': '本当に', 'PN': 'notfound'}
{'Surface': '美味しかっ', 'POS1': '形容詞', 'POS2': '自立', 'BaseForm': '美味しい', 'PN': 'notfound'}
{'Surface': 'た', 'POS1': '助動詞', 'POS2': '*', 'BaseForm': 'た', 'PN': 'notfound'}
{'Surface': 'よ', 'POS1': '助詞', 'POS2': '終助詞', 'BaseForm': 'よ', 'PN': 'notfound'}
{'Surface': 'ねえ', 'POS1': '助詞', 'POS2': '終助詞', 'BaseForm': 'ねえ', 'PN': 'notfound'}
{'Surface': 'え', 'POS1': 'フィラー', 'POS2': '*', 'BaseForm': 'え', 'PN': 'notfound'}

単語感情極性値対応表の際にも調べたテキストです。
今回はどちらも同様の評価(pn_sc = 2.5)をつけていました。
理由としては、認識できる名詞がなく、ほとんど「notfound」となっていました。
唯一認識された「秋」は「0」の評価のため、認識できるものは正しく判別していると評価しました。
※単語感情極性値対応表の際は「-0.9467959999999999」

しかし、「pn_sc = 2.5」が増えてしまうと結果が2.5付近に偏ってしまう可能性があります。
そのため、今回は「2.5 = notfound」とみなし、全て消去します。

結果は以下になります。

pn = 0.491882
pn_st = 0.560676
pn_sc = 3.729705

標準化後の数値が少し、正に偏りがありますが、今回は無視します。
他の店舗の結果は以下。
※pn_st/pn_scの値は「notfound」を消去する前の値を記載

比較

店名 ツイート数 PN PN_ST 処理前_SC 処理後_SC 単語感情極性値対応表使用時 後 - 前
キル フェ ボン 1914 1.79E-01 3.89E-15 2.95 3.60 1.59 0.65
ピエール・エルメ 786 1.87E-01 -1.84E-15 2.97 3.60 1.52 0.63
ブルーボトルコーヒー 565 2.39E-01 2.68E-15 3.10 3.78 1.53 0.68
ケンズカフェ東京 567 1.94E-01 2.96E-15 2.99 3.41 1.53 0.42
マリアージュフレール 429 2.65E-01 -2.13E-15 3.16 3.71 1.55 0.55
ひみつ堂 303 1.45E-01 1.38E-15 2.86 3.65 1.62 0.79
資生堂パーラー 308 2.00E-01 -1.25E-15 3.00 3.60 1.62 0.60
和栗や 265 2.66E-01 -1.79E-15 3.16 3.61 1.68 0.45
アンリ・シャルパンティエ 226 2.55E-01 -7.86E-17 3.14 3.90 1.62 0.76
銀座ウエスト 216 2.39E-01 -9.46E-17 3.10 3.73 2.28 0.63

結果よりわかること

  • 全数値「notfound処理後」数値上昇したことにより「notfound」によって低い評価に影響されていた
  • ツイート数と「notfound」の関係はなし
  • 単語感情極性値対応表より店舗ごとの評価に差ができた
  • グラフから分かるように「日本語評価極性辞書」の方が明らかに「notfound」が多い。これは適当に評価を下していないこととともに、認識できる名詞が少ないことが言える。

各々の分布は次回[その4]に記載あり。

今後の展望として、今回使用した「日本語評価極性辞書」には用言編もあるため
「notfound」として判別されなかったものはこちらで評価できるのか試してみたい。

続く!
[その4]
5.食べログ評価の取得(2021年09月29日)
6.比較

1
0
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
1
0