はじめに
最終回となる今回は、
この可視化では、
ソースコードのダウンロード
今回作成するプログラムのソースコードは、
特徴量ベクトルの生成
前回のプログラムでは、
このとき問題となるのは、
そこで、
public class IndexMapper {
    private Map<Object, Integer> map = new HashMap<Object, Integer>();
    public int size() { return map.size(); }
    public boolean contains(Object obj) {
        // インデックスが割り振り済みであるかどうかを調べる
        return map.containsKey(obj);
    }
    public void put(Object obj) {
        if (!contains(obj)) {
            // 新しいオブジェクトの場合、現在のサイズを新規インデックスとして割り振る
            int newIndex = size();
            map.put(obj, newIndex);
        }
    }
    public int get(Object obj) {
        // インデックスが割り振られていない場合は例外を発生
        if (!contains(obj)) {
            throw new IllegalArgumentException("Object is not found.");
        }
        // インデックスを返す
        return map.get(obj);
    }
}IndexMapperクラスを使って、
 
ブックマークノードクラスの作成
タグのベクトル化が可能となったところで、
public class BookmarkItem extends Item {
    ...
    public BookmarkItem(Bookmark bookmark, BookmarkDetail detail,
            IndexMapper mapper) {
        // ブックマーク数をそのまま面積化すると比率が極端になるので
        // 平方根をとって調整する
        super(bookmark.title, tagsToVector(detail, mapper),
                Math.sqrt(detail.bookmarkCount));
        this.bookmark = bookmark;
        this.detail = detail;
    }
    private static MultiVector tagsToVector(BookmarkDetail detail,
            IndexMapper mapper) {
        MultiVector vector = new MultiVector(mapper.size());
        for (String tag : detail.tags) {
            if (mapper.contains(tag)) {
                int index = mapper.get(tag);
                vector.set(index, vector.get(index) + 1);
            }
        }
        vector.normalize(); // ベクトルを正規化する
        return vector;
    }
}MultiVectorオブジェクトを作成した後、
「実用系ブックマーク」と「議論系ブックマーク」の色分け   
はてなブックマークは、
ツリーマップ上で、
 
コメント率を求めるため、
public class BookmarkDetail {
    public int bookmarkCount;
    public List tags;
    public List comments;  // コメントの一覧 
} public class HatenaBookmarkAPI {
    public BookmarkDetail getDetail(String url) {
        ...
        detail.comments = new ArrayList(); 
        ...
        for (int i = 0; i < bookmarks.size(); i++) {
            JSONObject item = bookmarks.getJSONObject(i);
            JSONArray tags = item.getJSONArray("tags");
            for (int j = 0; j < tags.size(); j++) {
                detail.tags.add(tags.getString(j));
            }
            // 以下、追加分
            String comment = item.getString("comment");
            if (comment != null && comment.length() > 0) {
                detail.comments.add(comment);
            }
        }
        ...
    }
}さらに、
public class BinaryTreeMapRenderer implements TreeMapRenderer {
    ...
    private void doRender(Graphics2D g, Node node, Rectangle2D bounds, int depth) {
        if (node instanceof BookmarkItem) {
            BookmarkItem item = (BookmarkItem) node;
            g.setPaint(bookmarkToColor(item));
            g.fill(bounds);
        }
        ...
    }
    private Color bookmarkToColor(BookmarkItem item) {
        BookmarkDetail detail = item.getDetail();
        // コメント率を計算
        float commentRate = (float) detail.comments.size()
                / (float) detail.bookmarkCount;
        // 補正をかける
        commentRate = Math.min(2f * commentRate, 1f);
        // コメント率からRGB要素を決定
        float red = 0.2f + 0.4f * commentRate;
        float green = 0.2f + 0.4f * (1f - commentRate);
        float blue = 0.2f;
        return new Color(red, green, blue);
    }
    ...
}デモプログラムの作成
それでは、
少々長くなりますが、
public class Demo {
    // 出力ファイル名
    private static final String OUTPUT_FILE_NAME =
            "C:/visualization/hatena_bookmark.png";
    public static void main(String[] args) {
        new Demo().run();
    }
    public void run() {
        HatenaBookmarkAPI api = new HatenaBookmarkAPI();
        List<Bookmark> bookmarks = api.getHotEntries();
        System.out.println(bookmarks.size() + " entries.");
        Map<Bookmark, BookmarkDetail> details =
                new HashMap<Bookmark, BookmarkDetail>();
        IndexMapper mapper = new IndexMapper();
        for (Bookmark bookmark : bookmarks) {
            System.out.println(bookmark.title);
            System.out.println("  [url] " + bookmark.url);
            try {
                // サーバの負荷を抑えるため呼び出し間隔を空ける
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            BookmarkDetail detail = api.getDetail(bookmark.url);
            if (detail != null) {
                System.out.println("  [bookmarkCount] " + detail.bookmarkCount);
                System.out.println("  [tags] " + detail.tags);
            }
            // タグを整数インデックスにマッピングする
            for (String tag : detail.tags) {
                mapper.put(tag);
            }
            details.put(bookmark, detail);
        }
        // 階層的クラスタリングの入力データを作成
        List<Item> input = new ArrayList<Item>();
        for (Bookmark bookmark : bookmarks) {
            BookmarkDetail detail = details.get(bookmark);
            input.add(new BookmarkItem(bookmark, detail, mapper));
        }
        // Ward法に基づく階層的クラスタリングを準備
        DistanceEvaluator evaluator = new WardDistanceEvaluator();
        ClusterBuilder builder = new ClusterBuilder(evaluator);
        // クラスタリングを実行
        Node result = builder.build(input);
        // クラスタリング結果をツリーマップに出力
        output(result);
    }
    private void output(Node node) {
        // 出力用画像を作成
        BufferedImage image = new BufferedImage(1024, 768,
                BufferedImage.TYPE_INT_RGB);
        // グラフィックオブジェクトを作成
        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        // 背景を白で塗りつぶす
        g.setPaint(Color.WHITE);
        g.fillRect(0, 0, image.getWidth(), image.getHeight());
        // ツリーマップの描画を実行
        TreeMapRenderer renderer = new BinaryTreeMapRenderer();
        Rectangle2D bounds = new Rectangle2D.Double(20, 20,
                image.getWidth() - 40, image.getHeight() - 40);
        renderer.render(g, node, bounds);
        // 画像をPNGファイルに保存
        try {
            ImageIO.write(image, "png", new File(OUTPUT_FILE_NAME));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}可視化の実行と結果の分析
それではいよいよ、
 
この可視化イメージから、
 
- イメージの左下部分には、「地球温暖化」 「ノーベル物理学賞」 「神舟7号」 と、 環境・ サイエンス系統の話題が集まっています。 
- イメージの右の部分には、いわゆる 「ネタ系統」 のエントリーが集まっています。この付近には赤っぽい領域が多いことから、 エントリーへのコメント率が高く、 「議論系ブックマーク」 の傾向が強いことが分かります。 
- 「ネタ系統」の領域は、 さらに 「2ちゃんねる系統」 と 「新聞系統」 に大別されている様子が分かります。 
- 「ネタ系統」は、 1つ1つの末端領域の面積が比較的小さいことから、 1エントリー当たりのブックマーク数が少ない傾向が分かります。 
いかがでしょうか。こういったブックマークの特徴は、
現実の集合知データである、
最後に
これまで6回にわたり、
本連載では多く触れるに至らなかった
