Google Natural Language APIでtweetの感情スコアをグラフ化してみた

#はじめに
　自然言語処理を少し触ってみたかったので、簡単に使えそうな[Google Cloud Natural Language API](https://cloud.google.com/natural-language/docs/?hl=ja)を使ってみました。このAPIは与えられたデータの「エンティティ分析」や「構文解析」など行うことができるのですが、今回はぱっと見面白そうな「**感情分析**」を使用してみました。

#解析するデータ
　どの言語データの感情を分析するか迷ったのですが、一番身近な自分のtweetを使用してみました。おそらくtweetが一番自然な言葉の使い方をしているので、感情分析するには結構面白いと思います。

tweetのデータを取得するには以下のコードを書きました。

```ruby
require 'twitter'
require 'uri'

class TwitterContent

  def initialize()
    client = Twitter::REST::Client.new do |config|
      config.consumer_key        = "YOUR_CONSUMER_KEY"
      config.consumer_secret     = "YOUR_CONSUMER_SECRET"
      config.access_token        = "YOUR_ACCESS_TOKEN"
      config.access_token_secret = "YOUR_ACCESS_SECRET"
    end

  end

  def get_tweets(count)

    tweets = [@client.user_timeline(count: 1)][0]
    remaining = count
    roop_count = 200

    until remaining == 0 do
      @client.user_timeline(count: roop_count, max_id: tweets.last.id-1).each do |t|
        remaining -= 1
        if remaining == 0
          break
        end
        
        unless URI.extract(t.text).empty?
          next
        end
        tweets << t

      end

      if remaining < 200
        roop_count = remaining
      end
    end
    tweets.map! {|t| t.text}
  end

end

```

　ちなみに僕のtwitterはnewspickをシェアしてるものが多かったので、urlを含んでいるものは除外することにしました。
(`#get_tweets`はtweetをほしい数だけ取得するのに書いたメソッドですが、我ながらとても汚いコードになっています...　改良したい...)

#Google Natural Language API 
　このAPIの使い方は殆どドキュメントに記載されている[クイックスタート](https://cloud.google.com/natural-language/docs/quickstart-client-libraries?hl=ja#client-libraries-usage-ruby)と同様なので説明は省略します。データをjavascriptで扱いたかったので、Sinatraを使ってテンプレートに渡しています。

　感情分析APIの返り値としては、`score`と`magnitude`が返ってきて、定義は次の様になっています。

- score: 　　ドキュメントの全体的な感情　（-1.0~1.0:ネガティブ~ポジティブ）
- magnitude: ドキュメントに感情的な内容がどのくらい含まれているか

本来は分析対象のデータに合わせて、それぞれの値を解釈すると良いみたいです。今回はさっと概観したいだけなので、`score`のみを用います。


```ruby
require 'sinatra'
require 'sinatra/reloader' if development?
require 'json'

require './twitter_content'
require "google/cloud/language"

get '/' do

  t = TwitterContent.new()
  tweets = t.get_tweets(500)

  language = Google::Cloud::Language.new
  sentiment_scores = []
  tweets.each do |t|
    sentiment_scores << language.analyze_sentiment( content: t, type: :PLAIN_TEXT).document_sentiment.score
  end
  
  scores = sentiment_scores.map! {|s| s.round(1)}

  #下記
end

```
google apiの認証に関してはいくつか方法があるみたいですが、今回はサービスアカウントによる認証を行っています。詳しくは[こちら](https://cloud.google.com/docs/authentication/getting-started?hl=ja)。

#グラフ用のデータ前処理
　今回はjsのC3.jsを使用してグラフ描画を試みました。以下のコードはグラフ描画用に値を前処理しています。`x軸用のカテゴリ`　`カテゴリごとのtweet数`　`全体に対してのカテゴリごとの割合`をグラフ用に処理しているだけなので、もっとスマートがやり方もあると思います。

```ruby
get '/' do

  #上記

  # [-1.0..1.0]を0.1刻みで作成
  categorical = []
  -1.0.step(1.0,0.1).each do |f|
    categorical << f.round(1)
  end

  # カテゴリごとの数を集計
  count_hash = Hash.new(0)
  scores.each do |s|
    count_hash[s.to_s] += 1
  end

  # カテゴリの値それぞれに値を貼り付ける。なかったら0
  scores_hash = {}
  categorical.each do |c|
    scores_hash[c] = count_hash.has_key?(c.to_s) ? count_hash[c.to_s] : 0
  end

  # 全体に関するそれぞれの値の割合
  scores_ratio = []
  scores_sum = scores_hash.values.inject(:+)
  scores_hash.values.each do |v|
    scores_ratio << ((v.to_f / scores_sum.to_f)*100).round(1)
  end


  @scores = scores_hash.values
  @categorical = categorical
  @count = tweets.count
  @scores_ratio = scores_ratio

  erb :index

end
```

#C3.jsによるグラフ描画
　あとはview側でグラフを描画するだけです。今回は二種類描画してみました。

*※　C3.jsはD3のラッパーなので、D3.jsを入れる必要がありますが、D3.js v3が必要です。D3.js v4では動きません。*

##*スコアごとの数*

```html+erb
<!DOCTYPE html>
<html>
  <head>
    <link href="/css/c3.css" rel="stylesheet">
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script src="/js/c3.min.js"></script>
  </head>
  <body>
    <p>分析対象： <%= @count %>つのtweet</p>
    <div id="chart"></div>
    <div id="c2"></div>
    <script>
    window.onload = function(){
      const scores = <%= @scores %>
      const categorical = <%= @categorical %>
      const scores_ratio = <%= @scores_ratio%>

      const chart = c3.generate({
        data: {
             columns: [
                 ['scores'].concat(scores)
             ],
             type: 'bar'
         },
         axis: {
           x: {
               type: 'category',
               categories: categorical
           },
              y: {
                label: {
                  text: '個',
                  position: 'outer-top'
                }
              }
        }
      });
    }
    </script>
  </body>
</html>
```
<img width="1267" height="300"alt="スクリーンショット 2018-02-01 16.26.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/51501/74f87ded-c406-0431-9d4b-89dc40848c80.png">

##*全体に対するカテゴリごとの割合*
ちょっと絶対数だけ見えてもよく分からないので、その割合を見てみましょう。
以下のように変更することで割合が見やすくなります。

```html+erb
<!DOCTYPE html>
<html>
  <head>
    <link href="/css/c3.css" rel="stylesheet">
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script src="/js/c3.min.js"></script>
  </head>
  <body>
    <p>分析対象： <%= @count %>つのtweet</p>
    <div id="chart"></div>
    <div id="c2"></div>
    <script>
    window.onload = function(){
      const scores = <%= @scores %>
      const categorical = <%= @categorical %>
      const scores_ratio = <%= @scores_ratio%>

      const chart = c3.generate({
          data: {
              columns: [
                  ['scores_ratio'].concat(scores_ratio)
              ],
              types: {
                  scores_ratio: 'area'
              }
          },
          axis: {
            x: {
              type: 'category',
              categories: categorical
            },
            y: {
              label: {
                text: '%(パーセント)',
                position: 'outer-top'
              }

            }
          }
      });

    }
    </script>
  </body>
</html>
```
<img width="1274" height="300" alt="スクリーンショット 2018-02-01 16.28.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/51501/0ee25ebe-ce9a-9868-5a35-b88a50a6184d.png">


　今回は最新500件のtweetの内、urlを含まない168つのtweetが分析対象になりました。
　こう概観してみると、微妙にポジティブな方が多いかな？という感じですね。もちろんmagnitudeなど度外視しているので実際のところはなんとも言えませんが、大体でも可視化できると結構楽しいものです。皆さんもぜひやってみて下さい。

#やってみて
　なんとなく思いついたことでしたが、とても楽しかったです。けど、それにも増してコードの書けなさを実感しました。特にeachしかブロック扱えていないのが、素人感にじみ出ていますね...　データの処理もっと上手くなりたい。


#参考
[D3 Document](https://d3js.org/)
[C3 Document](http://c3js.org/)
[Google Natural Language API](https://cloud.google.com/natural-language/docs/basics?hl=ja)
[Twitter Gem](https://github.com/sferik/twitter)