避免dplyr子集再合并,求高效生成连续年份计数变量的R方法
高效计算ID连续出现年份数的解决方案
问题背景
给定数据集:
df <- data.frame(year = c("2000", "2000", "2000", "2002", "2007", "2001", "2002", "2004", "2007"), id = c("X", "X", "X", "X", "X", "Z", "Z", "Z", "Z"), product = c("apple","orange", "melon", "orange", "orange", "orange", "cake", "bacon", "truffels"), market = c("CHN", "USA", "USA", "CAN", "USA", "USA", "CHL", "CHL", "ECU"), value = c(1, 2, 3, 4, 5, 6, 7, 8, 9))
需要创建变量years_id_consecutive,统计每个id的连续出现年份数,连续定义为当前年份t的前2年内该id有出现记录。
原有的dplyr方法需拆分合并数据集,处理大数据时效率极低,现提供基于R Base和data.table的高效一体化方案。原尝试代码如下:
df_panel <- df %>% mutate(year = as.numeric(year)) %>% group_by(id, year) %>% summarise(value=head(value,1)) %>% # 保留每个id-year组合的第一行 ungroup() %>% complete(nesting(id), year = full_seq(year, period = 1)) # 创建平衡面板 df_panel <- df_panel %>% group_by(id) %>% mutate(value_lag1 = lag(value, 1), value_lag2 = lag(value, 2), continuous = ifelse(!is.na(value) & !is.na(value_lag1) | !is.na(value) & !is.na(value_lag2), 1, 0)) %>% ungroup() %>% drop_na(value) %>% mutate(continuous_lag1 = lag(continuous, 1), years_id_consecutive = ifelse(continuous==0, 0, continuous+continuous_lag1), year = as.character(year)) %>% select(-value_lag1, -value_lag2, -continuous, -continuous_lag1) df_new <- left_join(df, df_panel, by = c("id", "year", "value")) df_new[is.na(df_new)] <- 0
高效解决方案
方案1:R Base 实现
无需创建平衡面板,直接按id分组处理,避免冗余计算:
# 转换年份为数值型便于计算 df$year_num <- as.numeric(df$year) # 按id分组生成连续年份计数 consecutive_data <- lapply(split(df, df$id), function(sub_df) { # 获取当前id的唯一年份并排序 unique_years <- sort(unique(sub_df$year_num)) # 标记连续段:当前年份与前一年份间隔≤2则属于同一段 seg_marks <- c(TRUE, diff(unique_years) > 2) seg_groups <- cumsum(seg_marks) # 为每个年份计算连续计数 year_counts <- sapply(unique_years, function(y) { current_seg <- unique_years[seg_groups == seg_groups[unique_years == y]] sum(diff(c(current_seg[current_seg <= y], y + 1)) <= 2) + 1 }) # 返回匹配用的数据框 data.frame(id = unique(sub_df$id), year_num = unique_years, years_id_consecutive = year_counts) }) # 合并结果到原数据集 consecutive_df <- do.call(rbind, consecutive_data) df_new <- merge(df, consecutive_df, by = c("id", "year_num"), all.x = TRUE) # 处理极端情况(原数据无缺失,此步仅作兜底) df_new$years_id_consecutive[is.na(df_new$years_id_consecutive)] <- 0 # 移除临时变量 df_new <- df_new[, !names(df_new) %in% "year_num"]
方案2:data.table 实现(大数据最优选择)
data.table的分组操作效率远高于dplyr,适合百万级以上数据集:
library(data.table) setDT(df) df[, year_num := as.numeric(year)] # 按id分组计算连续计数 df[, years_id_consecutive := { sorted_years <- sort(unique(year_num)) # 划分连续段 seg_groups <- cumsum(c(TRUE, diff(sorted_years) > 2)) # 为每个段内的年份生成计数 count_list <- lapply(unique(seg_groups), function(g) { seg_years <- sorted_years[seg_groups == g] sapply(seg_years, function(y) sum(diff(c(seg_years[seg_years <= y], y + 1)) <= 2) + 1) }) # 映射回当前行的年份 unlist(count_list[match(year_num, sorted_years)]) }, by = id] # 移除临时变量 df[, year_num := NULL]
结果说明
以原数据集为例,最终years_id_consecutive结果如下:
- id=X:2000(1)、2002(2,与2000间隔2符合连续)、2007(1,与2002间隔5不连续)
- id=Z:2001(1)、2002(2,与2001间隔1)、2004(2,与2002间隔2)、2007(1,与2004间隔3)
同一id-year的所有行将获得相同的连续计数。
内容的提问来源于stack exchange,提问作者vog
相关产品推荐
相关产品推荐

