Pythonによるk-meansクラスタリング(身長と体重のデータ)

身長(x)と体重(y)のデータに対して体格ごとにpythonを用いてクラスタリングを行った。
今回はコードを簡単にするため、クラスを用いず、必要な処理を関数にまとめた。

main関数の流れ
1. read_data関数でファイルを読み込む。
2. データ数やクラスタ数を決める。
3. 重心の位置の初期化(最初はすべてx, yともに0としておく)
4. clustering関数でクラスタを行う。クラスタしたデータと各クラスタの重心が返される。
5. 分類結果をファイルに書き込む。
6. 結果を表示

尚、実際のコードではmain関数は一番下に書いた。

```python
# main関数
def main():

    # 2次元データ読み込み
    filename = "height_weight.csv"
    data = read_data(filename)

    # データ数
    N = len(data)

    # クラスタ数
    number = 3
    # 重心
    # すべて0に初期化しておく
    # [(グループ番号), (xの重心), (yの重心)]
    u = [[1, 0.0, 0.0],[2, 0.0, 0.0], [3, 0.0, 0.0]]

    # クラスタリング
    cluster, u = clustering(data, u, number, N)

    # 2次元データ書き込み
    filename2 = "cluster.csv"
    write_data(filename2, cluster)

    # 結果表示
    print("Clustering")
    for sample in cluster:
        print(sample)

    # 重心表示
    print("Center")
    for sample in u:
        print(sample)

if __name__ == "__main__":
    main()
```
まずはファイルの読み取り。読み込んだデータはなぜかfloat型に変換するという作業が必要らしい。これをしないとエラーが出た。ここの原因や理屈はよく分からない。

```python
# coding:utf-8

import csv
import random

# 2次元データ読み込み
def read_data(filename):

    data = []

    with open(filename, "r") as f:
        reader = csv.reader(f)
        for row in reader:
            data.append(row)

    data = [[float(data[i][j]) for j in range(0, len(row))] for i in range(0, len(data))]

    return data
```
クラスタリング(グループ分け)したデータをファイルの書き込む。読み込み・書き込み共に、C/C++と比較して大分簡単である。

```python
# ファイル書き込み
def write_data(filename, cluster):

    with open(filename, "w") as f:
        writer = csv.writer(f)
        writer.writerows(cluster)

    print("File was written .")
```
メインの処理工程となるクラスタリング関数。最初は各データをランダムにグループ分けする。言い換えれば、ランダムにグループ番号を割り振る。
次に、各グループの重心をcenter関数で求め、その重心から再度belong関数によりグループ分けを行う。これを一定回数繰り返し、正確なグループ分けと各グループの重心を求める。

```python
# クラスタリングの関数
def clustering(data, u, number, N):

    # クラスタしたデータを格納する配列の初期化
    cluster = []

    for m in range(0, N):

        # 初期はランダムにクラスタリング
        # [(グループ番号), (データ番号), (xデータ), (yデータ)]
        cluster.append([random.randrange(1, number+1),m+1,  data[m][0], data[m][1]])

    # 1回目のランダムなクラスタリング結果を表示
    print("First cluster nuclear")
    for sample in cluster:
        print(sample)

    print("\nStart Clustering")

    # クラスタリング開始
    for count in range(0, 10000):

        # 重心計算
        u = center(u, number, N, cluster)

        # グループ分け
        cluster = belong(u, number, N, cluster)

    # クラスタリング後にクラスタ番号(グループ番号)ごとに並べる
    cluster.sort()

    print("Finish clustering .")

    # クラスタリング結果返す
    return cluster, u
```
重心を計算する関数

```python
# 重心計算
def center(u, number, N, cluster):

    # 各グループごとの重心を計算
    for n in range(0, number):

        x_center = 0.0
        y_center = 0.0
        sample_count = 0

        for m in range(0, N):

            if cluster[m][0] == n + 1:

                x_center = x_center + cluster[m][2]
                y_center = y_center + cluster[m][3]
                sample_count = sample_count + 1

        if sample_count != 0:

            x_center = x_center / sample_count
            y_center = y_center / sample_count

        u[n][0] = n + 1
        u[n][1] = x_center
        u[n][2] = y_center

    return u
```
グループ分けを行う関数。各データから一番近い重心のグループに割り振る。

```python
# グループ分け
def belong(u, number, N, cluster):

    # サンプル点がどの重心に一番近いかを計算
    # つまりどのグループに所属(belong)しているか
    for m in range(0, N):

        d_min = (cluster[m][2] - u[0][1])**2 + (cluster[m][3] - u[0][2])**2

        temp_number = 1
        for n in range(1, number):

            d = (cluster[m][2] - u[n][1])**2 + (cluster[m][3] - u[n][2])**2

            # 最小値更新
            # これによりグループが決まる
            if d < d_min:
                d_min = d
                temp_number = n + 1

        cluster[m][0] = temp_number

    return cluster
```

クラスタリングした結果。しっかりと分類できている。
![kmeans.png](https://qiita-image-store.s3.amazonaws.com/0/294396/e456678a-3352-3176-eddb-d260c1107b86.png)