K-Meansで株式銘柄のクラスタリング
K-meansとは?
K-meansは、特徴に基づいてデータをk個のグループに分類する教師なし分類(クラスタリング)アルゴリズムです。
今回は、株式銘柄のリターンとボラティリティを計算し、K-meansによるクラスタリングを行ってみたいと思います。
分析データの準備
今回はJPXプライム150指数の構成銘柄を対象に分析を実施したいと思います。構成銘柄の一覧は下記より確認できます。
構成銘柄である150個の証券コードをリスト化します。
stocks = [
6758, 6861, 9432, 8035, 6501, 4502, 4568, 7974, 7741, 4063, 8031, 9433, 6098, 6367, 8001, 8766,
4661, 6981, 6273, 9434, 6954, 3382, 4503, 2914, 7751, 6503, 5108, 6702, 6902, 6594, 8002, 4901,
6301, 9983, 6723, 4911, 9020, 6857, 7733, 2802, 4543, 4452, 2502, 4578, 4519, 8113, 4523, 6326,
7011, 6920, 8267, 6146, 1925, 9735, 2503, 7832, 7309, 1928, 6762, 7269, 9202, 6869, 6645, 9101,
4507, 3659, 9021, 9843, 4684, 6988, 6506, 4307, 9201, 2267, 8697, 9613, 2413, 9042, 6965, 7272,
9104, 6963, 9697, 2801, 7701, 7532, 9005, 4528, 4612, 1878, 6586, 3088, 6479, 4324, 3626, 6532,
2897, 9143, 2587, 7951, 9602, 3038, 9684, 4151, 9766, 5019, 4091, 4021, 3064, 7735, 9107, 1911,
9435, 4751, 6460, 3769, 8111, 6526, 3092, 3288, 6028, 6967, 7211, 2127, 8136, 3697, 2371, 3774,
3132, 8056, 7518, 2175, 8174, 8876, 6966, 9449, 9759, 9744, 4587, 3765, 6055, 2412, 5384, 8919,
4686, 4812, 4928, 4974, 7071, 5423
]
次に各銘柄の年間の終値を取得します。
取得にはpandas_datareaderというライブラリを使用しています。データソースはYahoo Financeになります。
ライブラリの詳細は下記より確認できます。
分析には調整後終値を使用したかったので、Adj Closeの値を使用します。
import pandas_datareader.data as web
import yfinance as yf
yf.pdr_override()
prices = []
for stock in stocks:
try:
closes = web.DataReader(str(stock) + ".T", start='2023-01-01', end='2023-12-31')["Adj Close"]
closes_df = pd.DataFrame(closes)
closes_df.columns = [stock]
prices.append(closes_df)
except:
pass
prices_df = pd.concat(prices, axis=1)
prices_df.sort_index(inplace=True)
最後に各銘柄の終値を結合して1つのDataFrameにします。
下記のようなDataFrameになります。

それでは各銘柄のリターンとボラティリティを計算します。
from math import sqrt
returns = pd.DataFrame()
returns['returns'] = prices_df.pct_change().mean() * 246
returns['volatility'] = prices_df.pct_change().std() * sqrt(246)
適切なクラスター数を求める
K-meansでクラスタリングを行う際に、適切なクラスタ数を決めるテクニックとしてエルボー法というものがあります。
クラスタの数を変えながらSSE(Sum of Squared errors、またはDistorsion)を計算し、SSEの減少が大きく改善しなくなる曲がり角のようなポイントを探します。
import numpy as np
from sklearn.cluster import KMeans
X = np.asarray([np.asarray(returns['returns']),np.asarray(returns['volatility'])]).T
distorsions = []
for k in range(2, 10):
k_means = KMeans(n_clusters=k)
k_means.fit(X)
distorsions.append(k_means.inertia_)
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=list(range(2, 10)), y=distorsions, mode='lines+markers'))
fig.update_layout(title='エルボー法', xaxis_title='クラスタ数', yaxis_title='クラスタ内誤差平方和', template='plotly_white')
fig.show()

今回はクラスタ数を5としました。
K-meansでクラスタリング
クラスタ数が決まったので、クラスタリングを行います。
from scipy.cluster.vq import kmeans, vq
centroids,_ = kmeans(data, 5)
idx,_ = vq(data, centroids)
details = [(name, cluster) for name, cluster in zip(returns.index, idx)]
details_df = pd.DataFrame(details)
details_df.columns = ['証券コード', 'クラスタ']
clusters_df = returns.reset_index()
clusters_df['クラスタ'] = details_df['クラスタ']
clusters_df.columns = ['証券コード', 'リターン', 'ボラティリティー', 'クラスタ']
計算した平均の年間リターンと年間ボラティリティーの特徴に基づいてK-meansアルゴリズムがクラスタリングを行います。
import plotly.express as px
fig = px.scatter(clusters_df, x="リターン", y="ボラティリティー", color="クラスタ", hover_data=["証券コード"])
fig.show()
最後にクラスタリングした結果をプロットします。
