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

「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](https://dic.pixiv.net/a/%E3%82%AF%E3%83%83%E3%83%91%E5%A7%AB)より）

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

![クッパ姫.jpg](https://qiita-image-store.s3.amazonaws.com/0/254780/b8ea17af-bc53-af5e-b149-4a2e19bb6583.jpeg)

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

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

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

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

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

```python
# 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]](#参考)の書き方を踏襲しました。

```python
# 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回？）のリクエスト制限に引っかからないように、制限に引っかかりそうになったら自動で待機します。

```python
# 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:    # 回数制限に達した場合は休憩

            # 待ち時間の計算. リミット＋５秒後に再開する
            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

```

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

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

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を用いました。実装は[前回記事](https://qiita.com/pocket_kyoto/items/0f43c9fdce87bddd31cf)の通りです。

```python
for k, v in sorted(tweet_princess_koopas.items()):
    print("%s～　のツイート" % k)
    freq_dict = CountWord(v)
    DrawWordCloud(freq_dict, "%s～　のツイート" % k)
```
出力：
(中略)
:
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254780/13095a04-65b8-65be-93e1-466e25a6a9f5.png)
:
(中略)
:
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254780/477f920d-6c43-d8e6-86d9-5dc89069b75d.png)

:
(中略)
:
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254780/0bc45da3-2e37-f941-0e76-f2fcb301801d.png)

:
(中略)
:
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254780/51c4b0c5-4c0d-a8f6-76b9-df01f60aad9b.png)

***

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

# 「キングテレサ姫」って？

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

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

![キングテレサ姫1.jpg](https://qiita-image-store.s3.amazonaws.com/0/254780/cc056f45-3ea0-4ec1-ef81-6e4efba061c9.jpeg)

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

![キングテレサ姫2.jpg](https://qiita-image-store.s3.amazonaws.com/0/254780/227efdb1-a1d1-e503-a0a3-7840d66d8c65.jpeg)

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

今回収集したデータセットと、バースト検出と呼ばれる手法を用いて、「キングテレサ」という語を含むツイートの激増を検出してみたいと思います。
バースト検出という手法に関しては、書籍では、「[ウェブデータの機械学習 (機械学習プロフェッショナルシリーズ)](https://www.amazon.co.jp/dp/B0756TSNBX/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)」に詳しくまとめられているようですが、ネットには解説記事が少ないです。
今回は、自然言語処理に関する研究室として著名な、[東北大学　乾・鈴木研究室の解説記事](http://www.cl.ecei.tohoku.ac.jp/index.php?Project%20311%2FTrend%20Analysis)を参考として、バースト検出手法の実装・適用に挑戦してみたいと思います。

今回は、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を以下のように実装しました。（間違えてたら、すみません。。）

```python
# 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キャラクターについて、バースト検出してみました。

<details><summary>上記の関数へデータを代入するプログラム（折り込み）</summary><div>

```python
# データ取得（べた書き）
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)
```
</div></details>

<details><summary>可視化プログラム（折り込み）</summary><div>

```python
# 結果の可視化
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)
```
</div></details>

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

![バースト検出.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/33165f01-3d2c-7d04-d1c2-d5fd3c14410f.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で大量に取得し、データ分析を試みる その１
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