如何用Python优化相似产品分组:兼顾相似度与组规模
产品分组优化方案(基于Jaccard相似度)
适用算法推荐
针对你的需求(最大化组内/整体平均相似度+避免组规模极端化),推荐以下两类算法:
- 带约束的社区检测算法:把产品看作图节点,Jaccard相似度看作边的权重,用Louvain算法(支持加权图)进行社区划分,可通过调整模块度权重或后期剪枝/合并来控制组规模。优点是全局优化性好,适合大规模数据。
- 贪心分组算法:从相似度最高的产品对入手,逐步吸纳与当前组平均相似度最高的产品,同时设置组规模上下限。优点是实现简单、可解释性强,适合快速迭代调整约束条件。
Python实现示例
贪心分组算法(带规模约束)
假设你的输入DataFrame包含product1、product2、jaccard_similarity三列,以下是可直接运行的代码:
import pandas as pd import numpy as np from collections import defaultdict # 加载数据(替换为你的数据源) df = pd.read_csv('product_similarity.csv') # 构建对称相似度字典,方便快速查询任意产品对的相似度 similarity_dict = defaultdict(dict) for _, row in df.iterrows(): p1, p2, sim = row['product1'], row['product2'], row['jaccard_similarity'] similarity_dict[p1][p2] = sim similarity_dict[p2][p1] = sim def greedy_grouping(similarity_dict, min_group_size=3, max_group_size=10, min_avg_sim_threshold=0.3): ungrouped = set(similarity_dict.keys()) groups = [] while ungrouped: # 选择未分组产品中,与其他未分组产品总相似度最高的作为组核心 core_product = max(ungrouped, key=lambda x: sum(similarity_dict[x].get(p, 0) for p in ungrouped)) current_group = [core_product] ungrouped.remove(core_product) # 逐步吸纳符合条件的产品 while len(current_group) < max_group_size and ungrouped: # 计算每个未分组产品与当前组的平均相似度 avg_sims = {} for p in ungrouped: sims = [similarity_dict[p].get(member, 0) for member in current_group] avg_sims[p] = np.mean(sims) # 取平均相似度最高的产品 next_product = max(avg_sims, key=avg_sims.get) # 只有当平均相似度超过阈值时才加入 if avg_sims[next_product] >= min_avg_sim_threshold: current_group.append(next_product) ungrouped.remove(next_product) else: break # 处理规模过小的组:合并到最相似的现有组 if len(current_group) < min_group_size and groups: group_similarities = [] for g in groups: cross_sims = [] for p1 in current_group: for p2 in g: cross_sims.append(similarity_dict[p1].get(p2, 0)) group_similarities.append(np.mean(cross_sims)) best_group_idx = np.argmax(group_similarities) groups[best_group_idx].extend(current_group) else: groups.append(current_group) return groups # 执行分组(可根据业务调整参数) groups = greedy_grouping(similarity_dict, min_group_size=4, max_group_size=8, min_avg_sim_threshold=0.25) # 计算分组效果指标 def calculate_group_avg_sim(group, sim_dict): sims = [] group_size = len(group) for i in range(group_size): for j in range(i+1, group_size): sim = sim_dict[group[i]].get(group[j], 0) sims.append(sim) return np.mean(sims) if sims else 0 # 输出结果 group_avg_sims = [calculate_group_avg_sim(g, similarity_dict) for g in groups] overall_avg_sim = np.mean(group_avg_sims) group_sizes = [len(g) for g in groups] size_variance_coeff = np.std(group_sizes) / np.mean(group_sizes) print(f"整体平均相似度: {overall_avg_sim:.4f}") print(f"组规模变异系数: {size_variance_coeff:.4f}") for idx, (group, avg_sim, size) in enumerate(zip(groups, group_avg_sims, group_sizes), 1): print(f"组{idx} | 规模: {size} | 平均相似度: {avg_sim:.4f} | 产品: {group}")
Louvain社区检测(带规模调整)
如果用Louvain算法,可使用python-louvain库,后期通过合并小规模组、拆分超大规模组来满足约束:
import community as community_louvain import networkx as nx # 构建加权图 G = nx.Graph() for _, row in df.iterrows(): G.add_edge(row['product1'], row['product2'], weight=row['jaccard_similarity']) # 执行社区检测 partition = community_louvain.best_partition(G, weight='weight') # 将分区转换为组列表 groups = defaultdict(list) for product, group_id in partition.items(): groups[group_id].append(product) groups = list(groups.values()) # 后期调整组规模(可复用前面贪心算法中的规模调整逻辑)
更优评价指标建议
除了平均相似度,建议补充以下指标全面评估分组效果:
- 组内平均Jaccard相似度:核心指标,直接反映组内产品的相似性
- 整体加权平均相似度:用组规模作为权重计算整体平均,比简单均值更贴合实际业务价值
- 组规模变异系数:(组规模标准差 / 组规模均值),数值越小说明组规模越均衡
- 组间平均相似度:计算不同组之间产品的平均相似度,数值越低说明组间差异越明显,分组效果越好
- 最小/最大组规模比:越接近1,说明规模越避免极端化
内容的提问来源于stack exchange,提问作者Liam
相关产品推荐
相关产品推荐

