Twitterアナリティクスのデータで形態素分析してコサイン類似度で類似ツイート検索してWordCloudを表示するまで

Twitterアナリティクスでデータ取得

まずTwitterアナリティクスからツイートのデータをby tweetで取得します。

下の画像の①〜③の手順でデータをダウンロードしてください。

データ整理

Jupyter LabやNotebookで、以下のコードを実行してファイルをまとめます。

import os
import glob
import pandas as pd

path = "./data_by_tweet"
files = glob.glob(path + '/tweet_activity*')

for f in files:
    os.rename(f, path + "/tw" + f[-25:-18] + ".csv")

files = glob.glob(path + '/tw_*')
tw = pd.DataFrame()
for f in files:
  tw = pd.concat([tw,pd.read_csv(f)])

次に、データクリーニングをしていきます。

tw['時間'] = pd.to_datetime(tw['時間'])
tw = tw.sort_values('時間', ascending=False)
tw = tw.reset_index(drop=True)

pd.set_option('display.max_columns', tw.shape[1])
print(tw.shape)
(1402, 40)

欠損値を確認します。

tw.isnull().sum()
URLクリック数                0
いいね                     0
アプリインストール             701
アプリ表示                 701
インプレッション                0
エンゲージメント                0
エンゲージメント率               0
ダイアル式電話               701
ツイートID                  0
ツイートの固定リンク              0
ツイートをメール送信            701
ツイート本文                  0
ハッシュタグクリック              0
フォローしている                0
プロモのURLクリック数          701
プロモのいいね               701
プロモのアプリインストール         701
プロモのアプリ表示             701
プロモのインプレッション          701
プロモのエンゲージメント          701
プロモのエンゲージメント率         701
プロモのダイアル式電話           701
プロモのツイートをメール送信        701
プロモのハッシュタグクリック        701
プロモのフォローしている          701
プロモのメディアのエンゲージメント数    701
プロモのメディアの再生数          701
プロモのユーザープロフィールクリック    701
プロモのリツイート             701
プロモの固定リンクのクリック数       701
プロモの詳細クリック            701
プロモの返信                701
メディアのエンゲージメント数          0
メディアの再生数                0
ユーザープロフィールクリック          0
リツイート                   0
固定リンクのクリック数           701
時間                      0
詳細クリック                  0
返信                      0
dtype: int64

無意味なデータを削除していきます。

tw = tw.replace({'-': pd.np.nan,'NaN': pd.np.nan, 'nan': pd.np.nan})
tw = tw.dropna(how='all',axis=1)
print(tw.shape)
(1402, 22)
# 重複しているツイートをツイートIDについて一意に変更
tw = tw.loc[~(tw.duplicated(subset=['ツイートID']))]
tw = tw.reset_index(drop=True)
tw = tw.fillna(0)

# 全部0の列を削除
all_0_cols = []

for col in tw.columns:
  if (tw[col] == 0).all() == True:
    all_0_cols.append(col)
    
print(all_0_cols)

tw = tw.drop(columns=all_0_cols)
tw = tw.sort_values('時間', ascending=False)
tw = tw.reset_index(drop=True)

print(tw.shape)
['アプリインストール', 'アプリ表示', 'ダイアル式電話', 'ツイートをメール送信', '固定リンクのクリック数']
(701, 17)

もう一度欠損値を確認します。

tw.isnull().sum()
URLクリック数          0
いいね               0
インプレッション          0
エンゲージメント          0
エンゲージメント率         0
ツイートID            0
ツイートの固定リンク        0
ツイート本文            0
ハッシュタグクリック        0
フォローしている          0
メディアのエンゲージメント数    0
メディアの再生数          0
ユーザープロフィールクリック    0
リツイート             0
時間                0
詳細クリック            0
返信                0
dtype: int64

良い感じなので出力します。

tw.to_csv('data_by_tweet/tw_cleaned.csv',index=False)

これである程度データをクリーニングして出力できました。

データクレンジング・可視化

ライブラリをインポートします。

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.family'] = 'IPAexGothic'
import seaborn as sns
sns.set()

先ほど出力したデータをインポートします。

tw = pd.read_csv('./data_by_tweet/tw_cleaned.csv')

カラムを追加します。

# カラム追加

# 画像や動画を添付している場合はTrue、そうでなければFalse
tw['media_exist'] = tw['ツイート本文'].str.contains('t.co')
# リプライしたツイートであればTrue、そうでなければFalse
tw['reply_flg'] = tw['ツイート本文'].str.contains('@')

import re

def format_text(text): # ツイート本文から余計なものを削除する
  text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
  text=re.sub(r'[!-~]', "", text)#半角記号,数字,英字
  text=re.sub(r'[︰-@]', "", text)#全角記号
  text=re.sub('\n', " ", text)#改行文字

  return text

tw['ツイート本文_x'] = tw['ツイート本文'].apply(lambda s: format_text(s))
tw['文字数'] = tw['ツイート本文_x'].str.len()

tw['時間'] = pd.to_datetime(tw['時間'])
tw['MD'] = tw['時間'].dt.strftime('%m%d')
tw['MONTH'] = tw['時間'].dt.month
tw['DAY'] = tw['時間'].dt.day
tw['TIME'] = tw['時間'].dt.hour
tw['WEEKDAY'] = tw['時間'].dt.weekday
tw['WEEK'] = tw['時間'].dt.week

tw = tw.drop(columns='ツイートの固定リンク')

print(tw.shape)
(701, 26)

時間帯ごとのインプレッション、エンゲージメント、いいね、リツイート、の平均と、時間帯ごとの平均ツイート数を比較します

fig = plt.figure(figsize=(20,12))
fig.suptitle('mean', fontsize=24)
axes1 = fig.add_subplot(221)
axes2 = fig.add_subplot(222)
axes3 = fig.add_subplot(223)
axes4 = fig.add_subplot(224)

s = tw.groupby('TIME').size()
data1 = tw.groupby('TIME')['インプレッション'].mean()
data2 = tw.groupby('TIME')['エンゲージメント'].mean()
data3 = tw.groupby('TIME')['いいね'].mean()
data4 = tw.groupby('TIME')['リツイート'].mean()


axes1.plot(data1.index, data1.values, label=data1.name)
axes1_s = axes1.twinx()
axes1_s.bar(data1.index,s,alpha=0.3,label='ツイート数')

axes2.plot(data2.index, data2.values, label=data2.name)
axes2_s = axes2.twinx()
axes2_s.bar(data2.index,s,alpha=0.3,label='ツイート数')

axes3.plot(data3.index, data3.values, label=data3.name)
axes3_s = axes3.twinx()
axes3_s.bar(data3.index,s,alpha=0.3,label='ツイート数')

axes4.plot(data4.index, data4.values, label=data4.name)
axes4_s = axes4.twinx()
axes4_s.bar(data4.index,s,alpha=0.3,label='ツイート数')

axes1.set_title(data1.name)
axes2.set_title(data2.name)
axes3.set_title(data3.name)
axes4.set_title(data4.name)

axes1.set_xticks(data1.index)
axes2.set_xticks(data2.index)
axes3.set_xticks(data3.index)
axes4.set_xticks(data4.index)

axes1_s.legend(loc=0)
axes2_s.legend(loc=0)
axes3_s.legend(loc=0)
axes4_s.legend(loc=0)

axes1.legend()
axes2.legend()
axes3.legend()
axes4.legend()
print(np.corrcoef(s.values, data1.values))
print(np.corrcoef(s.values, data2.values))
print(np.corrcoef(s.values, data3.values))
print(np.corrcoef(s.values, data4.values))
[[ 1.         -0.65646947]
 [-0.65646947  1.        ]]
[[ 1.         -0.60037046]
 [-0.60037046  1.        ]]
[[ 1.         -0.61819986]
 [-0.61819986  1.        ]]
[[ 1.         -0.55087126]
 [-0.55087126  1.        ]]

それぞれの項目の時間帯別中央値や平均が、時間帯別ツイート数と負の相関関係にあることがわかります。

形態素解析

形態素解析とは、文章を構成されている最初単位の単語に分けて、解析する方法です。今回は名詞のみでやっていきます。

その単語が出てきた「いいね」の平均と、その単語がでてきた回数を見れるようにしてみました。また、でてきた回数が3回より多いもののみに限定しています。

import MeCab

tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/ipadic')
all_words = []
goods = []
parts = ['名詞']
for n in range(len(tw)):
  text = tw.iloc[n]['ツイート本文_x']
  words = tagger.parse(text).splitlines()
  words_arr = []
  for i in words:
    if i == 'EOS' or i == '':continue
    word_tmp = i.split()[0]
    part = i.split()[1].split(',')[0]
    if not (part in parts):continue
    words_arr.append(word_tmp)
    goods.append(tw.iloc[n]['いいね'])
  all_words.extend(words_arr)
  
df = pd.DataFrame({
  'word':all_words,
  'いいね':goods,
  '回数':len(all_words) * [1]
})

df_good = df.groupby('word')['いいね'].mean()
df_count = df.groupby('word')['回数'].sum()
df_a = pd.concat([df_good,df_count],axis=1)

df_rank = df_a.loc[df_a['回数']>3].sort_values('いいね', ascending=False).reset_index()

df_rank.head()

意外にもダッフィという言葉がフォロワーからいいねされているようです。これは、ペットとぬいぐるみの組み合わせがフォロワーの心をくすぐっていることを示唆しているのかもしれません。

とはいえ、他のぬいぐるみの情報が取れていないので、たまたま「ダッフィ」の記事がバズっただけの可能性が高そうです。

それでも他のぬいぐるみをツイートに登場させることで、ぬいぐるみの存在がいいね数と相関しているかを調べることは有益だと考えられます。

類似ツイート検索

類似のツイートを探す方法を実装していきます。まず名詞、動詞、形容詞で形態素解析します。

tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/ipadic')
all_words_df = pd.DataFrame()
parts = ['名詞','動詞','形容詞']
goods = []
for n in range(len(tw)):
  text = tw.iloc[n]['ツイート本文_x']
  words = tagger.parse(text).splitlines()
  words_df = pd.DataFrame()
  for i in words:
    if i == 'EOS' or i == '':continue
    word_tmp = i.split()[0]
    part = i.split()[1].split(',')[0]
    if not (part in parts):continue
    words_df[word_tmp] = [1]
    goods.append(tw.iloc[n]['いいね'])
  all_words_df = pd.concat([all_words_df, words_df],ignore_index=True)

all_words_df = all_words_df.fillna(0)
all_words_df

インプレッション、いいね、ユーザープロフィールクリックそれぞれが最高のツイートのインデックスを取得します。

print(tw.loc[tw['インプレッション'] == tw['インプレッション'].max()].index)
print(tw.loc[tw['いいね'] == tw['いいね'].max()].index)
print(tw.loc[tw['ユーザープロフィールクリック'] == tw['ユーザープロフィールクリック'].max()].index)
Int64Index([230], dtype='int64')
Int64Index([234], dtype='int64')
Int64Index([536], dtype='int64')

今回はインデックス230をえらびました。

コサイン類似度で探します。

tar_text = all_words_df.iloc[230]

# コサイン類似度で検索

cos_sim = []
for i in range(len(all_words_df)):
  cos_text = all_words_df.iloc[i]
  cos = np.dot(tar_text, cos_text) / np.linalg.norm(tar_text) * np.linalg.norm(cos_text)
  cos_sim.append(cos)
all_words_df['cos_sim'] = cos_sim
all_words_df = all_words_df.sort_values('cos_sim', ascending=False)
all_words_df.head()

類似ツイートを表示します。

for idx in all_words_df.head(11).index[1:]:
  print("**********",idx, "**********")
  print(tw.iloc[idx]['ツイート本文_x'])
********** 614 **********
 こちらこそありがとうございます❤ よろしくお願いします🥰
********** 22 **********
 初回は本セット円でした 明日、で動画するので ぜひ参考にしてみてください💞🥰
********** 588 **********
 こんにちは💞 ママを敵と認識するんですね🥰 みるくもそうなのかもしれないです😌
********** 438 **********
 毛の種類が一緒です😂
********** 240 **********
めちゃくちゃ可愛い😍 
********** 583 **********
 こちらこそありがとうございます❤ よろしくお願いします💞 これから仲良くしてくれたら嬉しい限りです🥰
********** 239 **********
バスローブ着てみました🐶 
********** 566 **********
夕暮れと白い犬と飼い主 公園 夕暮れ 飼い主 犬 犬好きさんと繋がりたい 犬好きな人と繋がりたい トイプードル 
********** 436 **********
🐶生まれて初めて靴下履きました😆  犬好きさんと繋がりたい 
********** 311 **********
 今日も元気いっぱいみるくです🥰 ありがとうございます💞

どうもリプライのツイートだったっぽいですね。リプライはインプレッションが意外と高いのかもしれません。

WordCloud

WordCloudはよく出る単語を大きな文字で表示してくれるツールです。

以下のように、日本語フォントを指定する必要があります。

IPAゴシック等の日本語フォントを導入していない方はまず導入してください。

from wordcloud import WordCloud

fig = plt.figure(figsize=(12,10))
font_path="/Users/username/opt/anaconda3/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/ipaexg.ttf"
splitted = ' '.join([x.split('\t')[0] for x in all_words_df.columns])
wordc = WordCloud(font_path=font_path,background_color='white',width=800, height=600).generate(splitted)

plt.imshow(wordc)
plt.axis('off')
plt.show()