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

避免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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:00:58