如何在Python中将500×500股票相关矩阵聚类为高相关子矩阵?
解决思路:层次聚类+自定义规模均衡切割
嘿,我完全理解你要解决的问题——把500只股票按相关性聚类成几个子组,既要保证每组内股票高度相关,又要让各组的规模尽可能均匀(最小化规模标准差)。你试过SciPy的聚类库但没找到合适的方案?别担心,这其实是层次聚类+自定义聚类切割的组合问题,SciPy完全能搞定,只是需要我们多做一步自定义调整。下面一步步来:
核心逻辑拆解
你的需求本质是两个目标的平衡:
- 组内相关性最大化 → 用层次聚类的ward方法(它的核心是最小化组内方差,对应到相关矩阵就是组内相关性更高)
- 组规模尽可能均匀 → 不能用默认的聚类切割方式,需要我们遍历可能的聚类数,找到规模标准差最小的分组
具体实现步骤
1. 把相关矩阵转换成聚类能用的距离矩阵
聚类算法通常基于距离(距离越小,相似度越高),而我们手上是相关矩阵(相关系数越高,相似度越高),所以需要做转换:
import numpy as np from scipy.cluster.hierarchy import linkage, fcluster from scipy.spatial.distance import squareform # 假设你的500×500相关矩阵已经存在,名为corr_matrix # 转换为距离矩阵:用sqrt(2*(1 - corr))是最适配ward聚类的方式 # (当相关系数为1时距离为0,相关系数为-1时距离为2,符合欧氏距离的性质) dist_matrix = np.sqrt(2 * (1 - corr_matrix)) # linkage函数需要压缩的一维距离矩阵(不是方阵),用squareform转换 condensed_dist = squareform(dist_matrix)
2. 生成层次聚类树
用ward方法生成聚类树,它会自动把最相似(距离最小)的股票先合并,逐步形成更大的组:
# 生成聚类树(Z是聚类树的结构信息) Z = linkage(condensed_dist, method='ward') # 可选:画树状图直观看看聚类结构 import matplotlib.pyplot as plt plt.figure(figsize=(15, 8)) plt.title('股票聚类树状图') plt.xlabel('股票索引') plt.ylabel('距离') dendrogram(Z) plt.show()
3. 自定义切割聚类树,找到规模最均匀的分组
这是最关键的一步!默认的fcluster函数要么按距离切割,要么按固定聚类数切割,但我们需要找到规模标准差最小的分组。这里我们可以遍历合理范围内的聚类数,计算每个聚类数下的组规模标准差,选最优的:
# 定义函数:输入聚类数,返回组规模标准差、组内平均相关性和对应的聚类标签 def evaluate_clustering(n_clusters, Z, corr_matrix): # 获取聚类标签 labels = fcluster(Z, n_clusters, criterion='maxclust') # 计算每组的规模 _, group_sizes = np.unique(labels, return_counts=True) # 计算规模标准差 size_std = np.std(group_sizes) # 计算组内平均相关性(排除对角线的1,因为自己和自己的相关系数无意义) avg_group_corrs = [] for label in np.unique(labels): # 提取该组的股票索引 indices = np.where(labels == label)[0] # 获取子相关矩阵并过滤对角线 sub_corr = corr_matrix[np.ix_(indices, indices)] mask = np.ones(sub_corr.shape, dtype=bool) np.fill_diagonal(mask, False) avg_corr = np.mean(sub_corr[mask]) avg_group_corrs.append(avg_corr) return size_std, np.mean(avg_group_corrs), labels # 遍历合理的聚类数:比如500的规模,试试4到6个组(因为500/5=100,是比较均匀的规模) best_size_std = float('inf') best_avg_corr = -1 best_labels = None best_n_clusters = None for n in range(4, 7): current_std, current_avg_corr, current_labels = evaluate_clustering(n, Z, corr_matrix) print(f"聚类数{n}:规模标准差={current_std:.2f},组内平均相关性={current_avg_corr:.4f}") # 优先选规模标准差最小的,如果标准差相同,选组内相关性更高的 if current_std < best_size_std or (current_std == best_size_std and current_avg_corr > best_avg_corr): best_size_std = current_std best_avg_corr = current_avg_corr best_labels = current_labels best_n_clusters = n print(f"\n最优聚类方案:{best_n_clusters}个组,规模标准差={best_size_std:.2f},组内平均相关性={best_avg_corr:.4f}")
4. 拆分原相关矩阵为子矩阵
拿到最优聚类标签后,就可以把原相关矩阵拆分成各个子矩阵了:
# 按聚类标签分组,提取子相关矩阵 sub_corr_matrices = [] unique_labels = np.unique(best_labels) for label in unique_labels: indices = np.where(best_labels == label)[0] sub_corr = corr_matrix[np.ix_(indices, indices)] sub_corr_matrices.append(sub_corr) print(f"子矩阵规模:{sub_corr.shape}")
关键细节提醒
- 距离转换的选择:用
sqrt(2*(1 - corr))是因为ward聚类基于欧氏距离,这个转换能让相关系数和距离完美对应,聚类结果更准确。如果你想试试其他转换,比如1 - corr也可以,但ward的效果会打折扣。 - 聚类方法的选择:ward方法是最适合你需求的,因为它的目标是最小化组内的平方和,对应到相关矩阵就是组内的相关性尽可能高。如果想尝试其他方法,可以把
method='ward'换成method='average'(组内平均距离最小),但ward通常效果更好。 - 规模与相关性的权衡:如果某个聚类数的规模标准差很小,但组内相关性稍低,你可以根据自己的需求调整优先级——比如在
evaluate_clustering函数里给两个指标加权重,找到平衡点。
内容的提问来源于stack exchange,提问作者antimornings
相关产品推荐
相关产品推荐

