3章 その13 blogクラスタのビジュアライズ

p.42~44より、blogクラスタの図を描く。
Python Imaging Libraryの代わりに、RMagick http://d.hatena.ne.jp/shokai/20081112/1226502119 を使う。使い方も関数名もほぼ同じなので簡単。

あと配列内の一番大きい要素を返すArray.max関数を使った。


http://www.bitbucket.org/shokai/collective-intelligence-study/src/f1912ad66261/03/clusters.rb に関数追加

require 'bicluster.rb'
require 'rubygems'
require 'pp'
require 'RMagick'
include Magick

class Clusters

  # グラフを描く
  def drawdendrogram(clust, labels, imgfile='clusters.png')
    # 高さと幅
    h = getheight(clust) * 20
    w = 1200
    depth = getdepth(clust)
    
    # 幅は固定されているため、適宜縮尺する
    scaling = Float(w-150)/depth
    
    # 白を背景とする新しい画像を作る
    img = Image.new(w,h)
    draw = Draw.new
    draw.stroke('red')
    draw.stroke_width(1)
    draw.line(0, h/2, 10, h/2)
    
    # 最初のノードを描く
    drawnode(draw, clust, 10, (h/2), scaling, labels)
    
    # 描画、保存
    draw.draw(img)
    img.write(imgfile)
    
  end

  def drawnode(draw, clust, x, y, scaling, labels)
    if clust.id < 0
      h1 = getheight(clust.left) * 20
      h2 = getheight(clust.right) * 20
      top = y-(h1+h2)/2
      bottom = y+(h1+h2)/2
      # 直線の長さ
      ll = clust.distance*scaling
      # クラスタから子への垂直な直線
      draw.stroke('red')
      draw.line(x, top+h1/2, x, bottom-h2/2)
      
      # 左側のアイテムへの水平な直線
      draw.line(x, top+h1/2, x+ll, top+h1/2)
      
      # 右側のアイテムへの水平な直線
      draw.line(x, bottom-h2/2, x+ll, bottom-h2/2)
      
      # 左右のノードたちを描く関数を呼び出す
      drawnode(draw, clust.left, x+ll, top+h1/2, scaling, labels)
      drawnode(draw, clust.right, x+ll, bottom-h2/2, scaling, labels)
    else
      # 終点であればアイテムのラベルを描く
      draw.font = '/Library/Fonts/Arial.ttf'
      draw.stroke('transparent')
      draw.fill('black')
      draw.pointsize = 10 # 文字サイズ
      draw.text(x+3, y+4, labels[clust.id])
    end
  end
  
  def getdepth(clust)
    # 終端への距離は0.0
    return 0 if clust.left == nil && clust.right == nil
    
    # 枝の距離は二つの方向の大きい方にそれ自身の距離を足したもの
    return [getdepth(clust.left),getdepth(clust.right)].max + clust.distance
  end
  
  def getheight(clust)
    # 終端であれば高さは1にする
    return 1 if clust.left == nil && clust.right == nil
    
    #そうでなければ高さはそれぞれの枝の高さの合計
    return getheight(clust.left) + getheight(clust.right)
  end


irbで(出力は一部省略)

>> blognames,words,data = cs.readline('myblogdata.txt')
>> clust = cs.hcluster(data)
>> cs.printclust(clust, blognames)
>> cs.drawdendrogram(clust,blognames,'myblogclust.png')
=> myblogclust.png  1200x1940 DirectClass 16-bit 184kb

myblogclust.pngとして保存された。

myblogclust


作者サイトで提供されている単語頻出表 http://kiwitobes.com/clusters/blogdata.txt を使うと

>> blognames,words,data = cs.readline('blogdata.txt')
>> clust = cs.hcluster(data)
>> cs.drawdendrogram(clust,blognames,'blogclust.png')
=> blogclust.png  1200x1980 DirectClass 16-bit 183kb

ちょっと本に掲載されている形と違う。どこかアルゴリズム間違えたかも…
blogclust