1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rで描いたデンドログラムをd3.jsに変換

Posted at

はじめに

クラスター分析を行った際に表示させるデンドログラム、クラスタリングの構造が一目で見れて非常に便利である反面、RやPython等で出力した結果は基本画像ファイルになるため下記の点で使いにくい点がある。

  • 変数が大量にある場合、サイズ調整などを行ってもラベルが潰れてしまう。
  • 画像ファイルであることから当然ラベルの検索ができない。

デンドログラムを画像ではないフォーマットに変換する必要があり、表現の自由度などを考えるとd3.jsが適切と考える。既存の記事を探したものの、以下の点で望みのものが見つからなかった。

  • d3.jsのバージョンが古い(v3の記事がいくつか見つかる)。
  • デンドログラムの高さの情報が反映されない(ここがデンドログラムの本質でもある)。

そこで、同様に悩んでいる人もいるだろうということで、変換するスクリプトの作成にトライしてみた。

できたこと

  • Rで描いたデンドログラムをd3.jsに変換した。

Rで作成したデンドログラム
dendrogram_on_R.png

d3.jsに変換したデンドログラム
dendrogram_on_d3js.png

やったこと

基本的な戦略としては、

  1. デンドログラムを作成する。
  2. デンドログラムをJSONデータに変換する。
  3. JSONデータをHTMLテンプレートに流し込む。

となる。ここで足りない機能は「デンドログラムをJSONに変換する」機能と「d3.jsでデンドログラムを表示させるHTMLテンプレート」となるので、この2つを開発した。

Rでデンドログラムを作成

まずはお馴染みのアヤメデータを使って階層クラスタリングを行う。適度なサイズ感にするため、150あるデータのうち上から40行のみを抽出してクラスタリングする (setosaだけになってしまうが、今回はクラスタリングの内容は興味ないので放置)。

library(tidyverse)
library(dendextend)

data(iris)
ddg <- iris[1:40,1:4] %>%
  dist() %>%
  hclust() %>%
  as.dendrogram()

plot(ddg)

ここまでで作成されたデンドログラムが上記の「Rで作成したデンドログラム」になる。

デンドログラムをJSONに変換

以下の記事を参照してデンドログラムをJSON形式に変換する関数を定義する。
d3 dendrograms with R

as.json.dendrogram <- function(d){
  # internal helper function
  add_json <- function(x){
    v <- attributes(x)
    lab <- ifelse(is.null(v$label), "", v$label)
    json <<- paste(json,sprintf('{ "name" : "%s", "h" : %s',lab,v$height))
    if ( is.leaf(x) ){
      json <<- paste(json, "}\n")
    } else {  
      json <<- paste(json, ',\n "children" : [' )
      for ( i in seq_along(x) ){ 
        add_json(x[[i]])
        s <- ifelse(i<length(x), ",", "")
        json <<- paste(json, s)
      }
      json <<- paste(json, " ]}")
    }
  }
  
  json <- ""
  add_json(d)
  json
}

HTMLテンプレート

sprintfを用いてテンプレート文字列を生成する。基本的にはHTML文字列の「data = 」部分に先ほどの関数を用いて生成したJSONを埋め込む構成になっている。ここで書いているd3.jsのコードは解説が必要なものであると思われるが、とりあえず今回は結果を生成できるということを記事の主旨とするため省略する。

convertD3js <- function(json){
    sprintf('
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>Dendrogram with d3.js</title>
      <script src="https://d3js.org/d3.v7.min.js"></script>
      <style>
        .link {
          fill:none;
          stroke:#555;
          stroke-opacity: 0.4;
          stroke-width: 1.5px;
        }
      </style>
    </head>
    <body>
      <div id="dendrogram"></div>
      <script>
        var data = %s;
    
        let width = 1000;
        let height =1200;
        let margin = 100;
        let heightMax = data.h;
        let heightMin = 0;
        let scale = d3.scaleLinear()
          .domain([heightMin, heightMax])
          .range([width - margin, 0]);
          
        let root = d3.hierarchy(data);
        let cluster = d3.cluster()
          .size([height - margin, width - margin]);
        cluster(root)
    
        let svg = d3.select("#dendrogram")
          .append("svg")
          .attr("width", width)
          .attr("height", height);
    
        svg.append("g")
          .attr("transform", "translate(" + margin / 2 + "," + margin / 2 + ")")
          .call(d3.axisTop(scale));
          
        let g = svg.append("g")
          .attr("transform", "translate(" + margin / 2 + "," + margin / 2 + ")");
          
        let link = g.selectAll(".link")
          .data(root.descendants().slice(1))
          .enter()
          .append("path")
          .attr("class", "link")
          .attr("d", function(d){
            return "M" + scale(d.data.h) + "," + d.x +
              "L" + scale(d.parent.data.h) + "," + d.x +
              "L" + scale(d.parent.data.h) + "," + d.parent.x;
          });
    
        let node = g.selectAll(".node")
          .data(root.descendants())
          .enter()
          .append("g")
          .attr("transform", function(d){
            return "translate(" + scale(d.data.h) + "," + d.x + ")";
          });
    
        node.append("text")
          .attr("dy", 5)
          .attr("x", 0)
          .style("text-anchor", function(d){
            return d.children ? "end" : "start";
          })
          .attr("font-size", "15px")
          .text(function(d){
            return d.data.name;
          });
    
      </script>
    </body>
    </html>', json)
}

HTMLを生成

ここまでの結果を統合する。出力はhtmlファイルとなる。svgのサイズ、フォントサイズなどはハードコーディングになっているので、出力を見て適宜調整してほしい。

html <- ddg %>%
    as.json.dendrogram()
    convertD3js()

writeLines(html, "hoge.html")

まとめ

Rで出力したデンドログラムをd3.jsで表示するスクリプトを開発した。向きが横向きになるなど課題はあるものの、基礎技術はこれでカバーできていると考える。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?