Ruby
数学
分析
kmeans
クラスタリング

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

More than 3 years have passed since last update.


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


K-means法とは

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

http://tech.nitoyon.com/ja/blog/2009/04/09/kmeans-visualise/


コード

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次元のクラスタ分析もできます!

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