🍕

クラスタリングを半自動的に繰り返しする処理を検証してみた

5 min read

クラスタリングは「ある集合を何らかの規則によって分類」する手法です。

https://bcblog.sios.jp/what-is-clustering-merit-demerit/

kmeans法などでは分割する数は指定出来ますが、実際にどのように分割されるかはデータの分布次第です。

計算量をなるべく減らす等の目的で、なるべく細かく分割したいと思い、単純に沢山の分割数を指定する以外に何か良い方法はないかと思い、while を使って繰り返し処理を使ってクラスタリングできないかと試したのでまとめます。

Google Colab はこちら

Open In Colab

なお、タイタニックのデータセットをOne-Hotエンコーディングしたものを使用しています。

データセットをクラスタリング用に加工

適当にカラムを選択して scikit-learn の OneHotEncoderで特徴量に使えるカラムを適当に増やします。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

url = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/raw/titanic.csv"
df = pd.read_csv(url)
df_enc = df[["pclass", "sex", "age", "sibsp", "parch", "fare", "embarked"]]
df_enc["age"] = df_enc["age"].fillna(df_enc["age"].median())
df_enc = pd.get_dummies(df_enc, columns=df_enc.select_dtypes(include=object).columns)
df_enc.head()

繰り返し処理を使ってクラスタリングを実行

まずはkmeans法を使って単純に3分割してみます。

from sklearn.cluster import KMeans

k=3
labels = KMeans(n_clusters=k, random_state=42).fit_predict(df_enc)
df_enc["label"] = labels
pd.DataFrame(df_enc.groupby("label").count()["pclass"]).T

するとカテゴリが 1 の項目が圧倒的に多く、かなり偏りがあります。

そこで、それぞれのカテゴリの最大数が20以下になるか、最小値が10以下になるまで、一番データの多いカテゴリに対してクラスタリングを繰り返す処理を実行します。

併せて、繰り返し処理ごとにカテゴリ数を更新する処理を行っています。

# 最大値が指定の値になるまで繰り返す
while (df_enc.groupby("label").count()["pclass"].max() > 20) or (df_enc.groupby("label").count()["pclass"].min() > 10):
    k=3
    # 最も多いラベルをさらに分割する
    labels = KMeans(n_clusters=k, random_state=42).fit_predict(df_enc[df_enc["label"]==df_enc.groupby("label").count()["pclass"].idxmax()])

    # 最も多いラベルを分割した結果を、label_tmp カラムにする
    df_enc_in = df_enc[df_enc["label"]==df_enc.groupby("label").count()["pclass"].idxmax()]
    df_enc_in["label_tmp"] = labels
    df_enc_ex = df_enc[df_enc["label"]!=df_enc.groupby("label").count()["pclass"].idxmax()]
    df_enc = pd.concat([df_enc_in, df_enc_ex])

    # ラベルの更新
    max_num = df_enc["label"].max()
    df_enc["label"][
                    (df_enc["label"].isin([df_enc.groupby("label").count()["pclass"].idxmax()])) & \
                    (df_enc["label_tmp"]>=1)
                    ] = \
                    df_enc["label_tmp"][
                        (df_enc["label"].isin([df_enc.groupby("label").count()["pclass"].idxmax()])) & \
                        (df_enc["label_tmp"]>=1)
                    ] + max_num

    df_enc = df_enc.drop("label_tmp", axis=1)

pd.DataFrame(df_enc.groupby("label").count()["pclass"]).T

93個に分割することができました。

1度にクラスタリングを実行

比較のため、1度に93個に分割してみます。

k=93
labels = KMeans(n_clusters=k, random_state=42).fit_predict(df_enc)
df_enc["label_atOnce"] = labels
pd.DataFrame(df_enc.groupby("label_atOnce").count()["pclass"]).T

繰り返し処理と1度に分割する処理を比較

3分割を繰り返し処理した場合

df_enc.groupby("label").count()["pclass"].describe()
内訳 数値
count 93.000000
mean 9.580645
std 5.461968
min 1.000000
25% 5.000000
50% 9.000000
75% 13.000000
max 20.000000

1度に93分割した場合

df_enc.groupby("label_atOnce").count()["pclass"].describe()
内訳 数値
count 93.000000
mean 9.580645
std 6.960014
min 1.000000
25% 4.000000
50% 8.000000
75% 14.000000
max 28.000000

手間はかかりますが、一度に93分割するよりも、3分割を繰り返し処理したほうが最大値が小さくなっています。

より分類が実態に合っているかどうか、という観点では微妙ですが、今回の場合は、計算量を減らすためにより最大値が小さくなるように分割したい場合は3分割を繰り返し処理したほうがよかったです。

もちろんデータの分布や、シード値にもよるのでご留意ください。

以上になります、最後までお読みいただきありがとうございました。