Edited at

D3.js を使ってみる

More than 3 years have passed since last update.

まずはじめに、データ可視化は真の目的ではありません。手段です。

Vitaly Friedman の有名な言葉で、データ可視化の大目的は明瞭かつ効果的に情報とコミュニケーションができるように、データを視覚化できる能力そのものである (The main goal of data visualization is its ability to visualize data, communicating infomation clearly and effectivelty.) というものがあります。


  • 情報を視覚的に伝える

  • 明確に情報を伝える

  • 効果的に情報を伝える

こういったことがデータ可視化の要件かと思います。

とくに、何のための可視化なのかという大目的を見失ってはいけません。

この辺の話は以下の「データ可視化勉強会」のスライドがとても素晴らしいのであわせて参照すると良いでしょう。

http://www.slideshare.net/muddydixon/ss-31811179


D3.js

D3.js

公式 http://d3js.org/

日本語 http://ja.d3js.node.ws/

データに基づいて (主に) SVG Document Object を操作しデータの可視化をします。この D3.js で出来ることは単なるグラフユーティリティの枠に収まらず、 SVG 操作、数値計算、ビジュアライゼーションをできる汎用フレームワークと言っても良いレベルです。あまりに自由度が高すぎて機能が多く、ドメイン固有の用途であればラッパーとなるライブラリを被せたほうが時には良いくらいです。


可視化の必要性

高度な分析処理、たとえば分散分析やら重回帰分析やらを駆使しても、なかなか読み手に情報が伝わらないことはあります。レポートに数値や文字列の表をたくさん掲載するより、データビジュアライゼーションされた図形を見てもらう方が一目瞭然というケースは多々あります。

残念なことに、綿密に計算された詳細なデータより、派手でインパクトのある図形や動きを見たほうが人間の直感に訴える力ははるかに大きくなります。高いお金を払ってデータ分析者を雇用しているのにアウトプットがイマイチだなどという心無い言葉を投げかけられるのは不本意ですから、優れたデータ可視化をおこなって読み手に情報を明らかに効率良く伝える努力をしなければなりません。


D3.js (JavaScript) を使う利点

いままで RPython でのデータ可視化を紹介してきました。

では、あえて JavaScript (ウェブアプリケーション) で可視化する理由はなんでしょうか。ひとつにはインタラクションがあげられます。ウェブであればマウスやキーボード、タッチパネルによる操作 (インタラクション) を受け取り双方向にデータをやり取りすることができます。

顧客の要求があいまいであったり、あるいは探索的なデータ解析を求められる場面は多いでしょう。データ分析者は万能ではありませんし、これらの要求にこたえるために大量の静止画を用意してレポートを作成するのは現実的ではありませんから、データの絞り込みや異なる側面からの観測、全体の俯瞰などはデータを視る側に託したくなります。

インタラクションを備えた JavaScript や Flash のようなウェブアプリケーションで可視化されたデータビジュアライゼーションを提供できれば、こういったニーズにも応えることができます。

D3.js は Mike Bostock 氏によって開発が進んでいるいま主流のライブラリです。 SVG のような Web 標準のリソースのみを利用することで豊富な表現が可能となっており非常に注目されています。

pandas の公式サイトからたどれるオライリー本 Python for Data Analysis の 8.4 章 Python Visualization Tool Ecosystem においてもデータ可視化の未来について触れられています。ここには Web を基盤としたデータ可視化は避けられない未来であり、 pandas などのデータ分析・加工ツールとウェブブラウザをより密接に結合することが大きな課題であると述べられています。このようにウェブベースのデータ可視化は今後のトレンドとして避けては通れない道なのです。そしてこの道を切り開くいま最も有力ともいえる手段が D3.js なのです。


D3.js を使うには

さて前置きがだいぶ長くなりましたが D3.js を使うのは簡単です。


  1. D3.js をダウンロードして適切な場所に設置する

  2. script タグでロードする

これだけです。

ちなみに多くの記事ではホストされている .js を外部から直接読んでいますが筆者はあまりこの方法が好きではありません。理由は次の通りです。


  • (あまり無いでしょうが) 万一攻撃コードを挿入された場合に直接被害が発生する

  • 勝手に新しいバージョンに変わってしまうかもしれない

  • アプリケーションの可用性が外部のサイトに依存してしまう

直接は関係ないですが最近では JQuery の公式から latest 版を使うのをやめろというアナウンスが出たこともありました。いくら多くの記事がそうであるからといって、あまりよく考えずコピペでやり方を真似するようだとこのような事態を招いてしまうことがありますので気をつけましょう。


CSV を読み込む

D3.js で出来ることはあまりに多岐にわたるのですべてを説明するのは効率的ではありません。実務でよく必要となる例として、 CSV ファイルに出力されたデータを読み込んで、 Web で図示するというケースを考えてみます。

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<title>D3.js</title>
<script type="text/javascript" src="/d3/d3.min.js"></script>
</head>
<body>
<h1>D3.js</h1>
<div id="result"></div>
<script src="js/d3js.my.js"></script>
</body>
</html>

まずは元となる HTML を記述します。この div タグの部分に CSV から読み込んだデータを table として表示してみましょう。 CSV データは次のように単純なものを想定します。

value

200
100
150
200
...

CSV データを読み込むには d3.csv を使います。次の JavaScript コードは d3.csv でデータを読み込み table タグを生成しています。

d3.csv("./data.csv", function(error, list){

d3.select("#result")
.append("table")
.selectAll("tr")
.data(list)
.enter()
.append("tr")
.append("td")
.text(function(d){
return d["value"];
})
});

ブラウザからアクセスするとこのように表示されます。

2.png


棒グラフを描く

今度は同じデータを SVG の上に棒グラフとして描きます。まず HTML のほうに SVG タグを用意しておきます。

<svg width="500" height="300"></svg>

この SVG に対して操作をします。

// CSV の読み込み

d3.csv('data.csv', function(csvdata) {
var dataset = [];
for (var i = 0; i < csvdata.length; i++) {
dataset.push(csvdata[i]['value']);
};
make(dataset);
});

// SVG にバーを描く
function make(dataset) {
d3.select('svg').selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr({
x : function(d, i){ return i * 30; },
y : function(d){ return 300 - d; },
width : 15,
height : function(d){ return d; },
fill : '#6fbadd'
});
}

ひとまず棒グラフを SVG で描くことができました。

3.png


JSON の読み込みとバブルチャートの描画

D3.js で読み込むことができるのは CSV だけではありません。 JSON の読み込みは外部サイトからデータを取得してマッシュアップしたいようなときに便利です。

公式の例に沿って JSON を読み込みバブルチャートを描いてみます。まず元となる JSON データは次の通りです。

{

"name": "適当なデータ",
"children": [
{
"name": "20140713",
"children": [
{"name": "りんご", "size": 8258},
{"name": "ゴリラ", "size": 10001},
{"name": "ラッパ", "size": 8217},
{"name": "パイナップル", "size": 12555},
{"name": "Ruby", "size": 2324},
{"name": "Python", "size": 10993},
{
"name": "20140714",
"children": [
{"name": "バナナ", "size": 9354},
{"name": "梨", "size": 1233}
]
},
{"name": "いちご", "size": 335},
{"name": "ごりら", "size": 383},
{"name": "らっぱ", "size": 874},
{
"name": "20140715",
"children": [
{"name": "珈琲", "size": 3165},
{"name": "紅茶", "size": 2815},
{"name": "緑茶", "size": 3366}
]
},
{"name": "抹茶", "size": 17705},
{"name": "烏龍茶", "size": 1486},
{
"name": "20140716",
"children": [
{"name": "月", "size": 6367},
{"name": "火", "size": 1229},
{"name": "水", "size": 2059},
{"name": "木", "size": 2291}
]
},
{"name": "火星", "size": 5559},
{"name": "水星", "size": 19118},
{"name": "金星", "size": 6887},
{"name": "木星", "size": 6557},
{"name": "土星", "size": 22026}
]
}
]
}

このように日本語を含める場合は UTF-8 にしておく必要があります。

var diameter = 960,

format = d3.format(",d"),
color = d3.scale.category20c();

var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);

var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");

d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });

node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });

node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});

function classes(root) {
var classes = [];

function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}

recurse(null, root);
return {children: classes};
}

d3.select(self.frameElement).style("height", diameter + "px");

バブルチャートを描くことができました。

1.png


まとめ

ここまで説明した内容で出来ることは R や Python (matplotlib) と大差がありません。 D3.js が真価を発揮するのは前述したとおりインタラクションによるリアルタイムなデータのフォーカス、ピックアップといった操作性にあります。インタラクションの実装については次回以降に説明をしていきます。