library(data.table);
## generate data
set.seed(5L);
NK <- 6L; pA <- 0.8; pB <- 0.2;
keydf <- unique(data.frame(Age=sample(18:65,NK,T),City=sample(c('Chicago','NewYork'),NK,T),Gender=sample(c('M','F'),NK,T),stringsAsFactors=F));
NO <- nrow(keydf)-1L;
Af <- cbind(keydf[-1L,],Name=sample(paste0(LETTERS,LETTERS,LETTERS),NO,T),Income=sample(c(NA,paste0(seq(20L,90L,10L),'K')),NO,T,c(pA,rep((1-pA)/8,8L))),stringsAsFactors=F)[sample(seq_len(NO)),];
Bf <- cbind(keydf[-2L,],`Avg Income`=sample(c(NA,paste0(seq(20L,90L,10L),'K')),NO,T,c(pB,rep((1-pB)/8,8L))),stringsAsFactors=F)[sample(seq_len(NO)),];
At <- as.data.table(Af);
Bt <- as.data.table(Bf);
At;
## Age City Gender Name Income
## 1: 50 NewYork F OOO NA
## 2: 23 Chicago M SSS NA
## 3: 62 NewYork M VVV NA
## 4: 51 Chicago F FFF 90K
## 5: 31 Chicago M XXX NA
Bt;
## Age City Gender Avg Income
## 1: 62 NewYork M NA
## 2: 51 Chicago F 60K
## 3: 31 Chicago M 50K
## 4: 27 NewYork M NA
## 5: 23 Chicago M 60K
出于演示目的,我生成了一些随机测试数据。我对种子 5 得到的结果非常满意,它涵盖了很多情况:
- A 中未与 B 连接的一行 (50/NewYork/F)。
- B 中未与 A 连接的一行 (27/NewYork/M)。
- 连接的两行应导致将 A 中的 NA 替换为 B 中的非 NA 值(23/Chicago/M 和 31/Chicago/M)。
- 在 B 中加入但有 NA 的一行,因此不应影响 A 中的 NA (62/NewYork/M)。
- 可以加入的一行,但在 A 中具有非 NA,因此不应从 B 中获取值(我假设您会想要这种行为)(51/Chicago/F)。 A (90K) 中的值与 B (60K) 中的值不同,因此我们可以验证这种行为。
我故意打乱了 A 和 B 的行,以确保我们正确连接它们,而不管传入的行顺序如何。
## data.table solution
keys <- c('Age','City','Gender');
At[is.na(Income),Income:=Bt[.SD,on=keys,`Avg Income`]];
## Age City Gender Name Income
## 1: 50 NewYork F OOO NA
## 2: 23 Chicago M SSS 60K
## 3: 62 NewYork M VVV NA
## 4: 51 Chicago F FFF 90K
## 5: 31 Chicago M XXX 50K
在上面我首先过滤 A 中的 NA 值,然后在键列上的 j 参数中进行连接,并使用 data.table := 语法将源列原位分配给目标列.
请注意,在 data.table 世界中,X[Y] 会进行 右连接,因此如果您想要 左连接,您需要将其反转为 Y[X]( “左”现在指的是X,与直觉相反)。这就是为什么我使用Bt[.SD] 而不是(可能更自然的期望).SD[Bt]。我们需要在.SD 上进行左连接,因为连接索引表达式的结果将就地分配给目标列,因此分配的 RHS 必须是与目标列对应的全向量。
您可以为要替换的每一列重复就地分配行。
## base R solution
keys <- c('Age','City','Gender');
m <- merge(cbind(Af[keys],Ai=seq_len(nrow(Af))),cbind(Bf[keys],Bi=seq_len(nrow(Bf))))[c('Ai','Bi')];
m;
## Ai Bi
## 1 2 5
## 2 5 3
## 3 4 2
## 4 3 1
mi <- which(is.na(Af$Income[m$Ai])); Af$Income[m$Ai[mi]] <- Bf$`Avg Income`[m$Bi[mi]];
Af;
## Age City Gender Name Income
## 2 50 NewYork F OOO <NA>
## 5 23 Chicago M SSS 60K
## 3 62 NewYork M VVV <NA>
## 6 51 Chicago F FFF 90K
## 4 31 Chicago M XXX 50K
我想我在这里感觉有点创意,所以对于一个基本的 R 解决方案,我做了一些可能有点不寻常的事情,而且我以前从未做过。我将合成的行索引列列绑定到每个 A 和 B data.frames 的键列子集,然后调用merge() 来加入它们(注意这是一个内连接,因为我们在这里不需要任何类型的外连接),并且只提取了连接产生的行索引列。这有效地为所有后续修改操作预先计算连接的行对。
对于修改,我预先计算 A 中的行满足替换条件的连接对的子集,例如对于Income 替换,它的Income 值是NA。然后,我们可以为这些行对连接对表进行子集化,并直接从 B 分配到 A 以执行替换。
和以前一样,您可以为要替换的每一列重复分配行。