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

百万级DataFrame:替代For循环的R语言高效数据匹配统计方案

问题

需要从data_frame_1的每行提取用户ID和日期,统计data_frame_2中对应用户、日期早于该行日期的job_id数量。当前用For循环实现,但实际场景中data_frame_1有200万行、data_frame_2有800万行,循环耗时过长。尝试过连接操作但结果表过大,希望找到更快的向量化或优化连接方案。

原循环代码:

store_info <- numeric(nrow(data_frame_1))
start_time = Sys.time()
for (i in seq(1:dim(data_frame_1)[1]) ) {
  
  users_id = data_frame_1$user_id1[i]
  date = data_frame_1$date_1[i]
  
  store_info[i] = length(data_frame_2[data_frame_2$user_id2 == users_id &
                           data_frame_2$date_2 < date, "job_id"])
  
}
end_time = Sys.time()
(diff_time = end_time - start_time)

示例数据生成代码:

start_date <- as.Date("2021-01-01")
end_date <- as.Date("2022-12-31")
dates_pull = seq(start_date, end_date, by = "day")
random_dates <- sample(dates_pull, 80000, replace = TRUE)

# 生成随机用户ID
possible_chars <- c(letters, LETTERS, 0:9)
num_ids <- 800 # 生成的ID数量
id_length <- 8 # 每个ID的长度
random_ids <- replicate(num_ids, paste0(sample(possible_chars, 
                                id_length, replace = TRUE), collapse = ""))
sample_ids <- sample(random_ids, 80000, replace = TRUE)

data_frame_1 = data.frame(user_id1 = sample_ids, date_1 = random_dates)

# 生成随机任务ID
random_ids_j <- replicate(num_ids, paste0(sample(possible_chars, id_length, replace = TRUE), collapse = ""))
sample_ids_j <- sample(random_ids, 500000, replace = TRUE)

random_dates <- sample(dates_pull, 500000, replace = TRUE)
sample_ids <- sample(random_ids, 500000, replace = TRUE)

data_frame_2 = data.frame(user_id2 = sample_ids, date_2 = random_dates,
                          job_id = sample_ids_j)
解决方案

针对大规模数据,推荐用分组+滚动计数的向量化方法,避免循环和全量连接,以下是几种高效实现:

方法1:使用data.table(性能最优)

data.table的分组和滚动操作专为大数据量优化,速度远快于基础R循环:

library(data.table)

# 转换为data.table格式
setDT(data_frame_1)
setDT(data_frame_2)

# 按用户ID和日期排序,确保滚动计数逻辑正确
data_frame_2 <- data_frame_2[order(user_id2, date_2)]

# 为每个用户的记录生成累计计数
data_frame_2[, cum_count := seq_len(.N), by = user_id2]

# 匹配每个data_frame_1行对应的最大累计计数
result <- data_frame_1[data_frame_2, on = .(user_id1 = user_id2, date_1 > date_2), 
                       mult = "last", .(user_id1, date_1, cum_count)]

# 无符合条件记录时填充0
data_frame_1$store_info <- fifelse(is.na(result$cum_count), 0, result$cum_count)

方法2:使用dplyr窗口函数

代码更易读,适合熟悉tidyverse的场景:

library(dplyr)

# 预处理data_frame_2:按用户分组排序后生成累计计数
df2_processed <- data_frame_2 %>%
  arrange(user_id2, date_2) %>%
  group_by(user_id2) %>%
  mutate(cum_count = row_number()) %>%
  ungroup()

# 匹配并统计每个data_frame_1行的有效记录数
result <- data_frame_1 %>%
  left_join(df2_processed, by = c("user_id1" = "user_id2")) %>%
  filter(date_2 < date_1) %>%
  group_by(user_id1, date_1) %>%
  summarise(store_info = max(cum_count, na.rm = TRUE)) %>%
  ungroup()

# 填充无匹配记录的行
final_result <- data_frame_1 %>%
  left_join(result, by = c("user_id1", "date_1")) %>%
  mutate(store_info = ifelse(is.na(store_info), 0, store_info))

方法3:去重预处理(进一步优化)

如果data_frame_1存在大量重复的(user_id1, date_1)组合,先去重统计再合并,减少计算量:

library(data.table)

setDT(data_frame_1)
setDT(data_frame_2)

# 对data_frame_1去重
unique_df1 <- unique(data_frame_1[, .(user_id1, date_1)])

# 按方法1逻辑处理去重后的行
data_frame_2 <- data_frame_2[order(user_id2, date_2)]
data_frame_2[, cum_count := seq_len(.N), by = user_id2]

unique_result <- unique_df1[data_frame_2, on = .(user_id1 = user_id2, date_1 > date_2), 
                            mult = "last", .(user_id1, date_1, cum_count)]
unique_result[, store_info := fifelse(is.na(cum_count), 0, cum_count)]

# 合并回原data_frame_1
data_frame_1 <- data_frame_1[unique_result, on = .(user_id1, date_1), store_info := i.store_info]

性能说明

  • 避免全量连接:全量连接会生成冗余行导致内存溢出,上述方法通过滚动匹配+累计计数仅保留有效统计值。
  • 排序提升效率:对data_frame_2按用户和日期排序后,累计计数可线性生成,匹配操作的效率大幅提升。
  • data.table在超大数据量下的性能显著优于dplyr,优先推荐使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:57:06