安倍首相に関するtweetデータをクラスタリングして類似を分析してみた (Part 2)


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/b39bf50e-fdc3-ab01-45db-14d02fcc733f.png)


## 前回までのおさらい

[前回](https://qiita.com/hidekoji/items/fa6e0634f338336110a4)はTwitterから安倍首相に言及されたTweetをインポートし、RMeCabでトークナイズして、ストップワード等を取り除き、頻出する単語が何かを確認するところまで見てきました。Part 2では、Tweetのテキストから、Tweetをクラスタリングして類似性を分析してみます。

## Tweetのクラスタリングまでの道のり

安倍首相に関するTweetを、類似するクラスタにクラスタリングするには

1. 前回単語分けされた安倍首相関連のTweetsのテキストを使う
2. Nグラムのアルゴリズムを使って1単語と2単語の組み合わせのテキストを作成
3. TF-IDFのアルゴリズムを使ってそれぞれのTweetを数値化する
4. SVDのアルゴリズムを使って次元削減する
5. K-Meansクラスタリングのアルゴリズムを使ってTweetsを似た者同士のグループに分ける

という手順を踏んでいきます。では早速順場に見ていきましょう。

## 前回単語分けされた安倍首相関連のTweetsのテキストを使う

前回Part1で説明した、1)単語分けして、2)ストップワードを取り除き、3)動詞と名詞だけに絞り、4)文字数が１文字以上にした安倍首相に関するTweetデータをここでは使います。

## Nグラムのアルゴリズムを使って1単語と2単語の組み合わせのテキストを作成

Part1でトークナイズしたトークン(形態素)からNグラムを生成します。Nグラムとはテキスト内の連続するn個の表記単位（gram）でNが 1ならユニグラム(uni-gram)、2ならバイグラム(bi-gram)、3ならトリグラムまたはトライグラム(tri-gram)と呼ばれます。

緑のプラスボタンを押して、**テキストマイニング** メニューより**Nグラムトークンを作成** を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/e2e6f09e-cf6f-ff34-1dbb-b9e1e7c831f7.png)


トークナイズされたテキストがある列に**.token** 列を指定します。また、ここでは、1 tweet = 1文書 = 1センテンスとして扱うので、文書ID列とセンテンスID列両方に**status_id** 列を指定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/b5528963-4c54-1d23-52bb-bbfcca218c72.png)


すると、以下のようなNグラムが生成されます。バイグラムの時は**自衛隊_日報**のように２つの単語が**_** で接続されているのが分かります。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/8a31514e-8bd3-31ab-d333-724a85aa1eb0.png)


これをバーチャートにすると、 **支持、拉致** といったN グラム(１つなのでユニグラム)が上位に来ているのが分かります。
　
![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/21dd7ca3-d274-6bd2-2a7d-42eee7078f07.png)


## TF-IDFのアルゴリズムを使ってそれぞれのTweetを数値化する

TF-IDFは文書に含まれる単語がどれだけ重要かを示す手法の一つで、TF (= Term Frequency: 単語の出現頻度)とIDF (Inverse Document Frequency: 逆文書類度)の２つを使って計算します。TFはここではあるTweetに何回このNグラムが出現したのかを、IDFは沢山あるTweet全体の中で、このNグラムが何回現れたかの**逆数**を示します。これは、その単語が他のTweetにはなかなか出現しない「レアな単語」なら高い値を、「色々なTweetによく出現する単語」なら低い値を示すものです。つまりレアな単語は、そのTweetの特徴を判別するのに有用ということになります。

緑のプラスボタンを押して、**テキストマイニング** -> **TF-IDFを計算** を選びます。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/89183f42-b7b9-ede2-9122-7aa00a327078.png)



文書ID列に**status_id**列を、トークナイズされたテキストがある列に**token**列を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/402ac72f-d0de-debf-a4fc-f7fba6621012.png)

**実行**ボタンを押すと、次のようにTF-IDFが計算されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/8da5eca2-e3d5-0a21-7980-b7026beaa563.png)

## SVDのアルゴリズムを使って次元削減する

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/0a122303-b408-4417-a57a-74ac33a76ce5.png)


実はテキスト分析のときにはこれら一つ一つのユニークな単語のことを次元と言います。ここで、縦（行）に文書のID、この場合だとそれぞれのTweetのID、そして横（列）にそれぞれの単語が割り当てられたマトリックスのようなものを想像してみて下さい。そうすると、それぞれのTweetの特徴が、それぞれの列にある単語に対する数値（スコア）によって決まるわけです。

しかし、ここで気をつけなくてはいけないのは、その列の数が24,258個（次元）あるということです。しかしそもそもそれぞれのTweetは140単語以下なので、ほとんどの列（次元）は０もしくはNAとなっているはずです。つまり以下の表のようなスッカスカのマトリックスであるということです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/9a9162c8-8bdd-f864-b533-efa94f505447.png)

こうしたデータをそのまま使ってクラスタリング等のアルゴリズムを適用すると結果に歪みが生じてしまいます。こういったことをCurse of High Dimensionality([次元の呪い](https://ja.wikipedia.org/wiki/%E6%AC%A1%E5%85%83%E3%81%AE%E5%91%AA%E3%81%84)) と言ったりします

そこで、本質的な特徴を残しつつ、重要でない情報量を削減することができる次元削減という手法を使うことになるのですが、ここでは、SVD (Singular Value Decomposition: [特異値分解](https://ja.wikipedia.org/wiki/%E7%89%B9%E7%95%B0%E5%80%A4%E5%88%86%E8%A7%A3))という次元削減のアルゴリズムを使ってみます。

ところで、このSVDですが、Exploratoryの現行のバージョンのUIで用意されているのは、各次元が列になっているワイド型のデータに対応したものです。今回、次元はtfidfという列に入ったロング型のデータとなっているのでUIでは対応できません。

そこで、UIではサポートされていない**do_svd.kv**というロング型のデータに対応したSVDの関数を使います。実行する方法として、関数を直接指定できるカスタム・コマンドを使います。(ちなみにこの**do_svd.kv**は次のバージョンからUIでサポートされるようになります。)

緑のプラスボタンを押して、カスタムコマンドを選びます。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/b0f5fc9f-f1bd-2fea-be2b-884d4aeb2df1.png)


今回はTweet毎に単語をtfidfの値に基づいて次元削減をしたいので、一番最初の引数(対象列)にTweetのIDである**status_id**を、２番目の引数(キー列)にNグラムである**token**を、３番目の引数(値列)に**tfidf**を指定します。また、この例では4次元まで次元数を減らしてみるので、n_componentsには4を指定します。

```r
do_svd.kv(status_id, token, tfidf, n_component=4)
```

実行すると、以下のようにTweet毎に４つまで次元数が削減されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/28ed1df0-9920-1168-03c7-8b6309221d2e.png)


## K-Meansクラスタリングのアルゴリズムを使ってTweetsを似た者同士のグループに分ける

さて、無事次元数も４つまで削減されたので、この４つの次元を用いて、Tweetをクラスタリングします。ここでは[K-Means](https://ja.wikipedia.org/wiki/K%E5%B9%B3%E5%9D%87%E6%B3%95)というアルゴリズムを使います。

### データをロング型からワイド型へ変換

今、SVDを実行した結果はロング型のデータフレームとなっています。つまり、
new.dimensionという列に各次元(1,2,3,4)が、value列にそれぞれの値が入る 縦長なデータフレームとなっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/68f527b8-0fb3-cc82-748e-fadc4aaf9202.png)

K-Meansクラスタリングでは、データがワイド型、つまり、1つの次元(変数)が1列であることを前提としているので、ワイド型に変換しましょう。new.dimensionとvalueの列をコマンドキーとクリック( Windowsの場合はコントロールキーとクリック)で両方選択し、列のメニューから**Spread (ロング型からワイド型へ)**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/934ddde5-0e77-f602-b9eb-ce47c950a00d.png)


開いたダイアログでOKボタンを押します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/e199b938-2e1f-b5b5-4c90-71784024bf59.png)


すると、以下のように、一つの次元(変数)が一つの列で、１つのTweetが１つの行であるワイド型のデータになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/974e8b63-fdbc-1595-3772-23e5deb0e6b9.png)

### K-Meansでクラスタに分ける

次に、1から4までの次元の列をコマンドキー(Windowsはコントロールキー)を押しながらクリックし、４つとも選択します。列メニューより、**分析** -> **K平均法でクラスタリング** -> **選択された範囲**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/bab4a2f3-0c88-a24c-6083-682e61a43486.png)


ダイアログの中でクラスタの数をここでは6に設定し、**実行**ボタンを押します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/55adc463-14a9-dbd0-eb23-ebc768cd3cfc.png)


すると、データフレームに**cluster**という列が追加され、各Tweetにそれぞれ1から6の何れかのクラスの値が割り当てられました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/14ece667-ab56-191c-9077-823da3762651.png)

ちなみに、今回はクラスタ数は６と指定しましたが、これは[最適なk-meansのkを見つける](https://exploratory.io/note/hideaki/5761630931545019)というブログの方法を使用して求めました。

## クラスタリングの結果を可視化する

では、計算されたクラスタの情報を使ってTweetを可視化してみましょう。
ところで、今Tweetのテキスト情報は、下の図を見ると分かるように、クラスタを計算したデータフレームに残っていません、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/892af308-2ff6-b2a3-4c34-adc786abb3b4.png)

そこで、可視化のためにクラスタの情報と元のTweet情報と結合します。

### 元のTweet情報を持つブランチを作成

SVDとK-Meansを実行した後には、上の表を見てわかる通り、text列はありません。しかし、同じデータフレームの先頭のステップ(画面右側のステップの先頭)に行くと、元々のTweetがtext列に残っているのが下の図のように分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/86bab734-a7c9-dbe8-034f-64190706d3a1.png)


このTweetのテキスト情報をスナップショットとして保存しておくために、tweet_abeというデータフレームの一番先頭のソースのステップ(画面右側のステップの先頭)のメニューをクリックし、**ブランチの作成**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/1787759d-93b5-42a2-3e35-b7ca6fab0481.png)



名前を入れて**作成**ボタンをクリックします。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/c56be507-2b5d-6136-a35a-ca7b4254bbac.png)


すると、画面左手のツリーのデータフレームのセクションに**tweet_abe_source**というブランチが**tweet_abe**というデータフレームの下に出来たのが確認できます。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/2290df6e-86bf-9f6e-e767-fe02561e4f2e.png)

最終的に必要なのは結合キーに使うstatus_idと、Tweet情報のtext列だけなので、この２つの列だけを残します。status_id列をtext列をコマンドキー(Windowsではコントロールキー)とクリックで両方選択し、**残す** -> **選択された列**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/cc4f090b-639e-e32b-287a-bd167a385fe3.png)

status_idと、Tweet情報のtext列だけになりました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/dbccf35d-c6ea-40ca-c6cf-342296a5eda4.png)


さて、SVD及びクラスタを計算したデータフレーム(tweet_abe)に戻り、先ほどとったスナップショット(tweet_abe_sourceというブランチ)のtext列と結合しましょう。緑のプラスボタンを押して、**結合**をメニューから選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/543d95ee-983e-53cc-4915-2c079894995d.png)


結合の相手先に、今作成したブランチ(tweet_abe_source)を指定し、マッチさせる列に**status_id**をそれぞれ指定し、**実行**ボタンをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/d534f93e-13cc-98f7-a0fe-9963136dc28b.png)


これでクラスタとSVDの計算結果(X1からX4の列)とTweetのテキストが以下のように一つのデータフレームに結合されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/d4cbb3b8-5726-a5a9-03e5-ca669b5a4ade.png)


### 散布図でTweetのクラスタを可視化

次にVizビューに行き、散布図を作成します。X軸に**X1**列を、Y軸に**X2**列を、Z軸に**X3**列を割り当てます。(注意：Z軸を使った3次元の散布図は現在Macのみのサポートとなります。Windows版ではご利用いただけません
)　次に色で分割に**cluster**列、ラベルに**text**列をそれぞれ割り当てます。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/92b20c52-33ed-7d82-b2de-ca883a8d89f1.png)

散布図に６つのクラスタが可視化されました。それぞれの点をホバーすると、次のようにTweetのテキストの内容を確認できます。例えば緑のクラスタのある一点をマウスでホバーすると、次のようなTweetであることが分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/f2574c44-e92c-84d1-0ac2-bbbb7ed57891.png)


### クラスタ毎の特徴

では次に、各クラスタ毎に頻出する単語をみて、クラスタ毎の特徴を確認してみましょう。そのためには単語とクラスタの情報を一つのデータフレームに集める必要があります。

SVDとK-Meansを実行した後には、単語を保持するtoken列はありません。しかし、同じデータフレームの単語を導出したのステップ(画面右側のCreate N-Gramのステップ)に行くと、元々の単語がtoken列に残っているのが下の図のように分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/96c81215-b9f7-59db-bdd4-7f7022ba6280.png)

この単語情報をスナップショットとして保存しておくために、tweet_abeというデータフレームのCreate N-gramのステップのメニューをクリックし、**ブランチの作成**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/f246894b-f2f1-a6fe-5526-80bf87820154.png)



tweet_abe_ngramという名前のブランチを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/b1613c34-bec6-fb3b-dc4e-170cb8f9c1ea.png)

クラスタ情報を持つtweet_abeのデータフレームの下に、単語情報を持つtweet_abe_ngramができました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/35d9882b-4841-d7d7-4a1c-897aa44e70bc.png)


最終的に必要なのは結合キーに使うstatus_idと、単語情報のtoken列だけなので、先ほどと同じようにこの２つの列だけを残します。status_id列をtoken列をコマンドキー(Windowsではコントロールキー)とクリックで両方選択し、**残す** -> **選択された列**を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/a15ce769-6ebc-76d9-e499-7794f2d92048.png)

status_idと、単語情報のtoken列だけになりました。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/34474050-465d-b350-af05-168407cdc2a1.png)



さて、SVD及びクラスタを計算したデータフレーム(tweet_abe)に戻り、先ほどとったスナップショット(tweet_abe_ngramというブランチ)のtoken列をtweet_abeデータフレームに結合しましょう。緑のプラスボタンを押して、**結合**をメニューから選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/0c82144e-2259-77a3-e4ce-70ba1bc5aa63.png)


結合の相手先に、今作成したブランチ(tweet_abe_ngram)を指定し、マッチさせる列に**status_id**をそれぞれ指定し、**実行**ボタンをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/422260e3-44fb-3112-57f4-63db58969024.png)

これでクラスタとSVDの計算結果(X1からX4の列)と単語が以下のように一つのデータフレームに結合されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/e7ebc505-b5f7-3e2f-7b33-6f185878072e.png)


クラスタ毎のTop 5の頻出単語を計算しましょう。

clusterとtokenでグループ化して、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/bf17f557-fb7a-66d8-ccff-503b9377b594.png)

各件数を集計して、単語の出現頻度を求めます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/2b838559-f007-df2e-c68c-3e423d5ad2f6.png)

すると集計結果が以下のように分かります。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/012a24bf-dc2d-a947-786a-1454ad4ec405.png)


各クラスタで頻度が上位の5つだけに残します。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/b9403676-67e3-7bb2-0a7b-49f6e2d08003.png)


すると、以下のような結果になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/58621227-4fc2-81ae-6ed6-788a006a939e.png)



バーチャートで、**繰り返し**にcluster列を割り当てて、先ほど上位５までに絞った各単語の頻度を見ると、以下のようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/3811a7a1-d223-2483-41a7-2e9cb14f5172.png)



上のバーチャートを見てみると、どうやらそれぞれのクラスタを特徴付ける単語は以下のようです。

* クラスタ 1 (青色) - 米国、首脳
* クラスタ 2 (オレンジ) - 支持、調査
* クラスタ 3 (緑) - 拉致、横田
* クラスタ 4 (赤) - 支持、拉致
* クラスタ 5 (紫) - 柳瀬、国会
* クラスタ 6 (茶) -  愚か、教授

では各クラスタに実際にどんなTweetがあるのか例を見てみましょう。

#### クラスタ １(青色) - 米国、首脳
１のクラスタに関しては同率のものが多かったのですが、ここでは米国と首脳という単語を取り上げて見ます。

このクラスタのTweet例です。

```
[朝日]対話表明の米に「周回遅れ」日本　首脳会談、蜜月に誤算 https://t.co/muy64UTEwT 安倍晋三首相が１７、１８両日に米国でトランプ大統領と会談する。米国の米朝対話表明の真意を確かめる狙いだが、急きょの訪米には、首脳間の蜜月への誤算がにじむ。北朝鮮問題で日米の「完全一致」を演出した… https://t.co/eqht6qK3Y5
```

#### クラスタ 2 (オレンジ) - 支持、調査

```
日本テレビ世論調査 内閣支持率26.7%だって。安倍首相2日後の訪米前にあえてこの数字なのかね。基本、新聞社のやる世論調査の内閣支持率って各社の思惑とかで創出するものだろうし、そこで最も自民党内の意向示して影響あるっぽい読売がこのタイミングでこの数字出すって与党内荒れてきた証拠ね
```

#### クラスタ 3 (緑)- 拉致、横田


```
滋さんのご回復をお祈り申し上げます。めぐみさん他拉致被害者全員の帰国が果たされる事を現政権には成し遂げていただきたい。\n#拉致被害者全員奪還\n#特定失踪者全員奪還\n【北朝鮮拉致】安倍晋三首相が横田めぐみさんの父、滋さん見舞う https://t.co/pKXzQHXAPq @Sankei_newsから
```

#### クラスタ 4 (赤) - 支持、拉致


```
大河とNスペの間のニュースで「安倍首相が訪米を前に横田滋さんを訪問」ってのがあったんだけど、\n「拉致問題の解決を（云々）…話した"か"」ってなってて、映像も車に乗り込むとこ以外は過去の映像。\nこれ、囲み取材がなかったってことよね？支持率上げますパフォーマンスですら、囲み取材が怖いと。
```

#### クラスタ 5 (紫) - 柳瀬、国会


```
シリアの空爆やゴールデンウイークなどで世間が森友、加計疑惑を忘れるのを待つ作戦です」「首相案件」は他の愛媛県メモにも 訪米で柳瀬氏国会招致の引き伸ばし図る安倍官邸 https://t.co/0llshqchHR
```

#### クラスタ 6 (茶) -  愚か、教授

```
「愚かなアベ」しか言ってない。\nこれヘイトだよね。\n\n【動画あり】慶應大・金子勝教授「安倍は歴代の首相の中で最も愚かな首相だ！バカほど恐ろしいものはない」 https://t.co/FZzvn4es8o
```


なお、緑のクラスタ3と赤のクラスタ4は、どちらも横田めぐみさんや北朝鮮拉致に関する内容なので、先ほどの散布図で確認すると似た方向に向かっているのが分かります。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/90296/04003d48-840a-1e6a-95cd-64b536eef569.png)


# まとめ

今日は、いくつものアルゴリズムを使って最終的に安倍首相関連のTweetを6つのタイプに分け、それぞれの特徴をつかんでみました。もう一度簡単に振り返ると、以下のようなステップをたどりました。

1. 前回単語分けされた安倍首相関連のTweetsのテキストを使う
2. Nグラムのアルゴリズムを使って1単語と2単語の組み合わせのテキストを作成
3. TF-IDFのアルゴリズムを使ってそれぞれのTweetを数値化する
4. SVDのアルゴリズムを使って次元削減する
5. K-Meansクラスタリングのアルゴリズムを使ってTweetsを似た者同士のグループに分ける

こうすることで、見かけ上は数値化されていないテキストのようなものでも類似度を計算することができ、一見ばらばらでたくさんあるように見えるTweetのようなテキストデータの全体像が効率的に浮かび上がってくるわけです。

まだExploratory Desktopをお持ちでない場合は、[こちら](https://exploratory.io/?utm_campaign=twitter_abe2&utm_medium=blog&utm_source=qiita)から30日間無料でお試しいただけます。

# データサイエンスを本格的に学んでみたいという方へ

6月の中旬に、Exploratory社がシリコンバレーで行っているトレーニングプログラムを日本向けにした、[データサイエンス・ブートキャンプ](https://exploratory.io/training-jp?utm_campaign=twitter_abe2&utm_medium=blog&utm_source=qiita)が東京で行われます。データサイエンスの手法を基礎から体系的に、プログラミングなしで学んでみたい方、そういった手法を日々のビジネスに活かしてみたい方はぜひこの機会に、参加を検討してみてください。