LoginSignup
14
11

More than 5 years have passed since last update.

RubyでK-means法でクラスタ分析してみた

Last updated at Posted at 2015-12-17

RubyでK-means法でクラスタ分析してみた

K-means法とは

下記のサイトが分かりやすくアルゴリズムを解説しています。

コード

require 'set'

# クラスタをランダムに割り振る
def random_clusterize data, cluster_num
  data_num = data.size
  clusters = []
  cluster_num.times do |i|
    _start = (i*data_num/cluster_num).to_i
    _end = (i!=cluster_num-1) ? ((i+1)*data_num/cluster_num).to_i : data_num
    clusters[i] = data[_start..._end]
  end

  clusters
end

# クラスタの中心座標を取得
def get_center_point cluster
  result = [0.0, 0.0]
  cluster_size = cluster.size.to_f
  cluster.each do |point|
    result[0] += point[0]
    result[1] += point[1]
  end
  result[0] /= cluster_size
  result[1] /= cluster_size

  result
end

# 点の距離を取得
def distance point1, point2
  (point1[0]-point2[0])**2 + (point1[1]-point2[1])**2
end

# dataをcluster_numにクラスタライズ
def clusterize data, cluster_num
  center_points = []

  # ランダムに分割
  data.shuffle!
  clusters = random_clusterize data, cluster_num

  # 仮中心を作る
  clusters.each_with_index do |cluster,i|
    center_points[i] = get_center_point cluster
  end

  # 終わるまでループ
  while(true) do
    tmp_clusters = []
    cluster_num.times do |cluster_index|
      tmp_clusters[cluster_index] = []
    end
    data.each do |datum|
      tmp_min_distances = Float::INFINITY
      minimum_index = 0
      center_points.each_with_index do |center_point, center_point_index|
        # 重心との距離を計算
        dist = distance(center_point, datum)
        if distance(center_point, datum) < tmp_min_distances
          tmp_min_distances = dist
          minimum_index = center_point_index
        end
      end
      # 近い重心を判断
      tmp_clusters[minimum_index] << datum
    end

    # 仮中心を決定
    cluster_num.times do |index|
      center_points[index] = get_center_point tmp_clusters[index]
    end

    # クラスタの決定
    if tmp_clusters.to_set == clusters.to_set
      break
    end

    clusters = tmp_clusters
  end

  # クラスタを返却
  clusters
end

使い方

require 'csv'

# さっきのコード...

# 3つのクラスタに分類
cluster_num = 3

# 元データを読み込む
data = CSV.read("test.csv").map do |datum|
  [datum[0].to_f, datum[1].to_f]
end

# クラスタリング
clusters = clusterize data, cluster_num

# クラスタ後のデータごとにcsvとして吐き出す
cluster_num.times do |i|
  CSV.open("myfile#{i}.csv", "w") do |csv|
    clusters[i].each do |row|
      csv << row
    end
  end
end

動かしてみた

元データを以下のようにある程度クラスタが見える程度に配置して作ってみました。

元データ群


これをCSVデータとして、さっきのコードに読み込ませてクラスタ毎に色分けして出力しました。

クラスタリング後のデータ群


ちゃんとクラスタに分かれてることが分かります!!

まとめ

今回は2次元にしましたが、ちょっと変えれば、n次元のクラスタ分析もできます!

何か、大きいデータを分類しなきゃいけないなどがあれば、是非、お試しください。

14
11
1

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
14
11