Web系toC向けサービスを開発している新卒一年目サーバーサイドエンジニアです。 バックエンドはGoで実装し、クラウドはGCPを使っています。 部署配属されてから半年が経ち、ようやく業務以外の技術・分野に目を向ける余裕が出てきたので、以前より興味のあったデータ分析に挑戦します。
概要
Twitter API と GCP Natural Languageを使って岸田総理のツイートを分析していきます
以下のステップで進めていきます
1.TwitterAPIを使って岸田総理のツイートを取得する
2.GCP Natural Language APIで岸田総理のツイートを感情分析する
3.Matplotlibで感情値をグラフとして可視化する
分析結果
本記事で使うAPI
はじめに
- 将来的に業務でPythonを使う可能性がある
- 学生時代、ゼミで統計を学んでいた為、データ分析は親和性が高いと感じた
データ分析って響きかっけぇええええええ
上記の理由からPythonを用いてデータ分析に挑戦することを決めました。
といっても使用するライブラリはPandas,Matplotlib+αなので、そこまで難しい内容ではありません。
私と同じように 「データ分析に挑戦(勉強)したいけど、何をしたら良いのかわからない」という方の参考になれればと思います。
用意するもの
以下の二つのAPIから必要なトークンやKeyを取得します。
- Twitter API
- Consumer Key
- Consumer Secret
- Access Token
- Accesss Token Secert
- GCP Natural Language API
- key
Twitter APIを使用するにはDeveloper申請が必要です。私の場合、こちらの記事を参考に取得しました。
開発環境
Jupyter Labを使用しました。Jupyter Notebookでも可能です。
環境構築がめんどくさい方は、Googleが提供しているColab(Colaboratory) がおすすめです。
ブラウザ上で Python を記述、実行できるGoogleのフルマネージドサービスになります。
1. TwitterAPIを使って岸田総理のツイートを取得する
tweepy を使用して特定ユーザーのタイムラインからデータを引っ張ります。
取得の際に、ツイッター名が必要になるので岸田総理のツイッターを探します。
居ました。オレンジ枠で囲った部分の文字列を控えます。これで準備は整いました。
では、さっそく岸田総理のツイートを取得していきます。
岸田総理(@kishida230)のツイートを取得
ツイート取得のプログラムコード
全コード
from requests_oauthlib import OAuth1Session
import datetime, time, sys
import pandas as pd
import tweepy
import csv
import re
consumer_key = '' # Consumer Keyを記載
consumer_secret = '' # Consumer Secretを記載
access_token = '' # Access Tokenを記載
access_secret = '' # Accesss Token Secertを記載
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
#ツイート格納するために空の配列を用意
tweet_data = []
tweets = tweepy.Cursor(
api.user_timeline,#特定ユーザーのツイートを取得
screen_name = '@kishida230',#岸田総理のアカウントを指定
exclude_replies = True,#リプライを除外する
include_rts = False,#リツイートを除外する
tweet_mode="extended" #ツイートを全文取得する
)
}
#ツイートに含まれる余分な情報を除外する為の関数
def create_net_url(text):
text = re.sub(r' ','\n',text)
text = re.sub(r'#','\n#',text)
text = re.sub(r'(\A|\n|[ -/:-@\[-~]|\s)#.+(\n|[ -/:-@\[-~]|\Z|\s)', "", text)
text = re.sub(r"(https?|ftp)(:\/\/[-_\.!~*\'()a-zA-Z0-9;\/?:\@&=\+\$,%#]+)", "" ,text)
net_text = re.sub("\n", "", text)
return net_text
#用意した配列に取得したツイートを格納する。 ID、日付、ツイートテキスト、いいね数、RT数を追加する
for tweet in tweets.items():
tweet_data.append([tweet.id,tweet.created_at+datetime.timedelta(hours=9),create_net_text(tweet.full_text.replace('\n','')),tweet.favorite_count,tweet.retweet_count])
tweet_data
#CVSとして書き出す
with open('@kishida230.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, lineterminator='\n')
writer.writerow(["id","date", "text", "fav", "rt"])
writer.writerows(tweet_data)
解説
まずは必要となるライブラリをインポートします。
from requests_oauthlib import OAuth1Session
import datetime, time, sys
import pandas as pd
import tweepy
import csv
import re
各ライブラリをインストールしていない場合は、pip install {ライブラリ名}
でインストールできます。
続いてTwitter Developerで取得した Token
、Key
情報を' '
の間に入力します
consumer_key = '' # Consumer Keyを記載
consumer_secret = '' # Consumer Secretを記載
access_token = '' # Access Tokenを記載
access_secret = '' # Accesss Token Secertを記載
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
APIを使うために、先程記入した情報を元に認証を行います。認証が成功するとAPIを使うことが可能になります。
#ツイートをDataframeとして格納するために、空の配列を用意
tweet_data = []
#岸田総理のツイートを取得する
tweets = tweepy.Cursor(
api.user_timeline,#特定ユーザーのツイートを取得
screen_name = '@kishida230',#岸田総理のアカウントを指定
exclude_replies = True,#リプライを除外する
include_rts = False,#リツイートを除外する
tweet_mode="extended" #ツイートを全文取得する
)
}
今回は岸田総理のタイムラインに投稿されたツイートを取得したかったので、 user_timeline
を使いました。他の関数を使うことで、検索ワードを含む投稿を取得したり、api経由でツイートをすることが可能になります。
#ツイートに含まれる余分な情報を除外する為の関数
def create_net_url(text):
text = re.sub(r' ','\n',text)
text = re.sub(r'#','\n#',text)
text = re.sub(r'(\A|\n|[ -/:-@\[-~]|\s)#.+(\n|[ -/:-@\[-~]|\Z|\s)', "", text)
text = re.sub(r"(https?|ftp)(:\/\/[-_\.!~*\'()a-zA-Z0-9;\/?:\@&=\+\$,%#]+)", "" ,text)
net_text = re.sub("\n", "", text)
return net_text
感情分析をするにあたり、不要な文字列はできる限り除去したいです。
ツイートには不要なurlやハッシュタグが含まれているので、正規表現で取り除く関数を作りました。
#用意した配列tweet_dataに取得したツイートを格納する。 ID、日付、ツイートテキスト、いいね数、RT数を追加する
for tweet in tweets.items():
tweet_data.append([
tweet.id, #ツイートidを追加
tweet.created_at+datetime.timedelta(hours=9),#ツイート日時を追加
create_net_text(tweet.full_text),#先程の正規表現の関数を使用してツイートテキストを追加
tweet.favorite_count,#いいね数を追加
tweet.retweet_count])#リツイート数を追加
Natural Languageに読み込ませる情報はテキストのみですが、今後RT数やいいね数を元に分析してみたいのでこちらも一緒に取得しました。ツイート日時は時系列データとして、indexに必要なので取得しています。
#CVSとして書き出す
with open('@kishida230.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, lineterminator='\n')
writer.writerow(["id","date", "text", "fav", "rt"])
writer.writerows(tweet_data)
後のデータ分析で使うため、CSVとして書き出します。
以上でユーザーのツイートをタイムラインから取得する処理が完成です。
取得したツイートを確認してみる
ツイート確認のプログラムコード
全コード
#書き出したCSVをDataframeとして取り出す
import pandas as pd
df_tweets = pd.read_csv('@kishida230.csv',index_col="date",parse_dates=True)
#Dataframeの情報を見る
df_tweets.info()
#テキストを昇順ソートして古いツイートを一番上に持ってくる
df_tweets = df_tweets.sort_index(ascending=True)
##先頭の5件を出力
df_tweets.head()
解説
岸田総理のツイート取得が完了したので、中身を確認していきます。
まずは取得期間、件数から見ていきます
データの情報を見る
#書き出したCSVをDataframeとして取り出す
import pandas as pd
df_tweets = pd.read_csv('@kishida230.csv',index_col="date",parse_dates=True)
pandasを使って先程取得したデータをDataframe(行と列を持つ 二次元データのPythonオブジェクト)として読み込みます。
##情報を出力
df_tweets.info()
{Dataframe名}.info()
で、データ情報(データ数、型、カラム、欠損値)を確認することができます。
[出力結果]
DatetimeIndex: 914 entries, 2020-04-13 19:39:52+00:00 to 2022-12-12 21:21:20+00:00
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 id 914 non-null int64
1 text 914 non-null object
2 fav 914 non-null int64
3 rt 914 non-null int64
dtypes: int64(3), object(1)
2020年04月13日~2022年12月12日
の間で、合計914
件のツイートを取得することができました。
では次に中身を見ていきます。
ツイートの中身を見る
#テキストを昇順ソートして古いツイートを一番上に持ってくる
df_tweets = df_tweets.sort_index(ascending=True)
##先頭の5件を出力
df_tweets.head()
現状、ソートしないとデータの始まりが 2022年からになります。
時系列データとして、Matplotlibで可視化する場合、先頭に一番古いツイートを持ってくる必要があるのでソートします。
また、{Dataframe名}.head()
をすることで最新のデータ5件を出力することが可能です。
以下出力結果
2020-04-13 19:39 いま日本だけでなく、世界全体が未曾有の危機に直面しています。まずは新型コロナウイルスの封じ込めのため、国民の命と暮らしを守る施策を政府は打ち出していますが、与党自民党としても、それらが迅速に実行されるようしっかりと声をあげて参ります。
2020-04-13 19:42 そして、これらが結果につながるためには、国民の皆様一人一人のご理解とご協力が欠かせません。この困難に打ち勝ち、明るい未来を迎えられるよう、共に頑張っていきましょう。
2020-04-14 10:47 静岡4区補欠選挙に立候補しました自民党公認・公明党推薦の「ふかざわ陽一」さん。宏池会事務総長だった望月義夫さんの遺志を受け継ぎ、即戦力として大いに期待されています。ぜひこの大切な選挙、「ふかざわ陽一」さんへのご支援をよろしくお願い致します。
2020-04-14 20:14 本日の衆議院本会議にてUAEとの投資協定等5つの協定を採決しました。今回より新型コロナウイルス対策として、密集状態を回避するため採決を除く本会議はA班とB班に分けて開催されます。私は出席しない日なので採決後に退席。その後国民年金法の趣旨説明質疑は院内中継を通じて党本部にて見ました。
2020-04-16 19:32 緊急事態宣言を全国に広げ、日本全国全ての皆様に更なるご協力をいただくにあたり、ご協力にしっかり報い、皆様の暮らしを守るために、自民党としても当初から訴えてきた10万円一律給付を前倒しで実施することを総理が決断しました。あとはスピード、全力で取り組みます。
問題なく取得できているのが確認できます。余計なurlやrtも含まれていません。
これで準備が整いました。次に、Natural Languageを使って、取得したツイートを感情分析していきます。
2. GCP Natural Languageで岸田総理のツイートを感情分析する
感情分析のプログラムコード
全コード
import requests
import json
key = '' #keyを記載
url = f'https://language.googleapis.com/v1beta2/documents:analyzeSentiment?key={key}'
def sentiment_analyse(text):
header = {'Content-Type' : 'application/json'}
body = {
"document":{
"type":"PLAIN_TEXT",
"language":"JA",
"content":text
}
}
response = requests.post(url, headers=header , json=body).json()
return response["documentSentiment"]["score"],response["documentSentiment"]["magnitude"]
#空のリストの配列score_list,magnitude_listにツイートごとのスコア、強度結果を格納する
score_list = []
magnitude_list =[]
for tweet in df_tweets['text']:
score = sentiment_analyse(tweet)[0]
magnitude = sentiment_analyse(tweet)[1]
score_list.append(score)
magnitude_list.append(magnitude)
df_tweets['score'] = score_list
df_tweets['magnitude'] = magnitude_list
#先頭のツイートを出力
df_tweets.head()
GCP Natural Language APIを試す
解説
import requests
import json
まずは必要なライブラリを読み込みます。インストールが未だの場合、先ほど同様にpip install {ライブラリ名}
でインストールできます。
key = '' #keyを記載
url = f'https://language.googleapis.com/v1beta2/documents:analyzeSentiment?key={key}'
GCPで取得したkeyと、apiを読み込むためのurlを記入します。末尾の{key}
に格納された情報が正しければapiを使うことができます。
def sentiment_analyse(text):
header = {'Content-Type' : 'application/json'}
body = {
"document":{
"type":"PLAIN_TEXT",
"language":"JA",
"content":text
}
}
response = requests.post(url, headers=header , json=body).json()
return response["documentSentiment"]["score"],response["documentSentiment"]["magnitude"]
引数に分析対象のテキストを入力すると、返り値として分析結果を返却する関数ができました。
やっていることは単純です。POSTリクエストで必要な情報、今回でいうとapiのリクエスト先url
、header
、body
、をJSON
形式でapiサーバーにリクエスト送信します。変数response
に、apiからのレスポンス情報を格納し、return
でレスポンスに含まれる感情値を指定して出力しています。
今回は score
、magnitude
の二つを感情値として出力します。
試しに岸田総理のタイムラインの中で、最近バズったツイートを読み込ませます。
tweet = "今年の漢字が発表されましたが、私の今年の漢字は「進」です。歴史を画するような様々な課題に対して、悪質な献金被害の救済新法や防衛力の抜本強化、新しい資本主義の具体化などを、一つ一つ進めており、また、来年も進めていきます。"
sentiment_analyse(tweet)
[出力結果]
(0.1, 0.7)
スコアの見方
- score: -1.0(ネガティブ)~1.0(ポジティブ)のスコアで感情が表されます。これは、テキストの全体的な感情の傾向に相当します。
- magnitude: 指定したテキストの全体的な感情の強度(ポジティブとネガティブの両方)が 0.0~+inf の値で示されます。 score と違って、magnitude は正規化されていないため、テキスト内で感情(ポジティブとネガティブの両方)が表現されるたびにテキストの magnitude の値が増加します。そのため、長いテキスト ブロックで値が高くなる傾向があります。
scoreが0.1
でmagnitudeが0.7
という結果になりました。
感情スコアは0に近いですが、強度の数値が若干高めに出力されています。
公式ドキュメントを調べると以下の説明が記載されていました。
magnitudeについて
score の値がニュートラル(ほぼ 0.0)なドキュメントは、感情的でない場合もあれば、ポジティブとネガティブの両方の値が高いために互いに相殺されている混合的なドキュメントである場合もあります。
(公式)Natural Language API の基本より引用
つまり、先程の岸田総理のツイートにはポジティブとネガティブなスコアが混在している可能性が高いことを表しています。
詳しく中身を見たい方は、エンティティ感情分析を使うことで、テキスト内で表現された感情(ポジティブかネガティブか)の特定を試みることができます。
岸田総理のツイート、いざ感情分析
それでは先程取得したツイート全件に対して感情分析を行なっていきます。
# 空のリストの配列score_list,magnitude_listにツイートごとのスコア、強度結果を格納する
score_list = []
magnitude_list =[]
for tweet in df_tweets['text']:
score = sentiment_analyse(tweet)[0]
magnitude = sentiment_analyse(tweet)[1]
score_list.append(score)
magnitude_list.append(magnitude)
df_tweets['score'] = score_list
df_tweets['magnitude'] = magnitude_list
score
とmagnitude
を格納するための、空の配列を用意します。df_tweets
のtext
をfor文で回し、それぞれの分析結果を、先程作成した配列に追加していきます。
解析した結果はそれぞれscore
、magnitude
カラムに格納しています。
少し中身を覗いてみます。
#先頭5件を出力
df_tweets.head()
score
、magnitude
共に結果が格納されているのが確認できます。
次に、岸田のツイートでもっともネガティブな内容とポジティブな内容をみていきます。
もっともネガティブなツイートをみる
pd.set_option("display.max_colwidth", 140)#ツイート文を全て表示する
df_tweets[["text","score"]].sort_values('score').head()
全体的に政治や世界情勢、自然災害の内容が上位を占めています。
ツイートを見る限り、感情スコア相当の内容となっているのが確認できます。
もっともポジティブなツイートをみる
df_tweets[["text","score"]].sort_values('score', ascending=False).head()
分析の結果、岸田総理から取得したツイートの期間で、もっともポジティブなスコアを叩き出したツイートは、内閣総理大臣就任の報告ツイート でした。残念ながら、この出来事を越すポジティブなツイートはされていませんでした。 また、素晴らしいことに我々日本国の代表は愛妻家だということが判明いたしました。
上位5件のうち、2件に表示されるほどの 「岸田総理の妻が作ったお好み焼き」、とても興味があります。
冗談はさておき.....先ほどとは打って変わり、政治や世界情勢の話は一切表示されなくなりました。
この部分を深掘りしたらさらに面白い結果が判明しそうな気もしますが、、、今回はここまでにします。
3. Matplotlibで感情値をグラフとして可視化する
①Score推移を折れ線で確認する
最後に、Natural Languageで分析した結果をmatlotlibを使い可視化していきます。
感情値可視化プログラムコード①
全コード
import matplotlib.pyplot as plt
%matplotlib inline
# 日付をx軸scoreをy軸にしてプロット。
x = df_tweets.index
y = df_tweets.score
plt.figure(figsize=(18, 5)) # 図のサイズを設定
plt.plot(x,y)
plt.grid(True)
plt.xlabel("score", fontsize=12) #x軸ラベル
plt.ylabel("date", fontsize=12) #y軸ラベル
plt.title("kishida_score")#yタイトルラベル
解説
①では折れ線グラフで感情値の動きをみていきます。
import matplotlib.pyplot as plt
%matplotlib inline
まずは必要となるライブラリを(以下同文)
# 日付をx軸scoreをy軸にしてプロット。
x = df_tweets.index
y = df_tweets.score
横軸xに日時データindex
、縦軸xにscore
を設定します。
plt.figure(figsize=(18, 5)) # 図のサイズを設定
plt.plot(x,y)
plt.grid(True)
見やすいように、出力結果のサイズを調整した後にplt.plot(x,y)
でグラフを出力します。
plt.grid(True)
を加えることでグラフにグリッドを描画できます。
plt.xlabel("date", fontsize=12) #x軸ラベル
plt.ylabel("score", fontsize=12) #y軸ラベル
plt.title("kishida_score")#yタイトルラベル
最後に、それぞれの軸に対してラベルを付与します。
以下出力結果。
全体的にポジティブなツイートの比率が高いですね。
特に、2021年9月から年末にかけてツイートの量が増えています。
同年10月14日に衆院議員が解散となりました。その後、総選挙が行われ、10月31日に投票日を迎えます。もともと衆院議員の任期は10月21日と決まっていたので、その後の総選挙に向けた、選挙活動としてツイートが活発になっていた可能性が高いです。
CSVより一部抜粋
2021-09-21 第24代自民党総裁として全国を行脚し、国民の皆さまの声を受け止め、野党に転落していた自民党を立て直した谷垣禎一先生。必ず勝利を果たすようにと、激励をいただきました。ありがとうございます。多くの仲間たちと力を合わせ #チーム岸田 で必ず勝ち抜いてまいります。
2021-09-17 #自民党総裁選 が告示されます。私は8月26日に、誰よりも早く出馬を表明し、3回の政策発表会見を行い、#岸田BOX にお寄せいただいた1万件超の声を受け止め、#岸田ノート に書き留めてきました。国民とともに歩むリーダーとして、皆さまの声をかたちに、信頼ある政治を必ず実現します。#チーム岸田
2021-09-15 #岸田BOX への回答ライブで「総理になったら中国に甘くなるのでは?」という質問。「全く逆だ」と答えました。外務大臣時代の2016年、副大臣だった木原誠二さん @kihara_seiji と一緒に北京の外相会談に臨み、朝から激しい議論を重ね、日本の立場を強く主張しました。その時の様子をお伝えしています。
②ネガポジ比率を円グラフで確認する
感情値可視化プログラムコード②
全コード
#ネガポジ分類
nega_posi_list = []
for score in df_tweets['score']:
if score == 0.0:
nega_posi_list.append('neutral')
elif score > 0.0:
nega_posi_list.append('positive')
else:
nega_posi_list.append('negative')
df_tweets['nega_posi'] = nega_posi_list
#円グラフ出力
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
label = ['positive', 'negative', 'netural']
plt.pie(df_tweets['nega_posi'].value_counts(),
labels=label, counterclock=False, startangle=90,
autopct='%1.1f%%',textprops={'fontsize': 12})
plt.title('Kishida_Posi_Nega')
plt.show()
解説
②では、ネガポジ比率を円グラフで見ていきます。
matplotlibで円グラフを出力するには、先程Natural Languageで計算した数値を定性データにする必要があるので、新しくnega_posi
カラムを追加します。
数値が0ならニュートラル、0以上ならポジティブ、それ以外はネガティブに分類します。
nega_posi_list = []
for score in df_tweets['score']:
if score == 0.0:
nega_posi_list.append('neutral')
elif score > 0.0:
nega_posi_list.append('positive')
else:
nega_posi_list.append('negative')
df_tweets['nega_posi'] = nega_posi_list
それぞれ、スコア通り分類されているのが確認できました。
以上で準備が整ったので、matplotlibを使いグラフを出力していきます。
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
label = ['positive', 'negative', 'netural']
plt.pie(df_tweets['nega_posi'].value_counts(),
labels=label, counterclock=False, startangle=90,
autopct='%1.1f%%',textprops={'fontsize': 12})
plt.title('Kishida_Posi_Nega')
plt.show()
全体でポジティブが64.6%、ネガティブが27.7%、ニュートラルが7.8%という結果になりました。
ニュートラルの場合、感情が混在している可能性が高いので、こちらも詳しく調べてみると面白い結果がみれそうです。
きになったので、ニュートラル且つ感情強度が高いツイートを少しだけ覗いて終わります。
おまけ ニュートラルのツイートを覗いてみる
scoreが0、かつmagnitudeの数値が高い順でソートしていきます。
df_tweets.query('score == 0')[["text","score","magnitude"]].sort_values('magnitude', ascending=False).head()
scoreが0、magnitudeの数値が高い順にソートします。
全体的に政治の内容になっています。
一番感情強度が高いツイートを見ると、謝罪と感謝が同時に含まれた文章になっているのがわかります。
他のツイートも同様に、否定的な表現と肯定的な表現が混在しているのがわかります。
エンティティ感情分析までやると、細かな数値も見ることが可能なので、次はそちらにも挑戦してみようと思います。
おわりに
今回は岸田総理のツイートをTwitter APIで取得し、データをGCP Natural language APIに読み込ませて感情スコアと強度を出力しました。その後、matplotlibでデータの可視化を行いました。
比較的易しい内容となっていますので、興味をもった方は是非、他のアカウントのツイートを取得して分析してみてください。
私と同じように、データ分析をしたいけど何から初めていいかわからないという方の参考になれば幸いです。
P.S
データ分析というタイトルを使わせていただきましたが、今回行ったことはかなり初歩的な内容になりますので、何かご指摘があればコメントしていただけると勉強になります。