非逐行修改多列值:值不在指定列则设为NA的高效方法
高效实现行内b列值与a列值的匹配替换
示例数据
df <- structure(list(row = c(1L, 12L, 17L, 44L, 55L, 90L), b1 = c(55L,17L, 12L, 12L, 1L, 12L), b2 = c(NA, 44L, 44L, 17L, NA, 17L), b3 = c(NA, 90L, 90L, 90L, NA, 44L), a1 = c(55L, 17L, 12L, 12L, 1L, 12L), a2 = c(NA, 44L, 44L, 17L, NA, 28L), a3 = c(NA, 90L, NA, 90L, NA, 44L)), row.names = c(NA, -6L), class = "data.frame")
需求说明
对数据框每行执行以下操作:
- 检查所有以
b开头的列(如b1/b2/b3)的值是否存在于该行以a开头的列(如a1/a2/a3)中 - 若未匹配到,将该
b列对应位置的值设为NA - 注:
a、b列的数量可能变化,但命名规则固定(分别以a/b开头)
原实现使用嵌套循环,但面对大数据量时效率不足,现提供更高效的替代方案。
原循环实现(效率较低)
acols <- grep("^a", colnames(df)) bcols <- grep("^b", colnames(df)) for(i in 1:nrow(df)) { for(j in bcols) { if(df[i, j] %in% df[i, acols]) { next } else { df[i, j] <- NA } } }
高效解决方案
方案1:tidyverse 实现
利用dplyr的行处理能力,避免显式循环:
library(dplyr) df_processed <- df %>% rowwise() %>% mutate( across(starts_with("b"), ~ ifelse(.x %in% c_across(starts_with("a")), .x, NA)) ) %>% ungroup() print(df_processed)
rowwise():将数据框按行分组,确保后续操作逐行执行c_across(starts_with("a")):提取当前行所有a开头列的值组成向量across(starts_with("b")):对每个b开头列应用判断逻辑,匹配保留原值,不匹配设为NA
方案2:Base R 实现
通过矩阵操作和apply逐行处理,效率优于嵌套循环:
# 提取a列和b列的矩阵 a_mat <- df[, grep("^a", colnames(df))] b_mat <- df[, grep("^b", colnames(df))] # 逐行判断每个b值是否在对应行的a值中,生成逻辑矩阵 match_mat <- t(apply(b_mat, 1, function(row_b) { row_a <- na.omit(a_mat[rownames(b_mat)[row(b_mat)[1]], ]) row_b %in% row_a })) # 将不匹配的位置设为NA b_mat[!match_mat] <- NA # 合并回原数据框并恢复列顺序 df_processed <- cbind(df[, !colnames(df) %in% colnames(b_mat)], b_mat, a_mat) df_processed <- df_processed[, colnames(df)] print(df_processed)
- 先分离
a、b列矩阵,通过apply逐行生成匹配逻辑矩阵 - 利用矩阵索引直接替换不匹配的值为
NA,最后合并回原数据框并恢复列顺序
期望输出
row b1 b2 b3 a1 a2 a3 1 1 55 NA NA 55 NA NA 2 12 17 44 90 17 44 90 3 17 12 44 NA 12 44 NA 4 44 12 17 90 12 17 90 5 55 1 NA NA 1 NA NA 6 90 12 NA 44 12 28 44
内容的提问来源于stack exchange,提问作者L Tyrone
相关产品推荐
相关产品推荐

