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

按组计算每行与同组其他行的平均余弦距离

解决方案:分组计算每行与同组其他行的平均余弦距离

针对150万行的大数据量,直接计算两两余弦距离会导致内存溢出,因此我们采用向量化的数学推导方法,避免生成O(n²)的距离矩阵,大幅提升计算效率。

核心思路推导

余弦距离的定义为:1 - 余弦相似度,其中两个向量x_i和x_j的余弦相似度是(x_i · x_j) / (||x_i|| * ||x_j||)。

对于某组内的m个样本,样本i与其他m-1个样本的平均余弦距离可转化为:

平均余弦距离 = 1 - [ (单位向量u_i · (组内所有单位向量总和 - u_i)) / (m-1) ]

其中u_i = x_i / ||x_i||是样本i的单位向量,||x_i||是向量x_i的模长(L2范数)。

这种方法只需计算组内统计量,无需两两计算距离,时间复杂度为O(n*p)(n为样本数,p为特征数),适合大数据场景。

实现代码

1. 加载依赖包

library(dplyr)

2. 预处理:计算单位向量与模长

首先为每个样本计算value1-value10的模长,再生成单位向量的各分量:

# 提取数值列
value_cols <- grep("^value", names(df), value = TRUE)

# 计算每个样本的L2模长
df <- df %>%
  mutate(
    norm = sqrt(rowSums(across(all_of(value_cols))^2)),
    # 生成单位向量的各列(避免除以0,若模长为0则设为0)
    across(all_of(value_cols), ~ ifelse(norm == 0, 0, . / norm), .names = "u_{.col}")
  )

3. 分组计算统计量并推导平均余弦距离

按group1和group2分组,计算组内单位向量的总和、组内样本数,再推导每行的平均余弦距离:

df <- df %>%
  group_by(group1, group2) %>%
  mutate(
    # 组内样本数
    group_size = n(),
    # 计算组内每个单位向量列的总和
    across(starts_with("u_value"), ~ sum(.), .names = "sum_{.col}"),
    # 计算u_i与(组内单位向量总和 - u_i)的点积
    dot_product = rowSums(across(starts_with("u_value"), ~ . * (get(paste0("sum_", cur_column())) - .))),
    # 计算平均余弦距离,处理组内只有1个样本的情况
    dist = ifelse(group_size == 1, NA_real_, 1 - (dot_product / (group_size - 1)))
  ) %>%
  ungroup() %>%
  # 清理中间变量(可选,节省内存)
  select(-starts_with("u_"), -starts_with("sum_"), -norm, -dot_product, -group_size)

4. 验证结果(可选)

对于小样本测试数据,可手动计算某组的结果验证正确性:

# 取group1=1、group2=1的子组验证
test_group <- df %>% filter(group1 == 1, group2 == 1)
value_mat <- as.matrix(test_group[, value_cols])

# 手动计算两两余弦距离并求平均
cos_dist_mat <- 1 - as.matrix(proxy::dist(value_mat, method = "cosine"))
manual_avg <- apply(cos_dist_mat, 1, function(x) mean(x[x != 0]))

# 对比代码计算结果与手动结果
cbind(test_group$id, test_group$dist, manual_avg)

大数据量优化建议

  • 内存优化:若内存紧张,可使用data.table替代dplyr,其分组计算效率更高,内存占用更低:
    library(data.table)
    setDT(df)
    
    # 计算模长与单位向量
    df[, norm := sqrt(rowSums(.SD^2)), .SDcols = value_cols]
    df[, paste0("u_", value_cols) := lapply(.SD, function(x) ifelse(norm == 0, 0, x/norm)), .SDcols = value_cols]
    
    # 分组计算并推导距离
    df[, c("group_size", paste0("sum_", value_cols)) := .(
      .N,
      lapply(.SD, sum)
    ), by = .(group1, group2), .SDcols = paste0("u_", value_cols)]
    
    df[, dot_product := rowSums(mapply(function(u_col, sum_col) u_col * (get(sum_col) - u_col), 
                                       mget(paste0("u_", value_cols)), 
                                       mget(paste0("sum_", value_cols))))]
    df[, dist := fifelse(group_size == 1, NA_real_, 1 - (dot_product / (group_size - 1)))]
    
    # 清理中间变量
    df[, c(paste0("u_", value_cols), paste0("sum_", value_cols), "norm", "dot_product", "group_size") := NULL]
    
  • 并行计算:若分组较多,可结合parallel包进行并行分组计算,进一步提升速度。

内容的提问来源于stack exchange,提问作者ecl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:53:16