gihyo.jpの「具体例で学ぶ!情報可視化のテクニック」のプログラムを勝手にRubyで書き換えてみた。

gihyo.jpで情報可視化の特集をやってて、すごく勉強になってよかったんですけど、
プログラムがJavaで書かれてて、個人的にRubyでやりたかったんで、
勉強ついでにプログラムをRubyで書き換えてみました。
そのRubyのプログラムはGithubにおいてあるので、ご自由にお使いください。
以下のリンクのdownloadからzip、またはtarで固めたものがダウンロードできます。
GitHub - ombran/gihyojp-visualization-ruby: The Ruby version of the information visualization introduced by gihyo.jp.(unofficial)


とまあ、さすがにこれで終わるのはひどいので、プログラムの簡単な説明をします。
今回はvisualization2のフォルダにあるプログラムの説明をします。
ただしここでRubyはインストール済みであるものとします。
Rubyのインストールとかの記事はいろんなところにあるんでそっちを参考にしてください。
Linuxとかなら元々インストール済みだったりしますし。

visualization2の説明

元記事だと第2回にあたる内容になります。
このプログラムは簡単に言えば、色集合を最短距離法に基づいて階層的クラスタリングを行うものです。
詳しいことは元記事を見てもらったほうがわかりやすいと思います。

visualization2フォルダ内容

visualization2のフォルダ内容は以下のようになっています。

$ tree visualization2
visualization2
-- Demo.rb
-- Visualization
-- Cluster.rb
-- ClusterBuilder.rb
-- DistanceEvaluator.rb
-- Item.rb
-- MultiVector.rb
-- NearestDistanceEvaluator.rb
`-- Node.rb
`-- Visualization.rb

拡張子の違いはありますが、元記事のプログラムのファイル名と内容を対応させてあります。
Visualization.rbを読み込むことだけで、Visualizationフォルダ以下のファイルを全て読み込めるようにしてあります。

元記事のプログラムとの違い

プログラムの内容自体はさほど違いはないんですけど、
元プログラムと大きく違うところとしてMultiVectorクラスの引数がハッシュになっています。

# MultiVector.rb
module Visualization
  class MultiVector
    # ベクトル生成(引数はハッシュ)
    def initialize(hash={})
      @data = {}
      hash.each do |key, value|
        @data[key] = value
      end
    end

...
end

これはベクトルの次元数が違うもの同士でもクラスタリングできるようにするためです。
ちなみに今回はベクトルの次元数が全て同じなのでこの利点が実感できませんが、
visualization6ではこれによって元記事のプログラムで用いられているクラスを一つ削減できるようになっています。
まあとりあえずハッシュのほうが便利だよ、とでも思っておいてください。


その他のクラスでは言語の違いはありますが内容にほとんど変化はありません。
ただし、全てのクラスはVisualizationモジュール内に含まれるようにしてあります。
Javaでのパッケージの代わりだと考えてください。

デモプログラムの実行

それではプログラムのデモを行うDemoクラスは以下のようになります。

# Demo.rb
require File.dirname(__FILE__) + '/Visualization'

class Demo
  include Visualization

  def run
    # 入力データ作成
    input = []
    color = Struct.new(:red, :green, :blue)
    input << Item.new("BLUE",    colorToVector(color.new(0,   0,   255)))
    input << Item.new("CYAN",    colorToVector(color.new(0,   255, 255)))
    input << Item.new("MAGENTA", colorToVector(color.new(255, 0,   255)))
    input << Item.new("ORANGE",  colorToVector(color.new(255, 200, 0)))
    input << Item.new("PINK",    colorToVector(color.new(255, 175, 175)))
    input << Item.new("RED",     colorToVector(color.new(255, 0,   0)))
    
    # 最短距離法に基づく階層的クラスタリングを準備
    evaluator = NearestDistanceEvaluator.new
    builder = ClusterBuilder.new(evaluator)
    
    # クラスタリングを実行
    result = builder.build(input)
    
    # クラスタリング結果を表示
    output(result, 0)
  end
  
  def colorToVector(c)
    # 色成分を3次元のベクトルに変換
    MultiVector.new({:red => c.red, :green => c.green, :blue => c.blue})
  end

  def output(node, depth)
    # インデントを表示
    depth.times do
      print "    "
    end
    if (node.kind_of? Item)
      # 末端ノードなら項目名を表示
      puts node.getName
    elsif (node.kind_of? Cluster)
      # クラスタなら"+"を表示し、子ノードを再帰的に表示
      puts "+"
      cluster = node
      output(cluster.getLeft,  depth + 1)
      output(cluster.getRight, depth + 1)
    end
  end
end

demo = Demo.new
demo.run

入力データの色情報はRGBの値をそのまま入力してあります。
Demoクラスを実行すると,以下の出力が得られます。

$ ruby Demo.rb 
+
    +
        RED
        +
            MAGENTA
            +
                ORANGE
                PINK
    +
        BLUE
        CYAN

結果は元記事と同じになってるんで、プログラムは合ってると思うんですが、
もし何か間違い等ありましたら教えていただけるとありがたいです。

以上

今回はここまでです。
ほんとに簡単な説明で申し訳ないですけど、
元記事のプログラムと対応させて見てもらえるとわかりやすいかなと思います。
他のプログラムに関しては次回以降に説明します。