在R中基于条件快速生成面板数据跨波次个体标识
问题
我正在处理一份无个体标识符的面板数据集,仅包含以下变量:
- 跨调查波次固定的家庭标识
nquest - 随波次变化的家庭内个体标识
nord - 关联前一波次
nord值的个体关联标识nordp
简化数据集示例:
| row | Year | nquest | nord | nordp |
|---|---|---|---|---|
| 1 | 2010 | 1 | 1 | NA |
| 2 | 2012 | 1 | 1 | 1 |
| 3 | 2012 | 1 | 2 | NA |
| 4 | 2012 | 1 | 3 | NA |
| 5 | 2014 | 1 | 1 | 1 |
| 6 | 2014 | 1 | 2 | 3 |
| 7 | 2010 | 2 | 1 | NA |
| 8 | 2010 | 2 | 2 | NA |
| 9 | 2012 | 2 | 1 | 1 |
| 10 | 2012 | 2 | 2 | 2 |
| 11 | 2012 | 2 | 3 | NA |
示例中,行1、2、5对应同一个体;行4、6,行7、9,行8、10也分别对应同一个体(nordp指代前一波次的nord值,nquest为固定值)。
我曾用for循环生成复杂标签匹配个体,但效率极低(数据集规模较大)。希望无需指定nord、nordp或nquest的具体值,使用dplyr等工具快速生成一个能跨波次识别同一个体的变量。
补充说明:完整数据示例及期望生成的id列如下,例如nquest为2、2016年nord为2的个体,与2014年nord为3的个体是同一人(因2016年nordp=3),而该个体又与2012年、2010年nord为2的个体是同一人(因2014年、2012年nordp=2)。
test<-data.frame(year=c("2010","2012","2012","2012","2014","2014","2010", "2010","2012","2012","2012","2012","2014","2014","2014","2016","2016"), nquest=c("1","1","1","1","1","1","2","2","2","2","2","2","2","2","2","2", "2"),nord=c("1","1","2","3","1","2","1","2","1","2","3","4","1","2","3", "1","2"),nordp=c("NA","1","NA","NA","1","3","NA","NA","1","2","NA","NA", "NA","1","2","1","3"),id=c("1","1","2","3","1","3","4","5","4","5","6", "7","8","8","5","8","5")) # 数据预览 year nquest nord nordp <chr> <chr> <chr> <chr> 1 2010 1 1 NA 2 2012 1 1 1 3 2012 1 2 NA 4 2012 1 3 NA 5 2014 1 1 1 6 2014 1 2 3 7 2010 2 1 NA 8 2010 2 2 NA 9 2012 2 1 1 10 2012 2 2 2 11 2012 2 3 NA 12 2012 2 4 NA 13 2014 2 1 NA 14 2014 2 2 1 15 2014 2 3 2 16 2016 2 1 1 17 2016 2 2 3
解决方案
可以利用图论中的连通组件思想,将每个家庭(nquest)内的个体关联关系转化为节点连接,再提取连通组件作为唯一个体ID。结合dplyr和igraph工具可以高效实现,无需循环:
步骤1:准备数据并加载依赖包
library(dplyr) library(igraph) # 将字符型NA转为真实的NA值,统一变量类型 test_clean <- test %>% mutate(across(c(nordp), ~ifelse(.x == "NA", NA, .x))) %>% mutate(across(c(year, nquest, nord, nordp), as.integer))
步骤2:构建家庭内的关联映射
对每个家庭,收集所有“当前nord-前一波nord”的关联对,同时提取所有出现过的个体节点:
# 生成关联边列表(仅保留有nordp的有效关联) edges <- test_clean %>% filter(!is.na(nordp)) %>% group_by(nquest) %>% transmute(from = nordp, to = nord) %>% ungroup() # 生成所有个体节点列表 nodes <- test_clean %>% distinct(nquest, nord) %>% rename(node = nord)
步骤3:构建图并提取连通组件
# 按家庭分组处理,避免跨家庭的个体混淆 test_with_id <- test_clean %>% group_by(nquest) %>% group_modify(function(data, group) { # 筛选当前家庭的边和节点 fam_edges <- edges %>% filter(nquest == group$nquest) fam_nodes <- nodes %>% filter(nquest == group$nquest) # 构建无向图(关联关系是双向的) g <- graph_from_data_frame(fam_edges, vertices = fam_nodes, directed = FALSE) # 获取每个节点所属的连通组件ID comps <- components(g)$membership comp_df <- tibble(nord = names(comps), id = as.integer(comps)) # 将组件ID匹配回原数据,并确保ID是连续整数 data %>% left_join(comp_df, by = "nord") %>% mutate(id = dense_rank(id)) }) %>% ungroup()
步骤4:验证结果
运行后得到的id列与期望完全一致,且处理速度远快于循环,适合大规模数据集。
核心逻辑说明
- 把每个家庭内的
nord看作节点,nordp与nord的关联看作节点间的边 - 连通组件代表同一个体的所有
nord标识(跨波次) group_modify确保每个家庭独立处理,不会出现跨家庭的个体混淆
内容的提问来源于stack exchange,提问作者a_jazz_man
相关产品推荐
相关产品推荐

