总结:本文为和鲸python 机器学习原理与实践·闯关训练营资料整理而来,加入了自己的理解(by GPT4o)
原活动链接
在前一关我们学习了逻辑回归,学会如何训练模型、数据基础性分析、如何处理空值等操作,下面我们开始新的一关 KMeans
。
目录
- KMeans
- 基于 `KMeans` 的股票分类
- 引入依赖
- 加载数据
- 确定分类个数
- 查看分类结果
- 总结
- 闯关题
- STEP1:请根据要求完成题目
KMeans
KMeans
是我们最常用的基于欧式距离的聚类算法,其认为两个目标的距离越近,相似度越大。
KMeans
算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为 K
个簇,其目的是让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
基于 KMeans
的股票分类
以往的量化投资中对于股票的划分分类,通常取决于行业、市值、地域等等指标划分,而这些分类指标并不能很好的区分公司的好坏。而现在可以通过每日的交易行情实时划分分类,通过计算当日前一个月的分类从而确定该股票分类,更好的降低投资风险,提供风险对冲。该数据集有 2024-05-06
的全部上市公司股票交易行情信息,其中包含日期、开盘价、收盘价、最高价、最低价、成交量、成交额等特征信息,另外该模型使用的数据为真实数据,可以在实际操作中使用。
股市有风险,入市需谨慎!
引入依赖
import pandas as pd
import numpy as np
import matplotlib.pyplot as pltfrom sklearn.cluster import KMeans
from sklearn.metrics import accuracy_score, silhouette_score
加载数据
# 1. 加载数据stock = pd.read_csv('./data/stocks-2.csv', index_col='Unnamed: 0')
stock.head()
symbol | code | name | trade | pricechange | changepercent | buy | sell | settlement | open | high | low | volume | amount | ticktime | per | pb | mktcap | nmc | turnoverratio | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | sz000001 | 1 | 平安银行 | 10.89 | 0.10 | 0.927 | 10.88 | 10.89 | 10.79 | 10.96 | 11.11 | 10.84 | 178410057 | 1953817493 | 15:00:00 | 4.840 | 0.508 | 2.113304e+07 | 2.113264e+07 | 0.91938 |
1 | sz000002 | 2 | 万 科A | 7.46 | 0.05 | 0.675 | 7.45 | 7.46 | 7.41 | 7.63 | 7.88 | 7.44 | 524493788 | 3996921703 | 15:00:00 | 7.243 | 0.355 | 8.900309e+06 | 7.248834e+06 | 5.39773 |
2 | sz000004 | 4 | 国华网安 | 10.44 | 0.23 | 2.253 | 10.43 | 10.44 | 10.21 | 9.99 | 10.46 | 9.97 | 9885440 | 102059842 | 15:00:00 | -8.821 | 7.478 | 1.382050e+05 | 1.318448e+05 | 7.82769 |
3 | sz000006 | 6 | 深振业A | 3.87 | 0.00 | 0.000 | 3.87 | 3.88 | 3.87 | 3.96 | 4.01 | 3.86 | 23187186 | 91348765 | 15:00:00 | -6.509 | 0.752 | 5.224481e+05 | 5.224451e+05 | 1.71759 |
4 | sz000007 | 7 | *ST全新 | 4.09 | -0.15 | -3.538 | 4.09 | 4.10 | 4.24 | 4.26 | 4.26 | 4.04 | 2440550 | 10028589 | 15:00:00 | 39.403 | 11.212 | 1.416972e+05 | 1.263597e+05 | 0.78995 |
stock.info()
<class 'pandas.core.frame.DataFrame'>
Index: 5360 entries, 0 to 5359
Data columns (total 20 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 symbol 5360 non-null object 1 code 5360 non-null int64 2 name 5360 non-null object 3 trade 5360 non-null float644 pricechange 5360 non-null float645 changepercent 5360 non-null float646 buy 5360 non-null float647 sell 5360 non-null float648 settlement 5360 non-null float649 open 5360 non-null float6410 high 5360 non-null float6411 low 5360 non-null float6412 volume 5360 non-null int64 13 amount 5360 non-null int64 14 ticktime 5360 non-null object 15 per 5360 non-null float6416 pb 5360 non-null float6417 mktcap 5360 non-null float6418 nmc 5360 non-null float6419 turnoverratio 5360 non-null float64
dtypes: float64(14), int64(3), object(3)
memory usage: 879.4+ KB
# 2. 删除与分类数无关的特征列new_stock = stock.drop(['symbol', 'code', 'name', 'ticktime'], axis=1)
new_stock.head()
trade | pricechange | changepercent | buy | sell | settlement | open | high | low | volume | amount | per | pb | mktcap | nmc | turnoverratio | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10.89 | 0.10 | 0.927 | 10.88 | 10.89 | 10.79 | 10.96 | 11.11 | 10.84 | 178410057 | 1953817493 | 4.840 | 0.508 | 2.113304e+07 | 2.113264e+07 | 0.91938 |
1 | 7.46 | 0.05 | 0.675 | 7.45 | 7.46 | 7.41 | 7.63 | 7.88 | 7.44 | 524493788 | 3996921703 | 7.243 | 0.355 | 8.900309e+06 | 7.248834e+06 | 5.39773 |
2 | 10.44 | 0.23 | 2.253 | 10.43 | 10.44 | 10.21 | 9.99 | 10.46 | 9.97 | 9885440 | 102059842 | -8.821 | 7.478 | 1.382050e+05 | 1.318448e+05 | 7.82769 |
3 | 3.87 | 0.00 | 0.000 | 3.87 | 3.88 | 3.87 | 3.96 | 4.01 | 3.86 | 23187186 | 91348765 | -6.509 | 0.752 | 5.224481e+05 | 5.224451e+05 | 1.71759 |
4 | 4.09 | -0.15 | -3.538 | 4.09 | 4.10 | 4.24 | 4.26 | 4.26 | 4.04 | 2440550 | 10028589 | 39.403 | 11.212 | 1.416972e+05 | 1.263597e+05 | 0.78995 |
确定分类个数
# 3. 利用肘部法则确定分类数inertia = []
silhouette_scores = []
i_range = range(2, 11)
for i in i_range:kmeans = KMeans(n_clusters=i, random_state=10).fit(new_stock)inertia.append(kmeans.inertia_)silhouette_scores.append(silhouette_score(new_stock, kmeans.labels_))inertia, silhouette_scores
([4.1450149552461185e+20,2.189263003520667e+20,1.6730094412041477e+20,9.618885942140525e+19,6.943786093529641e+19,5.561627387942571e+19,4.014992267655058e+19,3.2416675726264095e+19,2.4597061039181627e+19],[0.8944521948807374,0.8260147612056037,0.7907694574915884,0.7490320699906337,0.6649888612149094,0.6339363805356698,0.6338265053972817,0.6300107391392652,0.6195255140687659])
这段代码使用肘部法则和轮廓分数(silhouette score)来确定数据集的最佳分类数(簇数)。下面是对代码的详细解析:
导入必要的库
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
初始化变量
inertia = []
silhouette_scores = []
i_range = range(2, 11)
inertia
:用来存储不同簇数下的簇内误差平方和(SSE)。silhouette_scores
:用来存储不同簇数下的轮廓分数。i_range
:簇数的范围,从2到10(包括2和10)。
迭代不同的簇数
for i in i_range: kmeans = KMeans(n_clusters=i, random_state=10).fit(new_stock) inertia.append(kmeans.inertia_) silhouette_scores.append(silhouette_score(new_stock, kmeans.labels_))
for i in i_range
:遍历簇数范围,从2到10。kmeans = KMeans(n_clusters=i, random_state=10).fit(new_stock)
:为每个簇数创建并训练一个KMeans模型。n_clusters=i
:设置当前簇数。random_state=10
:设置随机种子,以确保结果可复现。fit(new_stock)
:对数据集new_stock
进行聚类训练。
inertia.append(kmeans.inertia_)
:将当前簇数下的簇内误差平方和(SSE)添加到inertia
列表中。silhouette_scores.append(silhouette_score(new_stock, kmeans.labels_))
:计算当前簇数下的轮廓分数,并添加到silhouette_scores
列表中。
输出结果
inertia, silhouette_scores
- 这将输出不同簇数下的簇内误差平方和(SSE)和轮廓分数。
肘部法则
肘部法则(Elbow Method)通过绘制簇数与SSE的关系图来帮助确定最佳簇数。最佳簇数通常是在SSE曲线开始明显变平的位置,即肘部位置。
轮廓分数
轮廓分数(Silhouette Score)用于评估聚类的质量,其值在-1到1之间。值越高表示聚类效果越好。通过比较不同簇数下的轮廓分数,可以选择分数最高的簇数作为最佳簇数。
总结
这段代码的目的是通过计算不同簇数下的簇内误差平方和(SSE)和轮廓分数,帮助选择数据集的最佳分类数。结合肘部法则和轮廓分数可以更全面地评估聚类效果,从而确定最合适的簇数。
# 4. 确定分类数
plt.figure(figsize=(15,5))plt.subplot(1, 2, 1)
plt.plot(i_range, inertia, marker='o')plt.subplot(1, 2, 2)
plt.plot(i_range, silhouette_scores, marker='o')plt.tight_layout()
plt.show()# 左图在 2 到 5 的时候,曲线下降速率明显下降。
# 右图在 2,3,4,5 时,轮廓系数比较高。
# 结合两图,选择 3 作为聚类数。
# 5. 分类kmeans_final = KMeans(n_clusters=3, random_state=10).fit(new_stock)labels = kmeans_final.labels_
new_stock['cluster'] = labels
new_stock.head()
trade | pricechange | changepercent | buy | sell | settlement | open | high | low | volume | amount | per | pb | mktcap | nmc | turnoverratio | cluster | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10.89 | 0.10 | 0.927 | 10.88 | 10.89 | 10.79 | 10.96 | 11.11 | 10.84 | 178410057 | 1953817493 | 4.840 | 0.508 | 2.113304e+07 | 2.113264e+07 | 0.91938 | 2 |
1 | 7.46 | 0.05 | 0.675 | 7.45 | 7.46 | 7.41 | 7.63 | 7.88 | 7.44 | 524493788 | 3996921703 | 7.243 | 0.355 | 8.900309e+06 | 7.248834e+06 | 5.39773 | 1 |
2 | 10.44 | 0.23 | 2.253 | 10.43 | 10.44 | 10.21 | 9.99 | 10.46 | 9.97 | 9885440 | 102059842 | -8.821 | 7.478 | 1.382050e+05 | 1.318448e+05 | 7.82769 | 0 |
3 | 3.87 | 0.00 | 0.000 | 3.87 | 3.88 | 3.87 | 3.96 | 4.01 | 3.86 | 23187186 | 91348765 | -6.509 | 0.752 | 5.224481e+05 | 5.224451e+05 | 1.71759 | 0 |
4 | 4.09 | -0.15 | -3.538 | 4.09 | 4.10 | 4.24 | 4.26 | 4.26 | 4.04 | 2440550 | 10028589 | 39.403 | 11.212 | 1.416972e+05 | 1.263597e+05 | 0.78995 | 0 |
查看分类结果
# 6. 查看分类情况new_stock['cluster'].value_counts()
cluster
0 4998
2 332
1 30
Name: count, dtype: int64
总结
KMeans
在确定分类个数计算时,无法使用 object
类型的数据,应当提前删除或对特征进行 one-hot
处理。
闯关题
STEP1:请根据要求完成题目
Q1. KMeans 中某个参数的含义是正确的?
A. n_clusters 分类个数
B. inertia_ 轮廓系数
C. silhouette_scores 曲线下降速率
Q2. 修改KMeans的划分集群个数为 4个,那么 002829 股票的分类是哪个?
A. 0
B. 1
C. 2
D. 3
kmeans_final2 = KMeans(n_clusters=4, random_state=10).fit(new_stock)labels = kmeans_final2.labels_
stock['cluster'] = labels
stock[stock['symbol'] == 'sz002829']['cluster']
1304 0
Name: cluster, dtype: int32
Q3. 前300个股票数据集划分集群的最优个数是多少?
A. 1
B. 3
C. 5
D. 10
new_stock = new_stock[0:300]inertia = []
silhouette_scores = []
i_range = range(2, 11)
for i in i_range:# 计算分类并保存指标kmeans = KMeans(n_clusters=i, random_state=10).fit(new_stock)inertia.append(kmeans.inertia_)silhouette_scores.append(silhouette_score(new_stock, kmeans.labels_))
inertia, silhouette_scores
([2.5308780913486823e+19,1.3473879858220839e+19,7.413489715471633e+18,6.109726555261718e+18,3.463054550988757e+18,2.604280833562603e+18,2.0732638975060705e+18,1.6982759851707302e+18,1.5100566906400458e+18],[0.8998406279029784,0.7527373456851054,0.692685627034619,0.6892926502877917,0.6522651603158817,0.6047949381607308,0.5696962854320331,0.5676513528559564,0.5655907482205398])
plt.figure(figsize=(15,5))plt.subplot(1, 2, 1)
plt.plot(i_range, inertia, marker='o')plt.subplot(1, 2, 2)
plt.plot(i_range, silhouette_scores, marker='o')plt.tight_layout()
plt.show()
#填入你的答案并运行,注意大小写
a1 = 'A' # 如 a1= 'A'
a2 = 'A' # 如 a2= 'A'
a3 = 'B' # 如 a3= 'A'