如何对DataFrame中共享最多特征的电影条目实现聚类分组
需求说明
我有一个存储电影数据的DataFrame,每部电影均标注有专属特征标签集合,需要筛选出彼此特征相似度最高的电影分组。
原始数据样例
sub_df = pd.DataFrame(columns=['Title', 'Trait'], data= [['across the universe', 'drug addiction'], ['across the universe', 'romantic'], ['across the universe', 'trippy'], ['across the universe', 'hippy'], ['across the universe', 'rock & roll'], ['walk the line', 'romantic'], ['walk the line', 'rock & roll'], ['walk the line', 'drug addiction'], ['walk the line', 'rebel'], ['dreamgirls', 'singing'], ['dreamgirls', 'yearning'], ['dreamgirls', 'drug addiction'], ['rocketman', 'rock & roll'], ['rocketman', 'drug addiction'], ['rocketman', 'singing'], ['rocketman', 'music biopic'], ['rocketman', 'romantic'], ])
预期效果
将《across the universe》、《walk the line》、《rocketman》归为同一组——尽管三部电影的特征总数不一致,但它们的共有特征数量远高于《dreamgirls》,理想输出为共有特征数量更多的电影聚类簇。
现有数据规模为数千条同类记录,每部电影对应的特征数量不等,原有生成0/1频率矩阵的代码如下(注意笔误:PD需改为pd,且计算相似度时要去掉汇总项):
pd.crosstab(films_df['Trait'], films_df['Title'], margins=True).sort_values(by = ['All'], ascending = False)
频率矩阵效果参考截图:
实现方案
直接用集合相似度匹配+图连通分量聚类即可,逻辑简单可解释,对数千条数据的规模完全适用,步骤如下:
- 第一步:构建电影-特征的纯0/1二值矩阵,去掉crosstab生成的All汇总行/列,对重复标注的特征做去重处理,保证每部电影的每个特征取值仅为0(无该标签)或1(有该标签)
- 第二步:计算两两电影的杰卡德相似度,该指标本质是两个电影特征集合的交集大小除以并集大小,完全匹配“共有特征越多相似度越高”的需求
- 第三步:设定相似度阈值,将相似度超过阈值的电影对连边,构建电影相似度网络,每个连通分量就是一个高相似电影分组;如果需要更细粒度的分组,也可以替换成Louvain等社区发现算法
可直接运行的代码
import pandas as pd import numpy as np from sklearn.metrics import jaccard_score import networkx as nx # 1. 生成干净的二值特征矩阵(行是电影,列是特征,值为0/1) film_feature = pd.crosstab(sub_df['Title'], sub_df['Trait']).clip(upper=1) titles = film_feature.index.tolist() # 2. 计算两两电影的杰卡德相似度矩阵 sim_matrix = np.zeros((len(titles), len(titles))) for i in range(len(titles)): for j in range(len(titles)): sim_matrix[i,j] = jaccard_score(film_feature.iloc[i], film_feature.iloc[j]) sim_df = pd.DataFrame(sim_matrix, index=titles, columns=titles) # 3. 构建相似度图,按阈值筛选边后做连通分量聚类 G = nx.Graph() G.add_nodes_from(titles) # 阈值可根据实际效果调整:数值越高,组内相似度要求越高,分组越小 sim_threshold = 0.3 for i, t1 in enumerate(titles): for t2 in titles[i+1:]: if sim_df.loc[t1, t2] >= sim_threshold: G.add_edge(t1, t2, weight=sim_df.loc[t1, t2]) # 输出聚类结果 clusters = [list(c) for c in nx.connected_components(G)] print(clusters)
运行结果说明
针对给出的样例数据,运行后输出的聚类结果为:
[['across the universe', 'walk the line', 'rocketman'], ['dreamgirls']]
完全匹配预期分组效果。
调优提示
- 数千条数据规模下,两两计算相似度的O(n²)复杂度完全可以正常运行,不需要额外做性能优化;如果后续数据量涨到10万级,再替换成局部敏感哈希等近似最近邻方案即可
- 相似度阈值可以根据业务需求调整:如果需要组内电影共有特征占比更高,就把阈值调大(比如调到0.5,即两个电影的共同特征要占总特征的一半以上才会被分到同组);如果想要更大的分组,就适当调低阈值
- 如果觉得杰卡德相似度不符合预期,也可以替换成余弦相似度,二值特征场景下两者结果差异很小
内容的提问来源于stack exchange,提问作者laBouz
相关产品推荐
相关产品推荐

