Python入門トップページ


単語感情極性対応表を用いた感情分析:目次

  1. MeCab で形態素解析を実行する
  2. 単語感情極性対応表を確認する
  3. 見出し語の重複を無視して作成した辞書で感情を分析する
  4. 見出し語の重複を考慮した辞書を作成する
  5. 見出し語の重複を考慮して作成した辞書で感情を分析する
  6. 読みをカタカナに変換した辞書でスコアを取得する
  7. 見出し語と読みの情報を利用して感情を分析する

Python で感情分析をしてみよう

単語感情極性対応表を用いた感情分析

見出し語と読みの情報を利用して感情を分析する

単語感情極性表では見出し語「ホーム」に対して読みで「ホームラン」が登録されていたり,見出し語「大和」に対して読みで「やまとなでしこ」が登録されていたことから,うまく値を取り出すことができていませんでした.前のページでの作業の結果,読みの情報は「ホームラン」や「ヤマトナデシコ」などカタカナに統一されているはずです.

これまでの作業の結果,見出し語に重複のない「嬉しい」などは,見出し語の辞書から値が取得できていました.また,見出し語に重複のある「ホーム」についても読みも一致するものを探して値を取得できていました.このページでは「ホームラン」や「大和撫子」の感情極性値も取得できるようにします.具体的には,見出し語に存在しない場合には,読みの辞書から検索します.このとき,読みの「ホームラン」で検索したときに,見出し語の「ホーム」が3文字であるので,読み「ホームラン」の先頭3文字と一致することを確認して感情極性値を取得することにします.このような考え方を利用することで,読み「タ」(助動詞)で見出し語「田」の値を取得しないようにできます.つまり,「開催され【】」の読み「タ」は,「タ」を検索して得られる見出し語「田」ではないことを意味しています.一方で,読み「ヤマトナデシコ」で検索した見出し語「大和」と「大和撫子」の先頭2文字とは一致するので値を参照します.

ここでは,最も単純な方法として,文書の感情は各単語について求められた単語感情極性値の平均によって求めることにします.このとき,Pandas の mean() では空値を除いた平均が求められていることに注意してください.次に示す通り,「本日ホームで開催された試合ではエースが良く投げて大和撫子のホームランで勝利した」という文章について感情分析を行った結果は -0.0311 となりました.この感情分析結果は負の値になっていますが,すでに確認した通り単語感情極性値の平均が -0.32 であること,第3四分位点(75%点)が -0.18 であることも考慮して考察を行うとよいでしょう.


import MeCab as mc
import pandas as pd
import numpy as np
import os

def hiragana2katakana(text):
    """
    ひらがなをカタカナに変換
    """

    # カタカナの Unicode範囲 0x30A1 (ァ) から 0x30F6 (ヶ)
    # ひらがなの Unicode範囲 0x3041 (ぁ) から 0x3096 (ゖ)
    katakana_start = ord("ァ")
    hiragana_start = ord("ぁ")

    # ひらがなをカタカナに変換するための辞書
    hiragana2katakana_map = {
        chr(i): chr(i - hiragana_start + katakana_start) for i in range(hiragana_start, ord("ゖ") + 1)
    }
    # 次を有効にすると辞書の内容を確認できます
    # print(hiragana2katakana_map)

    # str.translate を使用して変換
    return text.translate(str.maketrans(hiragana2katakana_map))


def get_lemma_score(dic_lemma, lemma):
    """
    原型で検索して感情スコアを返す
    """
    results = dic_lemma.get(lemma, {})
    if len(results) == 1:
        return next(iter(results.values()))
    elif len(results) > 1:
        # 「ホーム」などは複数の読みで登録されている
        return results.get(lemma, np.nan)
    return np.nan


def get_reading_score(dic_reading, reading, lemma):
    """
    読みで検索して感情スコアを返す
    ただし,読みが同じで,原型については辞書に登録された原型の文字数分が同じであるものだけ
    つまり,「ホームラン」の「ホーム」が一致する場合はスコアを返すが,「タ」や「デ」についてはスコアを返さない
    """
    results = dic_reading.get(reading, {})
    if len(results) == 1:
        key, score = next(iter(results.items()))
        if key == lemma[:len(key)]:
            return score
    return np.nan


def get_sentiment_score(dic_lemma, dic_reading, text):
    """
    形態素解析した後に,単語感情極性実数値を取得し,辞書のリスト形式で返す
    """
    t = mc.Tagger()  # for Windows
    # t = mc.Tagger('-r /dev/null -d /opt/homebrew/lib/mecab/dic/ipadic')  # for macOS
    node = t.parseToNode(text)
    words = []
    while(node):
        if node.surface != "":  # ヘッダとフッタを除外
            pos = node.feature.split(",")[0] # pos (part_of_speech) 品詞
            lemma = node.feature.split(",")[6]  # lemma 原型
            reading = node.feature.split(",")[7] # reading 読み
            score = get_lemma_score(dic_lemma, lemma)
            if np.isnan(score):
                score = get_reading_score(dic_reading, reading, lemma)
            w = {
                'surface': node.surface, # 表層形
                'lemma': lemma,
                'reading': reading,
                'pos': pos,
                'score': score
            }
            words.append(w)

        node = node.next
        if node is None:
            break
    return words


# 単語感情極性対応表
path_dic = os.path.sep.join(['dic', 'pn_ja.dic'])
df_pn = pd.read_csv(path_dic, encoding="sjis", sep=":", names=["lemma", "reading", "pos", "score"])

# 読みをカタカナに変換
df_pn['reading'] = [hiragana2katakana(v) for v in df_pn['reading'].tolist()]

# データフレームを辞書に変換
dic_lemma = df_pn.groupby('lemma').apply(lambda x: dict(zip(x['reading'], x['score']))).to_dict()
dic_reading = df_pn.groupby('reading').apply(lambda x: dict(zip(x['lemma'], x['score']))).to_dict()

sent = "本日ホームで開催された試合ではエースが良く投げて大和撫子のホームランで勝利した"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
# print(results)

# 辞書からデータフレームを作成
df = pd.DataFrame(results)
# 単語感情極性値の平均値を文の感情分析結果とする
print(f"{df['score'].mean():7.4f} : {sent}")
df
-0.0311 : 本日ホームで開催された試合ではエースが良く投げて大和撫子のホームランで勝利した
2024-sentiment-18

なお,「ホームラン」の感情極性値 -0.199562 を正しく取得できていることがわかりました.さらに「大和撫子」についても -0.255471 という正しい値を取得できています.

2024-sentiment-19

以上のとおり,概ね感情極性値を取得できたようです.しかしながら,見出し語「ない」については読みも「ない」で重複した「形容詞」と「助動詞」の2つの値が登録されています(【サンプルコード1】の42行目と45行目).このあたりの処理については引き続き検討必要かもしれません.

上では単語感情極性値の平均で文の感情を分析しました.値が負に偏っているのことについては様々な方法で補正が可能でしょう.実際の利用にあたっては,次のいくつかの文章について,否定の形式がどのように取り扱われているのか,「ありません」の形態素解析結果がどのようになっているのか,なども含めて検討するべきでしょう.


sent = "悲しいです"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
-0.9991 : 悲しいです
  surface lemma reading  pos     score
0     悲しい   悲しい    カナシイ  形容詞 -0.999102
1      です    です      デス  助動詞       NaN

sent = "悲しくない"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
-0.9995 : 悲しくない
  surface lemma reading  pos     score
0     悲しく   悲しい    カナシク  形容詞 -0.999102
1      ない    ない      ナイ  助動詞 -0.999997

sent = "全然嬉しくない"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
-0.2203 : 全然嬉しくない
  surface lemma reading  pos     score
0      全然    全然    ゼンゼン   副詞 -0.659821
1     嬉しく   嬉しい    ウレシク  形容詞  0.998871
2      ない    ない      ナイ  助動詞 -0.999997

sent = "嬉しくない"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
-0.0006 : 嬉しくない
  surface lemma reading  pos     score
0     嬉しく   嬉しい    ウレシク  形容詞  0.998871
1      ない    ない      ナイ  助動詞 -0.999997

sent = "嬉しくありません"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
 0.9989 : 嬉しくありません
  surface lemma reading  pos     score
0     嬉しく   嬉しい    ウレシク  形容詞  0.998871
1   ありません   有馬線   アリマセン   名詞       NaN

sent = "嬉しいです"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
 0.9989 : 嬉しいです
  surface lemma reading  pos     score
0     嬉しい   嬉しい    ウレシイ  形容詞  0.998871
1      です    です      デス  助動詞       NaN

sent = "とても嬉しいです"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
 0.4149 : とても嬉しいです
  surface lemma reading  pos     score
0     とても   とても     トテモ   副詞 -0.169067
1     嬉しい   嬉しい    ウレシイ  形容詞  0.998871
2      です    です      デス  助動詞       NaN

sent = "目茶苦茶嬉しいです"
results = get_sentiment_score(dic_lemma, dic_reading, sent)
df = pd.DataFrame(results)
print(f"{df['score'].mean():7.4f} : {sent}")
print(df)
 0.2721 : 目茶苦茶嬉しいです
  surface lemma reading  pos     score
0    目茶苦茶  目茶苦茶  メチャクチャ   名詞 -0.454634
1     嬉しい   嬉しい    ウレシイ  形容詞  0.998871
2      です    です      デス  助動詞       NaN

更に複数の文を連続して感情分析してみましょう.


sents = [
    "本日ホームで開催された試合ではエースが良く投げて大和撫子のホームランで勝利した",
    "悲しいです",
    "悲しくない",
    "全然嬉しくない",
    "嬉しくない",
    "嬉しくありません",
    "嬉しいです",
    "とても嬉しいです",
    "目茶苦茶嬉しいです",
    "今日は胸糞悪い",
    "今日は胸糞が悪い",
    "今日は胸糞が悪く堪忍袋の緒が切れた",
    "どうか気を悪くしないでください",
    "外柔内剛かつ開放的な性格です",
    "業績は燦然と輝いています",
    "堂々たる姿で自信たっぷりだ",
    "感情分析のプログラミングなんて屁の河童です",
    "成績発表が待ち遠しいです",
    "涼しい顔で仕事に没頭しています",
    "やっつけ仕事でやりたい放題でした",
    "彼にはやさしさがありました",
    "彼には優しさがありました",
    "彼に優しさがなかったわけではない"
]
for sent in sents:
    results = get_sentiment_score(dic_lemma, dic_reading, sent)
    df = pd.DataFrame(results)
    print(f"{df['score'].mean():7.4f} : {sent}")
-0.0311 : 本日ホームで開催された試合ではエースが良く投げて大和撫子のホームランで勝利した
-0.9991 : 悲しいです
-0.9995 : 悲しくない
-0.2203 : 全然嬉しくない
-0.0006 : 嬉しくない
 0.9989 : 嬉しくありません
 0.9989 : 嬉しいです
 0.4149 : とても嬉しいです
 0.2721 : 目茶苦茶嬉しいです
-0.7645 : 今日は胸糞悪い
-0.7645 : 今日は胸糞が悪い
-0.6031 : 今日は胸糞が悪く堪忍袋の緒が切れた
-1.0000 : どうか気を悪くしないでください
-0.1179 : 外柔内剛かつ開放的な性格です
 0.6796 : 業績は燦然と輝いています
-0.1210 : 堂々たる姿で自信たっぷりだ
-0.3814 : 感情分析のプログラミングなんて屁の河童です
-0.3488 : 成績発表が待ち遠しいです
-0.1914 : 涼しい顔で仕事に没頭しています
-0.1479 : やっつけ仕事でやりたい放題でした
-0.9358 : 彼にはやさしさがありました
 0.0261 : 彼には優しさがありました
-0.4870 : 彼に優しさがなかったわけではない

目次に戻る