替代R语言for循环中append操作的高效方法
问题描述
我有两个数据框:家庭信息数据框df_men,以及家庭成员信息数据框df_ind。每个家庭有唯一IDid,每个成员都标记了所属家庭的ID。我需要获取每个家庭户主的年龄,规则是当LIEN_CM==0时,该成员为户主。
我原本用的代码如下,但因为df_ind有3,341,426行,循环运行速度极慢,想找更高效的方法:
age_hh <- c() for (i in df_ind$id) { b <- df_ind[df_ind$id==i & df_ind$LIEN_CM==0,]$AGE1 age_hh <- append(age_hh, b) }
age_hh是最终要得到的年龄向量。
更新内容
我尝试了评论里的方法,但结果有问题:我的家庭数据框df_men只有730,099个家庭:
> dim(df_men) [1] 730099 43
所以户主数量应该也是730,099,从LIEN_CM的统计也能看出来:
> table(df_ind$LIEN_CM) 0 1 2 3 4 5 6 7 8 9 730099 561080 1558839 163435 60048 75679 89529 69358 4360 22845
但用该方法得到的年龄向量有736,253个值:
> df_ind[df_ind$LIEN_CM==0,'AGE1'] # A tibble: 736,253 × 1 AGE1 <dbl+lbl> 1 NA 2 17 [17 ans] 3 NA 4 NA 5 NA 6 NA 7 NA 8 NA 9 NA 10 NA # ℹ 736,243 more rows # ℹ Use `print(n = ...)` to see more rows
其中大部分是NA:
> sum(is.na(df_ind[df_ind$LIEN_CM==0,'AGE1'])) [1] 724462
另一个评论里的方法运行时间极长,至今还在跑。
更新内容2
补充统计:
> sum(df_ind$LIEN_CM==0) [1] NA > sum(df_ind$LIEN_CM==0, na.rm = TRUE) [1] 730099
以下是df_ind前100行的id、LIEN_CM、AGE1字段的dput输出:
> dput(head(df_ind[c("id","LIEN_CM","AGE1")], n = 100)) structure(list(id = c(25500L, 25500L, 25501L, 25501L, 25501L, 25501L, 25501L, 25502L, 25502L, 25502L, 25502L, 25502L, 25503L, 25503L, 25503L, 25503L, 25503L, 25503L, 25503L, 25504L, 25504L, 25504L, 25504L, 25504L, 25505L, 25506L, 25506L, 25507L, 25508L, 25508L, 25508L, 25508L, 25509L, 25510L, 25510L, 25510L, 25510L, 25510L, 25511L, 25511L, 25511L, 25511L, 25511L, 25511L, 25511L, 25511L, 25511L, 25512L, 25512L, 25512L, 25512L, 25512L, 25513L, 25514L, 25514L, 25514L, 25514L, 25514L, 25515L, 25515L, 25515L, 25516L, 25516L, 25516L, 25516L, 25516L, 25516L, 25516L, 25517L, 25517L, 25517L, 25517L, 25518L, 25518L, 25518L, 25518L, 25518L, 25518L, 25519L, 25519L, 25519L, 25519L, 25519L, 25519L, 25519L, 25520L, 25520L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L, 25521L), LIEN_CM = c(0, 4, 0, 9, 9, 9, 9, 0, 1, 2, 2, 2, 0, 2, 2, 2, 2, 2, 4, 0, 1, 2, 2, 2, 0, 0, 1, 0, 0, 5, 5, 5, 0, 0, 1, 2, 2, 2, 0, 1, 2, 2, 2, 2, 2, 6, 2, 0, 1, 2, 6, 3, 0, 0, 1, 2, 2, 4, 0, 2, 2, 0, 1, 2, 2, 2, 5, 5, 0, 1, 2, 2, 0, 1, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1, 2, 6, 3, 3, 3, 3, 2, 6, 3, 3, 3), AGE1 = c(NA, NA, 17, 20, 22, 20, NA, NA, NA, NA, NA, 24, NA, NA, NA, NA, 21, 18, NA, NA, NA, 8, 5, 4, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 10, 5, 1, NA, NA, 20, 17, 13, 9, 21, 22, 0, NA, NA, NA, 24, 2, 20, NA, NA, 5, 3, NA, NA, NA, NA, NA, NA, 16, 13, 6, NA, NA, NA, NA, 3, 1, NA, NA, 21, 19, 17, 9, NA, NA, 24, 21, 19, 18, 17, NA, 23, NA, NA, NA, NA, NA, NA, 18, 15, NA, NA, 14, 10, 5 )), row.names = c(NA, 100L), class = c("grouped_df", "tbl_df", "tbl", "data.frame"))
解决方案
问题根源
从dput输出可见df_ind是分组数据框(grouped_df),直接筛选会保留分组结构导致计数异常;同时原始循环重复处理同一家庭ID,完全冗余。
高效方法1:用dplyr分组提取
先取消分组,再按家庭ID去重提取户主年龄:
library(dplyr) # 取消分组(否则筛选逻辑会受分组影响) df_ind_ungrouped <- ungroup(df_ind) # 筛选户主、去重、提取年龄 age_hh <- df_ind_ungrouped %>% filter(LIEN_CM == 0) %>% distinct(id, .keep_all = TRUE) %>% # 确保每个家庭仅保留一条户主记录 pull(AGE1)
高效方法2:用基础R的aggregate函数
无需加载第三方包,用基础R实现:
# 取消分组 df_ind_ungrouped <- ungroup(df_ind) # 按ID聚合,提取每个家庭的户主年龄 age_hh <- aggregate(AGE1 ~ id, data = df_ind_ungrouped[df_ind_ungrouped$LIEN_CM == 0, ], FUN = function(x) x[1])$AGE1
关键说明
- 取消分组:分组数据框的筛选逻辑会受分组规则干扰,必须先
ungroup()才能正常处理。 - 去重处理:
distinct(id, .keep_all = TRUE)避免同一家庭存在多条户主记录的异常情况,保证结果数量与家庭数一致。 - 性能优势:两种方法均为向量化操作,比循环快数倍,可轻松处理百万级数据。
内容的提问来源于stack exchange,提问作者Saïd Maanan
相关产品推荐
相关产品推荐

