ある話題へのツイートは時間でどう変化するかGIF化してみた

#本記事について
本記事は、以下に興味がある方を対象としている。

・ある単語を対象としてツイート内容を取得したい
・ツイート内容から頻度が高い単語を可視化（画像出力）したい
・出力した画像をGIFアニメーション化したい

#本記事内のコードを用いることで達成できること
（内容的に上記と連動している）
・Twitterからツイート内容を取得し、.csvファイルとして出力できる
・出力した.csvファイルから、出現頻度が高い単語を画像(.pngなど)として出力できる
・出力した画像からアニメーションGIFを出力できる

#本記事内のコードを作成した目的・経緯
:::note info
この項目は私見・私事を多分に含んでいるため、興味がない方はどんどん次項目に進もう
:::

__コードを作成した目的：ある話題に対するツイートされる内容の変化の把握__
現代は情報に溢れており、且つ物事の変化がとても早い。
よって、それらの物事に対し人々がどのように感じているかを把握することは、次へ繋がる要因を特定することになる。
その情報を何に使うかは人それぞれだが、少なくとも私は楽しいことをするために使いたいと考えている。

__コードを作成したいと思った経緯__
端的に言うと、人々が現代で起こっている物事に対してどのように感じているかを知りたいからである。
上記の通り、現代はとにかく情報に溢れており、それらに振り回されて疲れている人が多くいる。
Twitterという媒体はある程度の匿名性があるため、感情を乗せた発信をする人が少なからず存在する。

そういった発信を物事毎に調べ、何がネガティブに、何がポジティブに捉えられているかを調査すれば、疲れている人々を少しでも楽にできるではないかと私は考えている。
上記を達成するための足がかりとして、本記事のコード作成に取り組んだ。

#環境
開発環境：Google Colaboratory
ツイッター情報取得ライブラリ：Tweepy
日本語解析ライブラリ：janome-0.4.1
単語出現頻度可視化ライブラリ：WordCloud
絵文字除去ライブラリ：emoji-1.4.1

:::note info
以下に上記5つの説明を記載するが、実装を急ぎたい方は次項目へどうぞ
:::

###Google Colaboratory
ブラウザから Python を記述、実行できるサービスを指す。
手軽にPythonのコード作成、実行が可能であるため使用したが、ある程度制限が設けられている。
今回影響がある制限は__「90分ルール」__が該当する。(何もせず90分経過するとセッションが切断される)
詳細については、以下リンクを参照。
[Colaboratory へようこそ](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)
[リソース制限](https://research.google.com/colaboratory/faq.html?hl=ja#resource-limits)

###Tweepy
Twitterからツイート情報を取得するためのライブラリ。
このライブラリを使用するためには、予め__TwitterAPIの利用申請を行う__必要がある。
手順についてはGoogleなどで検索すれば出てくるため、各自で確認いただきたい。
また、__ツイート情報の取得については制限が設けられている__。
上記の詳細については、以下リンクを参照。
[Tweepy (English)](https://www.tweepy.org/)
[TwitterAPI利用制限(English)](https://developer.twitter.com/en/docs/twitter-api/rate-limits)

###janome
Python上で形態素解析を実施するためのライブラリ。
janomeについては、以下リンクを参照。
[janomeとは](https://mocobeta.github.io/janome/)

__形態素解析__とは、自然言語で書かれた文を言語上の最小単位に分割し、それぞれの品詞や変化などを割り出すこと。 
例：私はAIの勉強をします　→　私/は/AI/の/勉強/を/し/ます

###WordCloud
単語の出現頻度を可視化するライブラリ。
詳細については、以下リンクを参照。
[WordCloud for Python documentation (English)](http://amueller.github.io/word_cloud/index.html)

###emoji
Python上で絵文字を扱うためのライブラリ。
今回は、取得したツイート内容から絵文字を除去するために使用する。
WordCloudでは絵文字を扱うことができないため、予め除去をしておく必要がある。
詳細については、以下リンクを参照。
[emojiとは (English)](https://pypi.org/project/emoji/)

#おおまかな全体の流れ
①調べる話題を決める
②必要なライブラリをインストールする
③決めた話題をもとにツイート内容を時間ごとに取得する
④取得した内容をすべて画像化する
⑤すべての画像からGIFアニメーションを生成する

#実装
##①調べる話題を決める
これについては興味があるものでよいが、取得する情報は多いほうがいいためTwitterのトレンドになるような話題が良いと思われる。
起稿時は「東京オリンピック」が開催されていたため、今回はハッシュタグ「#Tokyo2020」とした。
（ツイート情報の取得はハッシュタグでなくても問題ない）

##②必要なライブラリをインストールする
以下を記載し、ライブラリのインストールを行う。

```python
# Word Cloudのライブラリのインストール
!pip install wordcloud
# 絵文字を扱うためのライブラリのインストール
!pip install emoji --upgrade
# Pythonの形態素解析ライブラリjanomeのインストール
!pip install janome
# フォントインストール
!apt-get -y install fonts-ipafont-gothic
```


##③決めた話題をもとにツイート内容を時間ごとに取得する
これを達成するために記述したコードは以下になる。
・Twitter APIを使用するためのコード
・絵文字を除去する関数の作成
・任意のワードを元にツイートを取得し、.csvとして出力する関数の作成
・上記2つの関数を任意の回数ループするためのコード

コード実行の際には以下をインポートしておく。

```python
import time
import tweepy
import csv
import numpy as np
import os
import matplotlib.pyplot as plt
import glob
from PIL import Image
import emoji
```

###Twitter APIを使用するためのコード
ここでは、TwitterAPI申請後に取得できる__Key__、__Secret__を記載する。

```python
consumer_key = ''       # ''内にConsumer Keyを記載
consumer_secret = ''    # ''内にConsumer Secretを記載
access_key = ''         # ''内にAccess Keyを記載
access_secret = ''      # ''内にAccess Secertを記載

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_key, access_secret)
api = tweepy.API(auth)
```

###絵文字を除去する関数の作成
以下では「remove_emoji」としている。

```python
remove_emoji = lambda text_without_url : ''.join(['' if c in emoji.UNICODE_EMOJI else c for c in text_without_url])
```

###任意のワードを元にツイートを取得し、.csvとして出力する関数の作成
ここでは、「q」にツイートを取得する際に元とするワードを入力する。（今回は#Tokyo2020）

関数は「get_tweet」で定義している。
引数は__(i,j)=(一回で取得するツイートの数,ツイートを取得する関数を実行する回数)__である。
必然的に__j=出力する.csvの数__になる。
出力した.csvのファイル名は「tweets_〇〇〇.csv」になる。（〇〇〇は001からの連番）

```python
# ------------------------------------------------------------------------------
# 任意のハッシュタグからツイートを取得
# ------------------------------------------------------------------------------
# q : 出力したいワード（今回はハッシュタグ）を指定
# result_type = 'recent' : 最新のツイートを指定
# itemsの引数 : 一回で出力するツイート数の指定
# lang = 'ja' : ツイートを取得する言語の指定（ここでは日本語）
def get_tweet(i,j):
    tweet_data = []
    tweets = tweepy.Cursor(api.search, q = '#Tokyo2020', result_type = 'recent', lang = 'ja')
    for tweet in tweets.items(i):
        tweet_data.append([tweet.id,tweet.created_at,remove_emoji(tweet.text.replace('\n','')),tweet.favorite_count,tweet.retweet_count])

# ------------------------------------------------------------------------------
# tweets.csvの出力
# ------------------------------------------------------------------------------
    with open("./tweets_{}.csv".format(str(j).zfill(3)), 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(["id", "created_at", "text", "fav", "RT"])
        writer.writerows(tweet_data)
    j = j + 1
```

###上記2つの関数を任意の回数ループするためのコード
今回は、以下の条件でツイート情報を取得する。
（Colaboratoryの90分ルール、TwitterAPIの制限に収まる範囲にしている）
・一度のコード実行で取得するツイート数：30
・コードを実行する回数(.csv出力数）：8
・コード実行ごとに空ける時間：10分（時間を空けないと同じツイートが取得される可能性が高い）

上記をコードに置き換えると以下になる。

・一度のコード実行で取得するツイート数：関数get_tweetの引数「i」
・コードを実行する回数(.csv出力数）：関数get_tweetの引数「j」
・コード実行ごとに空ける時間：time.sleep()の引数

また、.csvを連番で出力したいため、for文により1から8までを指定している。

```python
# sleepの引数 : 一時停止時間(sec)
for j in range(1,9):
    get_tweet(30,j)
    print("実行回数:{}".format(j)) #.csvの出力を行った回数のプリント、可視化しない場合は不要
    if j != 8: # 指定回数分ツイートデータを出力していない場合、一時停止
        time.sleep(600)
```

###実行後
上記すべてのコードを実行すると、以下のように.csvファイルが出力される。

・tweets_001.csv
・tweets_002.csv
・tweets_003.csv
・tweets_004.csv
・tweets_005.csv
・tweets_006.csv
・tweets_007.csv
・tweets_008.csv

##④取得した内容をすべて画像化する
これを達成するために記述したコードは以下になる。
・出力した.csvファイルを形態素解析し、単語数をカウントする関数の作成
・出力した.csvファイルを読み込む関数の作成
・.csvファイルをもとに単語出現頻度を画像化する関数の作成
・上記3つの関数を任意の回数ループするためのコード

コード実行の際には以下をインポートしておく。

```python
import csv
from janome.tokenizer import Tokenizer
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from bs4 import BeautifulSoup
from collections import Counter, defaultdict
from datetime import datetime
```

###出力した.csvファイルを形態素解析し、単語数をカウントする関数の作成
ここでは、除外する単語の指定に注意したい。
Twitterから検索する単語によるが、出現頻度が高すぎる単語を含むと、他の単語が埋没する可能性が高い。
今回は「#Tokyo2020」を検索対象にしたため、以下の単語を除外した。

Tokyo, 東京, オリンピック, 五輪, RT, 競技, daihyo

```python
# 画像の出力先フォルダを作成
if os.path.isdir("output") == False:
    os.makedirs("output")

# 名詞のみ抽出、単語をカウント
def counter(texts):
    t = Tokenizer()
    words_count = defaultdict(int)
    words = []
    for text in texts:
        tokens = t.tokenize(text)
        for token in tokens:
            # 品詞から名詞のみ抽出
            pos = token.part_of_speech.split(',')[0]
            if pos in ['名詞']:
                # 不要な単語を省く
                if token.base_form not in ["Tokyo", "東京", "オリンピック", "五輪", "RT", "競技", "daihyo"]:
                    words_count[token.base_form] += 1
                    words.append(token.base_form)
    return words_count, words
```

###出力した.csvファイルを読み込む関数の作成
.csvファイルを連番で出力しているため、番号を変数として読み込んだほうがコードが少なくて済む。
以下では「k」を連番の変数としている。

```python
def File_Open(k):
    with open('tweets_{}.csv'.format(str(k).zfill(3)), 'r') as f:
        reader = csv.reader(f, delimiter='\t')
        texts = []
        for row in reader:
            if(len(row) > 0):
                text = row[0].split('http')
                texts.append(text[0])
    return texts
```

###.csvファイルをもとに単語出現頻度を画像化する関数の作成
ここでは、出力する画像ファイルの名前を「wordcloud_年月日_時間(時分秒).png」としている。（最下行コード）
また、GIF作成時はフォルダ内のデータすべてを対象としたいため、「output」フォルダ内に出力するようにしている。

```python
def Output_Image(texts):
    words_count, words = counter(texts)
    text = ' '.join(words)
    fpath = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf"
    wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(text)
    wordcloud.to_file("./output/wordcloud_{0:%Y%m%d-%H%M%S}.png".format(datetime.now()))
```


###上記3つの関数を任意の回数ループするためのコード
コードの構成は、先の.csvファイル出力時と似ている。
for文にはkを指定している。（ファイルを読み込む際の連番とする変数）


```python
for k in range(1,9):
    texts = File_Open(k) #.csvファイルを読み込む関数
    words_count, words = counter(texts)
    text = ' '.join(words)
    k = k + 1
    Output_Image(texts) #単語頻出頻度を表す画像を出力する関数
```

###実行後
上記すべてのコードを実行すると、以下のように画像ファイル(.png)が出力される。
（wordcloud_◯-◯.pngの◯は実行した時間により異なる）

・wordcloud_20210722-134930.png
・wordcloud_20210722-134933.png
・wordcloud_20210722-134935.png
・wordcloud_20210722-134938.png
・wordcloud_20210722-134940.png
・wordcloud_20210722-134943.png
・wordcloud_20210722-134945.png
・wordcloud_20210722-134948.png


##⑤すべての画像からGIFアニメーションを生成する
先に「output」フォルダ内に画像を出力したため、「dir='output'」と記載している。
以下のコードで調整しておきたいのは「duration=」である。

　duration：一枚の画像が表示されている時間（ミリ秒）を指定できる

以下では0.5秒に設定している。

```python
from PIL import Image
import os
import glob

# GIF出力先フォルダの作成
if os.path.isdir("gif") == False:
    os.makedirs("gif")

# GIFアニメーションを作成
def create_gif(in_dir, out_filename):
    path_list = sorted(glob.glob(os.path.join(*[in_dir, '*']))) # ファイルパスをソートしてリスト
    imgs = []                                                   # 画像をappendするための空配列を定義
    # ファイルのフルパスからファイル名と拡張子を抽出
    for i in range(len(path_list)):
        img = Image.open(path_list[i])                          # 画像ファイルを1つずつ開く
        imgs.append(img)                                        # 画像をappendで配列に格納
    # appendした画像配列をGIF化。durationで持続時間(ミリ秒)、loopでループ数を指定可能。
    imgs[0].save(out_filename,
                 save_all=True, append_images=imgs[1:], optimize=False, duration=500, loop=0)
# GIFアニメーションを作成する関数を実行
create_gif(in_dir='output', out_filename='./gif/animation_{0:%Y%m%d-%H%M%S}.gif'.format(datetime.now()))
```

###実行後
上記のコードを実行すると、GIFアニメーションファイル(.gif)が出力される。
（animation_◯-◯.gifの◯は実行した時間により異なる）
・animation_20210722-134955.gif
![animation_20210722-134955.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1788247/66815468-ac4b-3718-0724-e54b897d8817.gif)

#本コードに対する見解、今後の課題
上図GIFを見ると以下が感じられる。

　__GIF内ではほぼ同じ単語が大きく描画されている__

Google Colaboratoryの90分ルールに収まるように10分ごとにコードを実行したため、単語に変化がない。
これについては環境を変えるなどして、コードを実行する時間間隔を空けるように対処をすべきである。

他に改善できる課題としては、以下が挙げられる。

###・除外する単語の自動設定
本件ではハッシュタグを予め決めていたため、除外する単語を苦もなく設定できた。
しかし、除外する単語を都度変えるのは、作業コストが膨大になってしまう。
よって、取得したツイート情報からどの時間でも出現頻度が高い単語を特定し、それらの単語を自動的に除外できるコードを実装することが望ましいと考える。

###・単語の出現頻度に重み付けをする（リツイート数など）
出現頻度のみではTwitter利用者の共感度を重み付けできないため、リツイート数を係数とすることにより感情が乗った情報を取得できると考える。

###・コード実行の時間間隔を自動で決定
上図GIFのように画像出力の時間間隔が狭いと、毎度同じ単語が大きく描画されてしまう。
しかしながら、時間間隔を人力で調整すると、手間が膨れ上がってしまう。
よって、時間ごとの単語出現頻度を先に調査し、それらのデータをもとに画像を出力する時間間隔を自動的に決めるコードを実装することが望ましいと考える。
実装としては、同様の単語がどれだけの時間内で頻度が高いかで決める、などが考えられる。

###・ネガポジの切り分けをできるようにする
取得したツイート情報を確認すると、ハッシュタグによってネガポジの割合が大きく異なる。
この割合を明確にすることで、出力した画像がどちらに傾いているかを感覚的につかめる。
実装手段としては、ネガポジの割合を背景色に反映するなどが考えられる。
（例：ネガに行くほど黒く、ポジに行くほど白く）

#終わりに
今回は、あるハッシュタグで出現頻度が高い単語に集中した出力としているが、上記に挙げた課題を達成していくことでより価値が高い情報を出力することが可能になると考える。
昨今は、ネット上に有用な情報が多く存在しているため、上記課題を達成する日も遠くはないと思う。

結果としてはあまり有用な情報は出力できていないが、本記事がどなたかの先に進む足がかりになれば幸いである。


#参考サイト
[Pythonで複数画像からGIFを作る時に便利な処理まとめ](https://watlab-blog.com/2021/01/31/python-gif/)
[Word Cloudでツイートを可視化してみた(python)](https://qiita.com/turmericN/items/04cd0b40f91076f0ef42)