LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

【初心者が】東京オリンピック2020に関するTweetを感情分析してみた!

Last updated at Posted at 2021-09-19

やったことの概要

  • 2021年7月1日から2021年9月3日までに投稿された東京オリンピックに関するTweetを収集
  • 収集したTweetデータをMeCabにより形態素解析
  • 極性辞書の参照によるTweet中の各単語の感情分析
  • 感情分析の時系列変化をグラフとして出力

やってみた理由

  • 今年の東京オリンピックは例年と異なり、世界的パンデミック禍での開催でした。そのため、ポジティブな感情だけでなくネガティブな感情も入り混じっていると思い、時系列での変化を見ると何か面白いことが分かるのではないかと思い解析してみました。
  • もともとTwitterの解析に興味があり、集めるTweet内容を変えれば様々なトピックに対しても応用できると思い、チャレンジしてみました。

私のプログラミングスキル

  • 非プログラマーの初心者です。
  • Pythonの勉強を初めて約5か月目です。(週末にまとめて勉強する派)
  • Aidemy Premium 自然言語処理コース(6か月)を受講中

開発環境

  • Windows
  • Python 3.8.6
  • PyCharm

実装

1. Tweetの収集

 Twitter APIを用いて、2021年7月1日から2021年9月3日までに投稿された東京オリンピックに関するTweetを収集しました。なので、まずはTwitter APIを取得するという下準備が必要です(ここが第一関門)。

 Twitter APIの取得については以下のようなサイトを参照しながら進めました(少し英作文が必要になります~~)。

2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説
TwitterAPIを申請して一発で承認されるまでの手順まとめ(例文あり)+APIキー、アクセストークン取得方法
Twitter APIキーとトークンの取得方法

無事にTwitter APIが取得できたらいよいよTweetの収集です!
今回はtweepyを用いて東京オリンピックに関するTweetを収集しています。

設定

  • q=['東京オリンピック'] or ["東京五輪"]:"東京オリンピック"または"東京五輪"という単語が含まれるTweetを取集
  • tweet_mode='extended': 140文字以上のTweetも収集できるよう設定
  • lang='ja': 日本語で投稿されたもののみを対象(他の言語が混ざると形態素解析が困難になるため)
  • count=100: 1回で100件のTweetを収集(countではMax 200件まで設定可能)
  • since='2021-09-03', until='2021-09-04':2021年9月3日から4日までの期間(1日の投稿数は100件を超えてくるので実際は3日のみが出力されました)
import tweepy
import csv

# 認証に必要なキーとトークン
API_KEY = "取得したAPI KEY"
API_SECRET = "取得したAPI_SECRET"
ACCESS_TOKEN = "取得したACCESS_TOKEN"
ACCESS_TOKEN_SECRET = "取得したACCESS_TOKEN_SECRET"

# APIの認証
auth = tweepy.OAuthHandler(API_KEY, API_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

# キーワードからツイートを取得(2021年9月3日のtweetを収集)
api = tweepy.API(auth)
tweet_data_list=[]

def search_tweet(api):
    tweet_data = api.search(q=['東京オリンピック'] or ["東京五輪"], tweet_mode='extended', lang='ja', count=100, since='2021-09-03', until='2021-09-04')
    for tweet in tweet_data:
        tweet_data_list.append([tweet.created_at, tweet.full_text.replace('\n', ' ')])

#繰り返し取得(より多くのTweetを自動で入手する場合に有効)
    next_max_id = tweet_data[-1].id
    for i in range(2, 101):
        tweet_data = api.search(q=['東京オリンピック'] or ["東京五輪"], tweet_mode='extended', lang='ja', count=100, max_id=next_max_id-1)
        next_max_id = tweet_data[-1].id
        for tweet in tweet_data:
            tweet_data_list.append([tweet.created_at, tweet.full_text.replace('\n', ' ')])

search_tweet(api)

#CSVファイルとして保存
with open('tweets_Tokyo Olympic_210903.csv', 'w', newline='', encoding='UTF_8_sig') as f:
    writer=csv.writer(f, lineterminator='\n')
    writer.writerow(["created_at","text"])
    writer.writerows(tweet_data_list)
pass

収集期間の設定を2021年7月1日から2021年9月3日まで変更していくことで、希望の期間でのTweetの収集ができます!
(もっと効率の良い方法があるのかもしれませんが、これだと各日のTweet数が約1万件集められるので、大量に集めたい人には良いかも?)

注意

  • 1回で収集できるTweet数に制限があるらしく、連続して実行するとエラーがでます。15分ほど置いてから実行するとうまくいきました。
  • APIで取得できるTweetは1週間前までのTweetのみになっていて、1週間以上前のTweetは収集できませんのでご注意を!

収集したTweetの形態素解析と感情分析

ついに本題の形態素解析と感情分析を行っていきます!
その前に前提知識の確認から入ります。

形態素解析とは

文章を機械で解析するためには、まずは単語ごとに分割する必要があります。その単語分割の手法の一つが形態素解析になります。

英語は単語ごとにスペースが入るので単語分割が簡単ですが、日本語のような言語の場合では分割は容易ではないので、いかに適切に形態素解析を行うかが、解析を行う上で重要になってきます。

とても難しそうな作業のようですが、すでに形態素解析のスペシャリストの方々が日本語の形態素解析ツールを用意してくれています!代表的なものが、MeCab, janomeでこの2つのいずれかを用いれば間違えないと思います。

今回は、MeCabを用いて形態素解析を行いました。

感情分析とは

自然言語処理という手法を用いて、テキストがポジティブな内容なのか、ネガティブな内容なのか、あるいはニュートラルな内容(ポジティブでもネガティブでもない)なのかを判断する技術のことです。

ポジティブかネガティブかを判断するのに用いるのが極性辞書と呼ばれるものです。各単語に-1から+1までのスコアが付いていて、-1に近いほどネガティブで、+1に近いほどポジティブになります。この極性辞書とTweetを形態素解析した結果を照らし合わせることで感情分析を行うことができます。

今回は、「岩波国語辞書(岩波書店)」を参考に作成された単語感情極性対応表を用いて感情分析を行いました。

形態素解析と感情分析の実装!

以下では、
「Tweetデータの形態素解析」

「極性辞書を用いることで形態素解析結果の各単語のPN値(-1 ~ +1のスコア)を調べ、各TweetのPN平均値を求めてリストに格納(感情分析)」

「格納したPN平均値のリストを標準化(極性辞書にはネガティブな意味合いの語彙が多く含まれるため、標準化して調整する)」

という流れで解析を進めています。

import MeCab
import re
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime

# 岩波国語辞書のダウンロード
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('.')


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[3]}
        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_tweets = pd.read_csv('./tweets_Tokyo Olympic_210701_210903.csv', header = 0, index_col=0)
df_tweets.index = pd.to_datetime(df_tweets.index)
df_tweets = df_tweets[['text']].sort_index(ascending=True)


#極性辞書の読み込み
pn_df = pd.read_csv('./6050_stock_price_prediction_data/pn_ja.csv', encoding='utf-8', names=('Word','Reading','POS', 'PN'))
word_list = list(pn_df["Word"])
pn_list = list(pn_df["PN"])
pn_dict = dict(zip(word_list, pn_list))
print(pn_list[0:10])

# means_listという空のリストを作りそこにツイートごとの平均値を求める
means_list = []
for tweet in df_tweets['text']:
    dl_old = get_diclist(tweet)
    dl_new = add_pnvalue(dl_old, pn_dict)
    pnmean = get_mean(dl_new)
    means_list.append(pnmean)
print(means_list[0:10])

# means_listをnumpy配列に変換
means_list = np.copy(means_list)

# means_listを用いて標準化を行う
x_std = (means_list - means_list.mean()) / means_list.std()
df_tweets['pn'] = x_std
df_tweets.index = pd.to_datetime(df_tweets.index)
df_tweets =  df_tweets.resample('D').mean().interpolate()

# df_tweets.csvという名前でdf_tweetsを出力
df_tweets.to_csv('./df_tweets.csv')

実際のTweet内容とPN値を確認してみる

コードを以下の様に修正し、各ツイートと標準化したPN値をCSVファイルとして出力しました。

# means_listを用いて標準化を行う
x_std = (means_list - means_list.mean()) / means_list.std()

#Tweetと標準化したPN値を出力
df_tweets_copy = df_tweets
df_tweets["pn"] = x_std
df_tweets_copy = df_tweets_copy.sort_values(by="pn", ascending=True)
df_tweets_copy.to_csv('./df_tweets_pn.csv', encoding="utf8")

出力したCSVの中で標準化したPN値の上位30位(最もポジティブな内容)と下位30位(最もネガティブな内容)のTweetをそれぞれ見てみると...

上位30位のTweeet
確かにポジティブな言葉(「いいね!」、「おめでとう」、「楽しかった」など)が多くみられることから、これらの単語によってPN値が高くなっているのかなと思います。(結構ニュースサイト公式Tweet系が多めですね)
best30.PNG

下位30位のTweet
「パンデミック」、「不正」などネガティブな内容のTweetが見られました。一方で、「東京オリンピックのドローンショウがやばいから見て!!!!!!」のようなポジティブそうな内容のTweetもネガティブに捉えられてしまっているものもいくつか含まれていました。(きっと「やばい」という単語をネガティブなものと捉えてしまったのでしょう...)
worst30.PNG

以上のように、一部スコアリングが不適切なものも含まれていましたが、おおよそポジティブ・ネガティブの判定が出来ているかなと思います。
次に時系列でこのPN値がどのように変化するのか見ていきたいと思います。
(一部判定を間違えるものも含まれることから、何かの傾向を見る場合には、やはりある一定量のデータを集めることが重要だと思いました。)

感情分析の時系列変化をグラフとして出力

さてようやく感情分析した結果をグラフとして出力してみたいと思います。
以下を実行しました。

x = df_tweets.index
y = df_tweets.pn
plt.plot(x,y)
plt.grid(True)
plt.show()

すると、以下のグラフが出力されました!(嬉しい~~!!)
Olympic tweet emotion analysis.png

コロナ新規感染者数のデータも重ねて表示

感情分析の結果を解釈する上で、やはりコロナの感染状況について参照することは必須かと思い、厚生労働省のオープンデータ(新規陽性者数の推移)から新規感染者数の生データを入手して、感情分析のグラフに重ねて表示しました。

#厚生労働省のオープンデータ
df = pd.read_csv("newly_confirmed_cases_daily.csv", encoding="UTF8")
#ALLのみ抽出(全国での新規感染者数のデータ抽出)
all_df = df[df["Prefecture"] == "ALL"].copy()

#Dateの値を日付として設定
all_df["Date"] = pd.to_datetime(all_df["Date"])
part_df = all_df[all_df["Date"] > datetime.datetime(2021,6,30)].copy()
part_df_1 = part_df[part_df["Date"] < datetime.datetime(2021,9,4)].copy()


# プロット
plt.figure(figsize=(10,8))
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

x = df_tweets.index
y = df_tweets.pn
y2 = part_df_1["Newly confirmed cases"]

ax1.plot(x, y, color="black")
ax1.set_ylabel("Emotion score")
ax2.plot(x, y2, color="red")
ax2.set_ylabel("COVID-19 Newly confirmed cases")
plt.grid(True)
plt.show()

その結果がこちら。
黒字:感情分析(プラス:ポジティブ、マイナス:ネガティブ)
赤字:全国の新規感染者前週比
Olympic tweet emotion analysis 6.PNG

結果の考察

感情分析の結果を東京オリンピックの出来事とコロナの状況と照らし合わせて、自分なりに解釈してみました。

- 東京オリンピック期間の前半はスコアがプラスの傾向だった

これは日本人選手のメダル獲得が大きく寄与しているのではないかと思っております。特に日本初金メダルの卓球混合ダブルスの試合があった7月26日にはスコアが最高点となっています。

- 東京オリンピック期間の後半はスコアが急激に下落した

新規感染者前週比の結果を見るとオリンピック期間中に急激に増加しているのが分かります。この感染者数の増加が日本人の感情をネガティブな方向にもっていった主な要因となったのではないかと推察します。

Olympic tweet emotion analysis 7.PNG

今後やってみたいこと

以上の考察が正しいのか検証するため、LDAによるトピック解析を行うことでより具体的にオリンピック期間中につぶやかれていたTweetの内容を解析してみたいなと思っています。

参考サイト

2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説
TwitterAPIを申請して一発で承認されるまでの手順まとめ(例文あり)+APIキー、アクセストークン取得方法
Twitter APIキーとトークンの取得方法
Twitterのつぶやきを一度に大量取得する
Janome v0.4 documentation (ja)
Python, Janomeで日本語の形態素解析、分かち書き(単語分割)
厚生労働省のオープンデータ(新規陽性者数の推移)
LDAによるトピック解析

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