R语言DataFrame聚合填充效率优化:替代低效三重循环方案
哇,三重循环处理要4天确实太折磨了!咱们用aggregate配合数据重塑的方法,能把效率提升几个数量级,直接把处理时间压缩到分钟甚至秒级。下面是具体的实现步骤:
第一步:先聚合用户-视频的总观看时长
首先用aggregate按Customer.ID和Video.ID.v26分组,对Time.Spent.Watching求和,把重复的用户-视频记录合并成一条总时长记录:
# 聚合数据,得到每个用户每个视频的总观看时长 aggregated_time <- aggregate( Time.Spent.Watching ~ Customer.ID + Video.ID.v26, data = d2_cleaned, FUN = sum )
这一步会把原来可能几十万条的记录压缩成“用户数×用户观看过的视频数”量级,直接减少后续处理的数据量。
第二步:把聚合数据转成宽格式(匹配目标表结构)
接下来需要把长格式的聚合数据转成和distinct_customers_after_cleaning一致的宽格式——每行一个用户,每列一个视频ID,值为对应总时长。这里用tidyr的pivot_wider或者reshape2的dcast都可以,我推荐tidyr(属于tidyverse生态,代码更直观):
library(tidyr) # 转宽格式,未观看的视频自动填充0 wide_time <- pivot_wider( aggregated_time, id_cols = Customer.ID, # 按用户ID分组 names_from = Video.ID.v26, # 把视频ID转成列名 values_from = Time.Spent.Watching, # 填充总时长 values_fill = 0 # 没有观看记录的视频填0 )
如果习惯用reshape2,代码是这样的:
library(reshape2) wide_time <- dcast( aggregated_time, Customer.ID ~ Video.ID.v26, value.var = "Time.Spent.Watching", fill = 0 )
第三步:合并到目标表并填充数据
最后把宽格式的聚合数据和distinct_customers_after_cleaning合并,替换掉原来的0值。这里用dplyr的left_join配合coalesce来确保保留目标表的所有行和列:
library(dplyr) # 合并数据,保留目标表的全部结构 final_df <- distinct_customers_after_cleaning %>% left_join(wide_time, by = "Customer.ID") %>% # 遍历所有视频列,用聚合后的时长替换原表的0(如果有匹配的话) mutate( across( names(.)[-c(1,2)], # 排除前两列(用户ID和Hits列),处理所有视频列 ~ coalesce(.x, .y), .y = cur_data_all()[cur_column()] ) )
为什么原来的三重循环这么慢?
原来的三重循环是**O(n×m×k)**的时间复杂度:n是d2_cleaned的行数,m是用户数,k是视频列数——每一条观看记录都要遍历所有用户和所有视频列做判断,数据量大的时候完全不可行。
而我们的方法是O(n + m×k):先聚合把n条记录压缩成几百/几千条,再转宽格式,最后合并填充,相当于把最耗时的循环提前用R内置的向量运算完成(这些函数都是底层优化过的,比手写循环快N倍)。
内容的提问来源于stack exchange,提问作者Alex S

