Twitterのポジティブ／ネガティブな言葉だけでワードクラウドを作成する

# 記事の内容
Pythonの勉強の一環としてTwitterAPI、ワードクラウド作成、単語のネガポジ判定などをやってみました。

やったこととしては「転校少女」というキーワードと一緒にツイートされている言葉のうち、ポジティブな言葉（形容詞、動詞）のみを使ってWordCloudを作成しました。
※「転校少女」は転校少女*というアイドルグループの名前です。
アスタリスクまで付けると取得出来るデータが減るので今回は除外しています。

ステップとしては以下の手順で作成しています。

１．TwitterAPIを使ってツイートデータを取得し、mongoDBに登録
２．日本語評価極性辞書をmongoDBに登録
３．ツイートの内容をネガポジ判定し、それぞれのWordCloudを作成

# １．TwitterAPIを使ってツイートデータを取得し、mongoDBに登録
早速ツイートを取得してみます。

``` get_time_lines.py
import json
import config
from requests_oauthlib import OAuth1Session
from time import sleep
import emoji
from mongo_dao import MongoDAO

# 絵文字を除去する
def remove_emoji(src_str):
    return ''.join(c for c in src_str if c not in emoji.UNICODE_EMOJI)

# APIキー設定(別ファイルのconfig.pyで定義しています)
CK = config.CONSUMER_KEY
CS = config.CONSUMER_SECRET
AT = config.ACCESS_TOKEN
ATS = config.ACCESS_TOKEN_SECRET

# 認証処理
twitter = OAuth1Session(CK, CS, AT, ATS)  

# タイムライン取得エンドポイント
url = "https://api.twitter.com/1.1/search/tweets.json"  

# 取得するキーワード
keyword = '転校少女'

# パラメータの定義
params = {'q': keyword,
          'count': 200}

# arg1:DB Name
# arg2:Collection Name
mongo = MongoDAO("db", "tenkou")
mongo.delete_many({})

# 最新の200件を取得／2回目以降はparams['max_id']に設定したIDより古いツイートを取得
for j in range(100):
    res = twitter.get(url, params=params)
    if res.status_code == 200:
        # API残り回数
        limit = res.headers['x-rate-limit-remaining']
        print("API remain: " + limit)
        if limit == 1:
            sleep(60*15)

        n = 0
        result = json.loads(res.text)
        # ツイート単位で処理する
        tweets = result['statuses']
        for tweet in tweets:
            # 絵文字があると、wordcloudが使用できないため、削除する
            tweet['text'] = remove_emoji(tweet['text'])
            # ツイートデータを丸ごと登録
            mongo.insert_one(tweet)
        
            if len(tweets) >= 1:
                params['max_id'] = tweets[-1]['id']-1
```
configは別のpyファイルに書き出し

``` config.py
CONSUMER_KEY = "****"
CONSUMER_SECRET = "****"
ACCESS_TOKEN = "****"
ACCESS_TOKEN_SECRET = "****"
```

mongoDBの操作用classはこんな感じです

``` （一部抜粋）mongo_dao.py
from pymongo import MongoClient

class MongoDAO(object):

    def __init__(self, dbName, collectionName):
        self.client = MongoClient()
        self.db = self.client[dbName] #DB名を設定
        self.collection = self.db.get_collection(collectionName)

    def insert_one(self, document):
        return self.collection.insert_one(document)

    def insert_many(self, documents):
        return self.collection.insert_many(documents)
```
ツイートのデータはtextのデータだけあれば十分なんですが、後から何かに使えるかもしれないと思い、全て突っ込んでいます。

# ２．日本語評価極性辞書をmongoDBに登録
この処理では単語のネガティブ／ポジティブ判定用のデータを作成します。
判定用のデータは東北大学 乾・岡崎研究室 が作成、公開している「日本語評価極性辞書（名詞編）」を使用しました。

[日本語評価極性辞書（名詞編）](http://www.cl.ecei.tohoku.ac.jp/index.php?Open%20Resources%2FJapanese%20Sentiment%20Polarity%20Dictionary)

辞書データは以下の様な形式になっています。

```
２，３日	e	〜である・になる（状態）客観
10%	e	〜である・になる（状態）客観
100%	e	〜である・になる（状態）客観
```
データは「単語」、「ネガティブ（n）／ポジティブ（p）／ニュートラル（e）」、「状態」がタブ区切りで登録されています。

``` insert_noun.py
from mongo_dao import MongoDAO
import codecs

mongo = MongoDAO("db","noun")

dict_path = './dict/noun_dict.trim'

with codecs.open(dict_path, "r", "utf-8") as f:
    for line in f:
        d = line[:-1].split('\t')
        if d[1] == 'n':
            d.append(-1)
        elif d[1] == 'p':
            d.append(1)
        else:
            d.append(0)
        mongo.insert_one({"word": d[0], "np": d[1], "evaluation": d[2], "score": d[3]})
```

この処理ではダウンロードした辞書データをタブ区切りにしてmongoDBに突っ込んでます。
ネガポジ判定は何かに使えるかもしれなかったので、ポジティブを「1」、ネガティブを「-1」、それ以外を「0」とするデータ「score」を追加しています。

# ３．ツイートの内容をネガポジ判定し、それぞれのWordCloudを作成


``` tweet_analyze.py
import MeCab
from mongo_dao import MongoDAO
import word_cloud
from wordcloud import WordCloud

target = "tenkou"
#MeCab準備
tagger = MeCab.Tagger("-Ochasen")

# mongoDBからデータを取得する
mongo = MongoDAO("db", target)
target_results = mongo.find()

# 解析結果の格納用
positive_words = []
negative_words = []
neutral_words = []
tweet_score = 0

# DBの接続先を辞書データに変更
mongo = MongoDAO("db", "noun")

for target_result in target_results:
    text = target_result['text']
    mecab_results = tagger.parse(text)

    for result in mecab_results.split('\n'):
        word = result.split('\t')[0]
        mongo_result = mongo.find_one(filter={"word":word})

        if type(mongo_result) is dict:
            tweet_score += mongo_result['score']
            if mongo_result['np'] == 'n':
                negative_words.append(word)
            elif mongo_result['np'] == 'p':
                positive_words.append(word)
            elif mongo_result['np'] == 'e':
                neutral_words.append(word)
        else:
            neutral_words.append(word)

# ワードクラウドから除外する単語
stop_words = ['RT','@', '//','NECOPLASTIC', 'ネコプラ', 'ネコ','chuLa', 'FESTIVE','FES', 'TIVE',
            'ナナランド','JYAPON','ナナ','ランド','JAPONISM','JYA','NEO','PON','なんキニ','なん',
            'キニ','する','とる','てる','くる','なる','いる','れる','せる','おる','どる','ぶる']
# fontは自分の端末にあるものを使用する
font_path = 'C:\\WINDOWS\\Fonts\\meiryo.ttc'

# ポジティブワードでワードクラウドを作成
wordcloud = WordCloud(background_color="white",font_path=font_path,contour_color='steelblue', collocations = False,
                    contour_width=3,width=900, height=500,stopwords=set(stop_words)).generate(word_cloud.parseWordCloudText(positive_words))
wordcloud.to_file("./output_wordcloud/wordcloud_" + target + "_positive.png")

# ネガティブワードでワードクラウドを作成
wordcloud = WordCloud(background_color="white",font_path=font_path,contour_color='steelblue', collocations = False,
                    contour_width=3,width=900, height=500,stopwords=set(stop_words)).generate(word_cloud.parseWordCloudText(negative_words))
wordcloud.to_file("./output_wordcloud/wordcloud_" + target + "_negative.png")
```

``` word_cloud.py
from janome.tokenizer import Tokenizer
from collections import defaultdict

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 ["こと", "よう", "そう", "これ", "それ"]:
                    words_count[token.base_form] += 1
                    words.append(token.base_form)
    return words_count, words

def parseWordCloudText(textList):
    return " ".join(textList) if type(textList) is list else ""
```

# 参考
[Python - Word Cloud を作成する方法について](https://www.pynote.info/entry/python-wordcloud)
[pythonで日本語文の感情分析（＋言語処理の基礎）](https://qiita.com/mtskhs/items/0323f299d03f5b07efdc)
大変勉強になりました。
ありがとうございます。

# 実行結果
ポジティブ単語のワードクラウド
![wordcloud_tenkou_positive.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/476876/9b31709f-c496-d580-86de-8d5058bfbfc8.png)
「情熱」、「美少女」、「完璧」、「清楚」など素晴らしくアイドルらしい言葉と一緒にツイートされていることが分かりました。

ネガティブ単語のワードクラウド
![wordcloud_tenkou_negative.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/476876/632f56ee-4455-550c-1440-ee6be5233adf.png)
ツイートを検索してみると転校少女というワードと一緒にいじめにあった方のツイートなどが多数見つかりました。
こういったツイートも影響してこの結果になったのだと推測できます。

# 感想
今回の内容でpythonを使った自然言語処理の触りを学ぶことが出来ました。
ツイッターをデータソースとする解析は硬い文章ではないので、解析に引っかからない単語が多くある。
その他、処理したい内容に関連するツイート以外も検索に多数引っかかってしまうので、そこをどう除外するのかも難しいポイントだと感じました。

また、ネガポジ判定には「ヤバい」などの良い意味でも悪い意味でも使われる言葉を文脈からどちらかなのかを判断する方法がかなり難しいことを実感しました。