R语言dplyr处理纵向数据:实现迁移累计计数、标记事件前后年份
问题修正方案
可直接运行的修正后代码
library(dplyr) set.seed(123) df <- tibble( id = c(1, 2, 3, 4, 5, 1, 2, 3, 5, 6, 7, 2, 3, 4, 6, 7, 8, 1, 2, 3, 4, 6, 7, 8 ), year = c(rep(2009, 5), rep(2010, 6), rep(2011, 6), rep(2012, 7)), age = c(0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 2, 2, 2, 1, 2, 2, 3, 3, 3, 3, 2, 3, 3), town = c("0", "0", "2", "0", "0", "1", "2", "1", "3", "0", "1", "3", "1", "4", "1", "2", "1", "4", "2", "2", "1", "2", "1", "5") ) df3 <- df %>% arrange(id, year) %>% group_by(id) %>% mutate(first_year = min(year)) %>% mutate(first_town = list(town[year==first_year])) %>% mutate(flag_move = as.numeric(year != first_year & !(town %in% unlist(first_town)) & town !="")) %>% mutate(flag_first_move = (flag_move==1 & as.numeric(!duplicated(flag_move)))) %>% mutate(moved = case_when( is.na(lag(town)) ~ 0, town != lag(town) ~ 1, TRUE ~ 0 )) %>% mutate(flag_cum_move = cumsum(moved)) df4 <- df3 %>% group_by(id) %>% filter(any(flag_first_move == 1)) %>% mutate( move_years = list(year[moved == 1]), year_before = as.integer(year %in% (unlist(move_years) - 1)), year_after = as.integer(year %in% (unlist(move_years) + 1)) ) %>% select(-move_years)
修正逻辑说明
1. 累计迁移计数flag_cum_move实现
原写法错误点:
- 用
diff(moved)生成的向量长度比分组内观测数少1,手动补0的逻辑没有对齐迁移事件触发节点,会把非迁移的数值波动计入计数。
修正逻辑: - 已生成的
moved列本身已正确标记每一次居住地变动:迁移发生对应行取值为1,其余为0。直接按分组对moved做累计求和cumsum(moved),就能得到每个观测时点对应的个体累计迁移次数,初始未迁移状态为0,每发生一次迁移计数加1,完全匹配需求。
额外优化:原moved生成逻辑没有显式处理每个id首行lag(town)返回NA的情况,显式加判断is.na(lag(town)) ~ 0避免隐式类型转换的潜在问题;排序改为先按id再按year,保证分组内时序正确。
2. 多次迁移下的迁移前后年份标记
原写法错误点:
- 当个体存在多次迁移时,
year[moved == 1]返回长度大于1的向量,直接和逐行的标量year做差会触发R的向量循环对齐规则,长度不匹配时输出结果完全错乱,同时抛出警告。
修正逻辑: - 先提取每个个体所有迁移发生的年份存入临时列表列,对每一行的年份,判断是否落在「所有迁移年份减1」的集合中(即迁移前一年),或落在「所有迁移年份加1」的集合中(即迁移后一年),用
%in%做逐元素判断,无论个体有多少次迁移都能正确标记,不会出现长度不匹配问题。 - 该逻辑自动适配连续迁移场景:例如个体2010、2011连续迁移,2010年会被标记为2011年迁移的
year_before,2011年会被标记为2010年迁移的year_after,符合时序逻辑。
内容的提问来源于stack exchange,提问作者viktorp
相关产品推荐
相关产品推荐

