Rubyで『集合知プログラミング』(4)
第3章「グループを見つけ出す」
3.4 デンドログラムを描く
"clusters.rb"の続きです。
描画ライブラリにはcairoを使用します。
cairoのインストールは
gem i cairoで行いました。
クラスタの高さを計算します。
ここで言う「高さ」は,文字通り画像のy軸方向の高さになります。
def get_height(clust) # 終端であれば1,枝であれば子クラスタの高さの合計 if clust.leaf? then 1 else get_height(clust.left) + get_height(clust.right) end end
クラスタの深さを計算します。
ここでは,子クラスタ間の距離の累積値になるんでしょうか?
def get_depth(clust) # 終端であれば0,枝であれば子クラスタの深さの最大値+子クラスタ間の距離 if clust.leaf? then 0 else [get_depth(clust.left), get_depth(clust.right)].max + clust.distance end end
デンドログラムを描画します。
画像のサイズなど,書籍とは異なります。
def draw_dendrogram(clust, labels, png='clusters.png') h = get_height(clust) * 20 w = 1400 depth = get_depth(clust) # 深さに応じて縮尺 この300は最右のラベルを表示するための余白 scaling = ((w-300)/depth.to_f) # 白を背景とする画像を作成 surface = Cairo::ImageSurface.new(w, h) context = Cairo::Context.new(surface) context.set_source_rgb(1,1,1) context.rectangle(0, 0, w, h) context.fill # 描画色,フォントなど context.set_source_rgb(0,0,0) context.select_font_face('serif') context.set_font_size(12) # ルートクラスタの横線 context.move_to(0, h/2) context.line_to(10, h/2) context.stroke # ルートクラスタから描画 draw_node(context, clust, 10, h/2, scaling, labels) # pngで保存 surface.write_to_png(png) end
クラスタを描画します。
top,bottomの意味が書籍と異なります。
def draw_node(context, clust, x, y, scaling, labels) unless clust.leaf? # 子クラスタの高さ h_l = get_height(clust.left) * 20 h_r = get_height(clust.right) * 20 # ここで書籍では # top = y - (h1 + h2) / 2 # bottom = y + (h1 + h2) / 2 # とし,子クラスタまで含めた上下端をtop,bottomとしているが, # 無駄な計算のような気がしたので,以下のようにした # 縦線の上端と下端 # leftのクラスタは上,rightのクラスタは下に描画する # 各クラスタの高さの逆比で描画位置を設定する # そうしないと,画像の高さ内にうまく収まらない top = y - h_r / 2 bottom = y + h_l / 2 # 水平線の長さ 長いほどクラスタ同士は似ていない horiz_len = clust.distance * scaling # [ 型に線を描画 context.move_to(x+horiz_len, top) context.line_to(x, top) context.line_to(x, bottom) context.line_to(x+horiz_len, bottom) context.stroke # 子クラスタの描画 draw_node(context, clust.left, x+horiz_len, top, scaling, labels) draw_node(context, clust.right, x+horiz_len, bottom, scaling, labels) else # 終端であればアイテムのラベルを描画 context.move_to(x+5, y+5) context.show_text(labels[clust.id]) end end
実行します。
前回同様,'blogdata.txt'は,ダウンロードした本家サンプルにあったものを使いました。
blognames, words, data = read_file('blogdata.txt')
clust = hcluster(data)
draw_dendrogram clust, blognames以下,生成されたデンドログラムです。