You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中将500×500股票相关矩阵聚类为高相关子矩阵?

解决思路:层次聚类+自定义规模均衡切割

嘿,我完全理解你要解决的问题——把500只股票按相关性聚类成几个子组,既要保证每组内股票高度相关,又要让各组的规模尽可能均匀(最小化规模标准差)。你试过SciPy的聚类库但没找到合适的方案?别担心,这其实是层次聚类+自定义聚类切割的组合问题,SciPy完全能搞定,只是需要我们多做一步自定义调整。下面一步步来:

核心逻辑拆解

你的需求本质是两个目标的平衡:

  1. 组内相关性最大化 → 用层次聚类的ward方法(它的核心是最小化组内方差,对应到相关矩阵就是组内相关性更高)
  2. 组规模尽可能均匀 → 不能用默认的聚类切割方式,需要我们遍历可能的聚类数,找到规模标准差最小的分组

具体实现步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:42:32