119
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「クッパ姫」に関するツイートをpythonで収集して、バースト検出してみた

Last updated at Posted at 2018-09-24

「Twitterデータを使って、何か面白いことができないか」の第二弾です。

前回:
「停電」に関するツイートをpythonで収集して、WordCloudで可視化してみた
https://qiita.com/pocket_kyoto/items/0f43c9fdce87bddd31cf

今回は、前回と同様の方法で、Twitterデータを収集し、
「クッパ姫」に関する2018/9/24のツイートを可視化し、「クッパ姫」と共起する語のバースト検出を試してみました。

※この記事は2018/09/24に執筆しました。「クッパ姫」の元ネタがツイートされたのが2018/09/20(木)。その後、2018/09/23(日)に日本でTwitterのトレンド入りし、2018/09/24(月)には、様々なスーパーマリオキャラの女体化に関するツイートがトレンド入りしました。

クッパ姫って?

クッパ姫とは、『スーパーマリオ』の敵役クッパが「スーパークラウン」によって女体化(ピーチ姫化)した姿の二次創作のことです。(pixivより)

↓この漫画の4コマ目が元ネタのクッパ姫です。

クッパ姫.jpg

詳細は「クッパ姫」で検索してみてください。
2018/09/23(日)以降に、日本で爆発的な人気を得ました。

「クッパ姫」に関するツイートを収集する

収集方法は、基本的に前回とほぼ同じなので、分析に興味がある人は、クッパ姫に関するツイートを時系列で比較してみるまで飛んでください。

一応、前回から少し修正した箇所があるので、全体を再度解説したいと思います。

まずは、ライブラリの読み込みなど、ツイート収集の準備です。ここは前回と一緒です。

# Twitterデータ収集用のログインキーの情報
KEYS = { # 自分のアカウントで入手したキーを記載
        'consumer_key':'*********************',
        'consumer_secret':'*********************',
        'access_token':'*********************',
        'access_secret':'*********************',
       }

# Twitterデータの収集(収集準備)
import json
from requests_oauthlib import OAuth1Session
twitter = OAuth1Session(KEYS['consumer_key'],KEYS['consumer_secret'],KEYS['access_token'],KEYS['access_secret'])

ツイート収集用の関数は、以下のように修正しました。
ツイート場所は今回使わないため、デフォルトの引数(None)を設定できるようにしました。
また、1回あたり最大100ツイートしか検索できないので、for文で繰り返しリクエストする必要があるのですが、Twitterデータ取得関数の外で管理した方がスマートだったので、そのように修正しました。
このあたりは、参考[2]の書き方を踏襲しました。

# Twitterデータ取得関数
def getTwitterData(key_word, latitude=None, longitude=None, radius=None, mid=-1):
    
    url = "https://api.twitter.com/1.1/search/tweets.json"
    params ={'q': key_word, 'count':'100', 'result_type':'recent'} #取得パラメータ
    if latitude is not None: # latitudeのみで判定
        params = {'geocode':'%s,%s,%skm' % (latitude, longitude, radius)}
    
    params['max_id'] = mid # midよりも古いIDのツイートのみを取得する
    req = twitter.get(url, params = params)

    if req.status_code == 200: #正常通信出来た場合

        tweets = json.loads(req.text)['statuses'] #レスポンスからツイート情報を取得

        # 最も古いツイートを取るための工夫(※もっと良い書き方ありそう)
        user_ids = []
        for tweet in tweets:
            user_ids.append(int(tweet['id']))
        if len(user_ids) > 0:
            min_user_id = min(user_ids)
        else:
            min_user_id = -1
        
        # メタ情報
        limit = req.headers['x-rate-limit-remaining'] if 'x-rate-limit-remaining' in req.headers else 0
        reset = req.headers['x-rate-limit-reset'] if 'x-rate-limit-reset' in req.headers else 0  
            
        return {'tweets':tweets, 'min_user_id':min_user_id, 'limit':limit, 'reset':reset}

    else: #正常通信出来なかった場合
        print("Failed: %d" % req.status_code)
        return {}

上記の関数を連続で実行するための制御関数(getTwitterDataRepeat)を作成しました。
15分間に450回(180回?)のリクエスト制限に引っかからないように、制限に引っかかりそうになったら自動で待機します。

# Twitterデータ連続取得
import datetime, time
def getTwitterDataRepeat(key_word, latitude=None, longitude=None, radius=None, mid=-1, repeat=10):
    
    tweets = []
    
    for i in range(repeat):

        res = getTwitterData(key_word, latitude, longitude, radius, mid)
        
        if 'tweets' not in res: #エラーとなった場合は離脱
            break
        else:
            sub_tweets = res['tweets']
            for tweet in sub_tweets:
                tweets.append(tweet)
            
        if int(res['limit']) == 0:    # 回数制限に達した場合は休憩

            # 待ち時間の計算. リミット+5秒後に再開する
            now_unix_time = time.mktime(datetime.datetime.now().timetuple())  #現在時刻の取得
            diff_sec = int(res['reset']) - now_unix_time
            print ("sleep %d sec." % (diff_sec+5))
            if diff_sec > 0:
                time.sleep(diff_sec + 5)
        
        mid = res['min_user_id'] - 1
        
    print("ツイート取得数:%s" % len(tweets))
    return tweets

このように実装することで、リクエストの上限を気にせず、ツイートの収集が自動で可能となります。
あとは、時間帯別でツイートを分けて収集したかったので、以下のようなスクリプトを回しました。

# クッパ姫に関するツイートを時間別に取得する

tweet_princess_koopas = {}
mid = -1

for t in range(24):
    tweets = getTwitterDataRepeat("クッパ姫", mid=mid, repeat=10)    
    old_tweet = tweets[-1]  # 収集した中で最も古いツイート
    
    key = YmdHMS(old_tweet["created_at"])  # YmdHMS関数の詳細は前回記事
    tweet_princess_koopas[key] = tweets  # 最も古いツイートの時刻をキーとして保存する
    
    mid = old_tweet["id"] - 10000000000000  # 約1時間遡って収集する

出力:
ツイート取得数:590
KEY : 2018/09/24 19:38:23
ツイート取得数:603
KEY : 2018/09/24 18:58:01
:
(中略)
:
ツイート取得数:300
KEY : 2018/09/24 04:25:53
ツイート取得数:307
KEY : 2018/09/24 03:42:16


ツイートを1時間ずつ遡りながら収集したかったので、最も古いツイートのmidから10,000,000,000,000を引いています。この10,000,000,000,000という値は、机上で概算で求めた値です。この値をそのまま信じるならば、1時間に10兆ツイート近くされていることになるので驚きです。ちなみに概算がズレて約40分間隔でのツイート取得となってしまったのですが、気にせず先に進みます。

「クッパ姫」に関するツイートを時系列で比較してみる

ここまでで、「クッパ姫」を含むツイートを時系列で収集することができました。
まずはデータを理解するために、単語の出現頻度を時系列で可視化したいと思います。可視化方法はWordCloudを用いました。実装は前回記事の通りです。

for k, v in sorted(tweet_princess_koopas.items()):
    print("%s~ のツイート" % k)
    freq_dict = CountWord(v)
    DrawWordCloud(freq_dict, "%s~ のツイート" % k)

出力:
(中略)
:
image.png
:
(中略)
:
image.png

:
(中略)
:
image.png

:
(中略)
:
image.png


今回の話題の中心である「クッパ」「マリオ」「ピーチ」などのキャラクター名や、「RT」や「https:/t.co/xx(画像のリンクなど)」といったTwitter独自の表記が安定して上位に出現していることが分かります。
上位に出現する語の大部分は変わらないのですが、あるキャラクターの名前だけは、突如上位にランクインしました。**「キングテレサ」**です。

「キングテレサ姫」って?

キングテレサ姫とは、『ルイージマンション』シリーズの敵役キングテレサが「スーパークラウン」によって女体化(ピーチ姫化)した姿の二次創作のことで、クッパ姫の派生形です。(pixivより)

↓オリジナルかは分かりませんが、みずしー@oshiruko_mizseeさんの作品が有名です。

キングテレサ姫1.jpg

ツリ目、キバがある、舌を出している などの身体的特徴や、
幽霊、目が合うと動けなくなる などのキャラクター設定が女体化に合っていたため、爆発的に流行ったものと思われます。

キングテレサ姫2.jpg

「キングテレサ」に関するツイートの激増を、バースト検出してみる

今回収集したデータセットと、バースト検出と呼ばれる手法を用いて、「キングテレサ」という語を含むツイートの激増を検出してみたいと思います。
バースト検出という手法に関しては、書籍では、「ウェブデータの機械学習 (機械学習プロフェッショナルシリーズ)」に詳しくまとめられているようですが、ネットには解説記事が少ないです。
今回は、自然言語処理に関する研究室として著名な、東北大学 乾・鈴木研究室の解説記事を参考として、バースト検出手法の実装・適用に挑戦してみたいと思います。

今回は、Moving Average Convergence Divergence (MACD) という指標を用いて、バースト検出に取り組んでみました。
バースト検出手法としては、Kleinbergが2002年に発表した手法がベースラインとして、よく用いられるようですが、He and Parker が2010年に発表したMACDのほうがシンプル、かつ計算量が少ないようです。

↓MACDの解説については、乾・鈴木研究室のものが分かりやすいので、そのまま引用したいと思います。


【MACDの解説】

ある時刻におけるMACDは,

MACD = (時系列値の過去f期間の移動指数平均) - (時系列値の過去s期間の移動指数平均)
Signal = (MACD値の過去t期間の移動指数平均)
Histgram = MACD - Signal

ここで,f, s, tはパラメータ(f < s)で,これらをまとめてMACD(f, s, t)と書きます. 今回の実験では,He and Parker (2010) の実験でも用いられていた MACD(4, 8, 5) を採用しました. MACDをテクニカル指標として用いる時は,「Signal < MACD」の状態を上げトレンド,「MACD < Signal」の状態を下げトレンドとし,Histgramがトレンドの強さを表すと言われています. 今回は,15分間の期間をひとまとまりとして(15分足),その期間内にツイッター上に出現した単語の出現頻度を15で割った値,つまり出現速度[回/分]を観測値として,MACDによるトレンド分析を行いました. MACDの計算に必要な移動指数平均の値は逐次計算が可能で,今回のトレンド分析はストリーミング・アルゴリズムとして実装できるため,ビッグデータからのトレンド分析に適していると考えています.


上記の解説内容から、MACDを以下のように実装しました。(間違えてたら、すみません。。)

# Moving Average Convergence Divergence (MACD) の計算
class MACDData():
    def __init__(self,f,s,t):
        self.f = f
        self.s = s
        self.t = t
        
    def calc_macd(self, freq_list):
        n = len(freq_list)
        self.macd_list = []
        self.signal_list = []
        self.histgram_list = []
        
        for i in range(n):

            if i < self.f:
                self.macd_list.append(0)
                self.signal_list.append(0)
                self.histgram_list.append(0)
            else :
                macd = sum(freq_list[i-self.f+1:i+1])/len(freq_list[i-self.f+1:i+1]) - sum(freq_list[max(0,i-self.s):i+1])/len(freq_list[max(0,i-self.s):i+1])
                self.macd_list.append(macd)
                signal = sum(self.macd_list[max(0,i-self.t+1):i+1])/len(self.macd_list[max(0,i-self.t+1):i+1])
                self.signal_list.append(signal)
                histgram = macd - signal
                self.histgram_list.append(histgram)   

このプログラムを用いて、「ヨッシー」「クッパ」「キングテレサ」「マリオ」「ピーチ」「ルイージ」の6キャラクターについて、バースト検出してみました。

上記の関数へデータを代入するプログラム(折り込み)
# データ取得(べた書き)
yossi_list = []
teresa_list = []
koopa_list = []
mario_list = []
peach_list = []
ruiji_list = []

for k, v in sorted(tweet_princess_koopas.items()):
    freq_dict = CountWord(v)
    if "ヨッシー" in freq_dict:
        yossi_list.append(freq_dict["ヨッシー"])
    else:
        yossi_list.append(0)
    if "キングテレサ" in freq_dict:
        teresa_list.append(freq_dict["キングテレサ"])
    else:
        teresa_list.append(0)
    if "クッパ" in freq_dict:
        koopa_list.append(freq_dict["クッパ"])
    else:
        koopa_list.append(0)
    if "マリオ" in freq_dict:
        mario_list.append(freq_dict["マリオ"])
    else:
        mario_list.append(0)
    if "ピーチ" in freq_dict:
        peach_list.append(freq_dict["ピーチ"])
    else:
        peach_list.append(0)
    if "ルイージ" in freq_dict:
        ruiji_list.append(freq_dict["ルイージ"])
    else:
        ruiji_list.append(0)

# 正規化
yossi_av_list = [elem/sum(yossi_list) for elem in yossi_list]
koopa_av_list = [elem/sum(koopa_list) for elem in koopa_list]
teresa_av_list = [elem/sum(teresa_list) for elem in teresa_list]
mario_av_list = [elem/sum(mario_list) for elem in mario_list]
peach_av_list = [elem/sum(peach_list) for elem in peach_list]
ruiji_av_list = [elem/sum(ruiji_list) for elem in ruiji_list]

# 計算(He and Parker(2010)と同じパラメータ)
f = 4
s = 8
t = 5
yossi_macd_data = MACDData(f,s,t)
yossi_macd_data.calc_macd(yossi_av_list)
koopa_macd_data = MACDData(f,s,t)
koopa_macd_data.calc_macd(koopa_av_list)
teresa_macd_data = MACDData(f,s,t)
teresa_macd_data.calc_macd(teresa_av_list)
mario_macd_data = MACDData(f,s,t)
mario_macd_data.calc_macd(mario_av_list)
peach_macd_data = MACDData(f,s,t)
peach_macd_data.calc_macd(peach_av_list)
ruiji_macd_data = MACDData(f,s,t)
ruiji_macd_data.calc_macd(ruiji_av_list)
可視化プログラム(折り込み)
# 結果の可視化
import numpy as np
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
from matplotlib.font_manager import FontProperties
fp = FontProperties(fname=r'C:\WINDOWS\Fonts\meiryo.ttc', size=10) #日本語対応

x = np.array(sorted(tweet_princess_koopas.keys()))
y1 = np.array(yossi_macd_data.histgram_list)
y2 = np.array(koopa_macd_data.histgram_list)
y3 = np.array(teresa_macd_data.histgram_list)
y4 = np.array(mario_macd_data.histgram_list)
y5 = np.array(peach_macd_data.histgram_list)
y6 = np.array(ruiji_macd_data.histgram_list)


plt.plot(x, y1, marker="o")
plt.plot(x, y2, marker="+", markersize=10, markeredgewidth=2)
plt.plot(x, y3, marker="s", linewidth=1)
plt.plot(x, y4, marker="o")
plt.plot(x, y5, marker="+", markersize=10, markeredgewidth=2)
plt.plot(x, y6, marker="s", linewidth=1)

plt.xticks(rotation=90)

plt.title("バースト検出結果", fontproperties=fp)
plt.xlabel("日時", fontproperties=fp)
plt.ylabel("バースト検出結果", fontproperties=fp)
plt.legend(["「ヨッシー」","「クッパ」", "「キングテレサ」","「マリオ」", "「ピーチ」","「ルイージ」"], loc="best", prop=fp)

可視化結果は以下の通りです。

バースト検出.PNG

何となくそれっぽい結果になりました。「キングテレサ」は、他のキャラクターと比較しても、バースト傾向が強い(瞬間的に激増している)ことが分かります。
16:00~18:00までが上昇トレンドで、18:15頃がピーク。それ以降は、下降トレンドであると読み取れます。

まとめと今後

今回は、「クッパ姫」をテーマにバースト検出に取り組んでみました。
前回は地域ごとでのツイート内容の違いを分析してみましたが、時系列でのツイート内容の推移の分析も結構使えそうな感触でした。

Twitterデータの他の活用方法についても、引き続き探っていこうと思います。

次回:
「指定アカウントのツイートをWordCloudで可視化する方法」
https://qiita.com/pocket_kyoto/items/e823f3319d41076f7899

参考

[1]
クッパ姫(pixiv)
https://dic.pixiv.net/a/%E3%82%AF%E3%83%83%E3%83%91%E5%A7%AB
[2]
スタバのTwitterデータをpythonで大量に取得し、データ分析を試みる その1
https://qiita.com/kenmatsu4/items/23768cbe32fe381d54a2
[3]
キングテレサ姫(pixiv)
https://dic.pixiv.net/a/%E3%82%AD%E3%83%B3%E3%82%B0%E3%83%86%E3%83%AC%E3%82%B5%E5%A7%AB
[4]
東北大学 乾・鈴木研究室 Project 311 / Trend Analysis
http://www.cl.ecei.tohoku.ac.jp/index.php?Project%20311%2FTrend%20Analysis
[5]
Dan He and D. Stott Parker(2010)
「Topic Dynamics: An Alternative Model of 'Bursts' in Streams of Topics」
https://dollar.biz.uiowa.edu/~street/HeParker10.pdf

119
118
2

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
119
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?