D3.js で文書の特徴となる語彙を可視化する

今回は[前回](http://qiita.com/ynakayama/items/234ad00ae520030217ab)の記事で青空文庫の作品から抽出した語彙を [D3.js](http://d3js.org/) を利用して可視化してみます。

完成形のデモアプリケーションは[こちら](http://d3js-data-clips.herokuapp.com/)から閲覧できます。
(うまく表示されない場合はブラウザをリロードしてみてください)

## テキストデータを可視化する

これまでテキストデータの扱いを中心に、[フィードの使い方](http://qiita.com/ynakayama/items/5a5166b3b7092656f77e)だとか、[大量の文書から興味関心のある話題をベイジアン分類で抽出する](http://qiita.com/ynakayama/items/de19da95cb56763d757d)方法、また[文書群から TF-IDF を指標として特徴となる語彙を抽出する](http://qiita.com/ynakayama/items/234ad00ae520030217ab)方法などを説明してきました。

[前回](http://qiita.com/ynakayama/items/234ad00ae520030217ab)の最後に述べたとおり、このように抽出した結果は単に文字列のようなデータとして見せるよりも、可視化ライブラリを使ったほうがよく伝わります。

### D3.js で表示するためのデータを作る

過去に [D3.js を利用したインタラクティブな可視化デモ](http://qiita.com/ynakayama/items/f661b493751370ee6568)を作りましたが、同じ要領でアプリケーションを実装し [Heroku](https://www.heroku.com/) で動かしてみます。

まずは語彙群をキーとし、その重みを数値で表します。

```py3
require 'json'
require 'codecs'

def write_json_data(dic):
    """ 結果を JSON に書き出す関数 """
    arr = [] # JSON 内に 2次元のベクトルを作るのでまずは配列を用意
    for k, v in dic.items():
        for w, s in v:
            # スコアを適当に調整しつつ配列に追加していく
            arr.append([w, str(round(s * 10000 + 100, 2))])

        # Python で日本語を含む辞書を JSON 化するときは
        # このように ensure_ascii を False にすると化けない
        hash = json.dumps({'values': arr},
                          sort_keys=True,
                          ensure_ascii=False,
                          indent=2,
                          separators=(',', ': '))
                          # セパレータも明示してキレイな JSON に

        # ファイルを出力するには codecs.open で
        f = codecs.open(os.path.join(output_dir, k),
                            "w", "utf-8")
        f.write(hash) # 書き出す
        f.close() # ちゃんと閉じる
```

生成される JSON は、先頭だけ表示するとこんな感じです

```json
{
  "values": [
    [
      "後ろ姿",
      "199.26"
    ],
    [
      "クセ",
      "299.26"
    ],
```

このように配列の中にキーと値の配列がある二次元配列になります。

### D3.js で可視化する

筆者は正直 JavaScript に強くないというかだいぶ苦手なので識者の皆様からの教示を仰ぎたいところです。ひとまず表示さえできれば良いという目標でゴリゴリと書いていきます。

```js
// ノードを追加する
var svg = d3.select("body")
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // JSON データのバインディング
  d3.json('../json/novel_name.json', function(error, data) {
    data.values.forEach(function(d) {
      d.word = String(d[0]); // キー
      d.score = d[1]; // 値
    });
  force
    .nodes(data.values)
    .start();

  var node = svg.selectAll("g.node")
    .data(data.values)
    .enter()
    .append("g")
    .attr("class", "node")
    .call(force.drag);
  // 円の大きさを値をもとに決定する
  // また値に応じて色も変化させる
  node.append("circle")
    .attr("r", function(d) { return d.score * .1; })
    .attr("opacity", .67)
    .attr("fill", function(d){
      if (d.score <= 300) {
        return "#449944"
      } else if (d.score > 300 && d.score <= 500) {
        return "#33AA33"
      } else if (d.score > 500 && d.score <= 750) {
        return "#22CC22"
      } else if (d.score > 750 && d.score <= 1000) {
        return "#11DD11"
      }
    });
  // 語彙、またその値を追加していく
  node.append("text")
    .text(function(d){ return d.word; })
    .attr('fill', '#fff')
    .attr('font-size', 24)
    .attr('dx', -16)
    .attr('dy', -5);
  node.append("text")
    .text(function(d){ return d.score; })
    .attr('fill', '#fff')
    .attr('dx', -25)
    .attr('dy', 15);
  // 演出
  force.on("tick", function() {
    node
    .attr('transform', function(d) {
      return 'translate('+ Math.max(20, Math.min(width-20, d.x)) + ','
        + '' + Math.max(20, Math.min(height-20, d.y)) + ')'; }); 
  });
})
```

## デモアプリケーションの完成

あとは Heroku に git push すれば完成です。

```bash
heroku create myapp
git push heroku master
heroku open
```

D3.js デモアプリケーション
http://d3js-data-clips.herokuapp.com/

## まとめ

今回は D3.js を使って得られた特徴を可視化し Heroku で動かしました。

この時点で文書を特徴づける単語の羅列と数値が得られていますから、他のデータソースと突き合わせたり、複数の文書間での関連性を調べたりといった風に応用していくこともできるかと思います。