ビットコイン関連ツイートをネガポジ判定して投資家心理を可視化する

ビットコインなどについて、人々がどのように思っているのかをツイートから可視化したいと思います。

#### やりたいこと
* Twitter APIからビットコイン関連ツイートを取得
* ツイートを形態素解析して、ネガポジ判定
* Word Cloudで可視化
* 画像をツイート

<img width=80% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1315219/a667e236-4fa9-b4d5-29d9-3f7b5cde7701.jpeg">

#### ライブラリ
```python
import pandas as pd
from pandas import DataFrame
import json
from janome.tokenizer import Tokenizer
from requests_oauthlib import OAuth1Session
from wordcloud import WordCloud
import emoji
import re
import csv
```

Twitter APIからビットコイン関連ツイートを取得
--
Twitter APIの利用方法については、参考サイトなどを参照ください。
関数search_wordのwordに検索したい単語、countに取得するツイート数を指定して（APIのデータ取得回数には制限があるので注意）、該当ツイートを取得します。
下記プログラムでは、jsonファイルに保存したキーを読み込んでいます。

```python
keysfile = 'keys.json'
signature = '#TweetFromPython'

##################### Twitter API #####################
def create_oauth_session(oauth_key_dict):
    oauth = OAuth1Session(
        oauth_key_dict['consumer_key'],
        oauth_key_dict['consumer_secret'],
        oauth_key_dict['access_token'],
        oauth_key_dict['access_token_secret']
    )
    return oauth

def search_tweet(search_word, count, oauth):
    url = 'https://api.twitter.com/1.1/search/tweets.json'
    params = {
        'q': search_word,
        'count' : count,
        'result_type' : 'recent',
        'exclude': 'retweets',
        'lang' : 'ja'
        }
    responce = oauth.get(url, params=params)
    if responce.status_code != 200:
        print("Error code: %d" %(responce.status_code))
        return None
    tweets = json.loads(responce.text)
    return tweets

#### キーを取得
keys = json.load(open(keysfile))
twitter = create_oauth_session(keys)

search_word = 'ビットコイン'
search_count = 200

search = search_tweet(search_word, search_count, twitter) # 検索結果
df_search = DataFrame.from_dict(search['statuses']) # DataFrameに変換
tweets = df_search['text'].tolist() # textカラムがツイート文
```

ツイートを形態素解析
--
形態素解析のための前処理として、絵文字やURLを除去します。
emoji.UNICODE_EMOJIはアップデートされて、言語情報も含まれるようになったので、'en'を指定しています。（以前は、emoji.UNICODE_EMOJIだけで大丈夫でした）
URLは正規表現を使って除去しています。
このままだと、ツイッター特有の文字（#、@など）が含まれますが、後のWordCloudでは1文字のアルファベットや記号は無視されるようなので、ここではスルーします。
関数get_wakachiでは、必要な単語だけ分かち書き（単語の間にスペース）に変換します。まず、必要な品詞の単語か識別して、無意味な単語を除外します。さらに、今回は、検索ワード自体も必要ないので除去します。

```python
# 絵文字除去
def remove_emoji(text):
    return ''.join(c for c in text if c not in emoji.UNICODE_EMOJI['en'])
# URL除去
def remove_url(text):
    return re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-]+', '', text)
# 形態素解析のための前処理
def normalize_tweets(tweets):
    normalized = []
    for tweet in tweets:
        text = tweet
        text = remove_emoji(text)
        text = remove_url(text)
        normalized.append(text)
    return normalized
# 必要な単語だけ分かち書きに変換
def get_wakachi(list_text, word, hinshi=['名詞', '形容詞']):
    remove_words = ['こと', 'よう', 'そう', 'これ', 'それ', 'もの', 'ここ', 'さん', 'ちゃん',
                'ところ', 'とこ', 'の', 'ん', word] # 無意味な単語と検索ワードのリスト
    t = Tokenizer()
    wakachi = ''
    for text in list_text:
        malist = t.tokenize(text)
        for w in malist:
            word = w.surface
            part = w.part_of_speech
            hit = False
            for h in hinshi:
                hit = hit or (h in part)
            if not hit:
                continue
            if word not in remove_words:
                wakachi += word + ' '
    return wakachi

tweets = normalize_tweets(tweets) # 形態素解析のための前処理
wakachi = get_wakachi(tweets, search_word) # 分かち書きに変換
```

Word Cloudでネガポジを可視化
--
分かち書きの文字列を使ってWordCloudで可視化しますが、このままだと、単語の意味に関係なく色が付きます。そこで、単語をネガポジ判定して、ネガティブとポジティブで色分けしてみたいと思います。

#### ネガポジ判定
日本語評価極性辞書をダウンロードして、この辞書から単語のネガポジ判定をします。この辞書には単語とそのネガポジ（ポジティブ=p、ネガティブ=n、どちらでもない=e）が格納されています。
ダウンロード

```
! curl http://www.cl.ecei.tohoku.ac.jp/resources/sent_lex/pn.csv.m3.120408.trim > pn.csv
```

WordCloudには、自作の色を返す関数を設定できるので、get_pn_color関数の中でネガポジ判定して色を決めます。単語が辞書に含まれていなければ、e。含まれていれば、その判定結果を返し、判定結果によって色を決めています。色は、ポジティブ=青、ネガティブ=赤、どちらでもない=緑としています。

word_cloudはWordCloudを作成して保存する関数で、wordsに分かち書きを、image_fileに保存するファイル名を、numに表示する頻出単語の最大数を指定しています。

```python
# Initialize Negative-Positive Dictionary
pn_dic = {}
fp = open('pn.csv', 'rt', encoding='utf-8') # ネガポジ辞書を読み込む
reader = csv.reader(fp, delimiter='\t')
for i, row in enumerate(reader): # Pythonの辞書型に変換
  name = row[0]
  result = row[1]
  pn_dic[name] = result

# 色を返す関数
def get_pn_color(word, font_size, **kwargs):
    r, g, b = 0, 0, 0
    pn = 'e'
    if word in pn_dic:
        pn = pn_dic[word]
    if pn == 'p':
        b = 255
    elif pn == 'n':
        r = 255
    else:
        g = 255
    return (r, g, b)

# Word Cloud
def word_cloud(words, image_file, num,
                font_path='C:/Windows/Fonts/MSGOTHIC.TTC'): #日本語フォントを指定
    wordcloud = WordCloud(
            background_color='white', 
            color_func=get_pn_color,
            max_words=num,
            font_path=font_path,
            width=800, height=400).generate(words)
    wordcloud.to_file(image_file)
    return True

image_file = 'word_cloud.jpg'
trend_num = 100

word_cloud(wakachi, image_file, trend_num)
```

画像をツイート
--
最後に、保存した画像をツイートします。

```python
def tweet_image(oauth, text, image_file):
    url_media = "https://upload.twitter.com/1.1/media/upload.json"
    url_text = "https://api.twitter.com/1.1/statuses/update.json"
    
    files = {"media" : open(image_file, 'rb')}
    req_media = oauth.post(url_media, files = files)
    if req_media.status_code != 200:
        print ('media upload failed: %s', req_media.text)
        exit()
        
    media_id = json.loads(req_media.text)['media_id']
    print ("Media ID: %d" % media_id)
    
    params = {'status': text + '\n' + signature, 'media_ids': [media_id]}
    req = oauth.post(url_text, params)

    if req.status_code == 200:
        print('tweet succeed!')
    else:
        print('tweet failed')

# ツイート文
tweet_text = 'search word: {}\nsearch tweets num: {}\ntrend words num: {}'.format(search_word, search_count, trend_num)
# ツイート
tweet_image(twitter, tweet_text, image_file)
```

<img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1315219/c4530837-6c04-6b50-f04a-e6317d8f1294.jpeg">

利益や暴落などの単語が正しく色分けされていることがわかります。これで、ビットコイン関連ツイートのネガポジを可視化することができました。

#### 課題
ネガポジの辞書に含まれない単語が多いため全体的に緑になっていまっています。ガチホなどの特有の単語は別でネガポジ辞書を用意した方がいいです。また、暴騰はネガティブですし、コインがポジティブ、取引がネガティブに分類されているのも紛らわしいかもしれません。

今回の検索ワード「ビットコイン」は、「ビット」と「コイン」に単語が分けられているので、WordCloudに出てきてしまっています。

APIにデータ取得の上限があるので、大量のツイートから可視化するのは難しいかもしれません。

最後に
--
ビットコイン関連ツイートをネガポジ判定して可視化することで、投資家心理の一部を見ることができました。
ちなみに、「コロナ」でネガポジを可視化すると、このようになりました。

<img width=80% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1315219/4c237aca-7e13-c8ef-150f-d371a8db00e9.jpeg">

参考サイト
--

#### Twitter API

https://qiita.com/Salinger/items/020b670466a9835c94bb

https://qiita.com/yubais/items/864eedc8dccd7adaea5d

#### Word Cloud

https://qiita.com/str32/items/4539e417a9cb333abd52

https://analytics-note.xyz/programming/wordcloud-color-function/

#### 形態素解析

https://www.udemy.com/course/tensorflow_rnn/learn/lecture/7732008#questions/6288392

https://ohke.hateblo.jp/entry/2019/02/09/141500

#### ネガポジ判定

https://news.mynavi.jp/article/zeropython-58/