在 R 中使用 2 个数据帧执行简单查找

Performing simple lookup using 2 data frames in R

在R中,我有两个数据框A和B如下-

数据框A:

Name      Age    City       Gender   Income    Company   ...
JXX       21     Chicago    M        20K       XYZ       ...
CXX       25     NewYork    M        30K       PQR       ...
CXX       26     Chicago    M        NA        ZZZ       ...

数据帧 B:

Age    City       Gender    Avg Income  Avg Height  Avg Weight   ...
21     Chicago    M         30K         ...         ...          ...
25     NewYork    M         40K         ...         ...          ...
26     Chicago    M         50K         ...         ...          ...

我想用数据框 B 填充数据框 A 中的缺失值。

例如,对于数据框 A 中的第三行,我可以用数据框 B 中的平均收入代替精确收入。我不想合并这两个数据框,而是想使用年龄、城市和性别列执行类似查找的操作。

所以我认为这适用于收入。如果只有这 3 列,您可以将其他列的名称替换为:

df1<-read.table(header = T, stringsAsFactors = F, text = "
Name      Age    City       Gender   Income    Company   
JXX       21     Chicago    M        20K       XYZ       
CXX       25     NewYork    M        30K       PQR       
CXX       26     Chicago    M        NA        ZZZ")       

df2<-read.table(header = T, stringsAsFactors = F, text = "

Age    City       Gender    Avg_Income 
21     Chicago    M         30K        
25     NewYork    M         40K        
26     Chicago    M         50K        ")

df1[is.na(df1$Income),]$Income<-df2[is.na(df1$Income),]$Avg_Income

如果其中一位常客有更好的方法让您不必重新键入列名,我也不会感到惊讶。

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)。
  • 连接的两行应该导致用 B 中的非 NA 值替换 A 中的 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 参数中进行连接,并使用 [=67 将源列就地分配给目标列=] := 语法。

请注意,在 data.table 世界中 X[Y] 执行 右连接 ,因此如果您想要 左连接 您需要将其反转为 Y[X]("left" 现在指的是 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() 加入它们(注意这是一个 inner join,因为我们在这里不需要任何类型的外部连接),并且只提取连接产生的行索引列。这有效地为所有后续修改操作预先计算了连接的行对。

为了修改,我预先计算了 A 中的行满足替换条件的连接对的子集,例如对于 Income 替换,它的 Income 值为 NA。然后我们可以对这些行的连接对 table 进行子集化,并从 B 到 A 进行直接赋值以执行替换。

和以前一样,您可以为要替换的每一列重复分配行。

您可以简单地使用以下内容将城市的平均收入从 B 更新为 A 的收入。

dataFrameA$Income = dataFrameB$`平均收入`[匹配(dataFrameA$City, dataFrameB$City)]

如果列名有 space

,则必须使用“`”

这类似于在 excel 中使用索引和匹配进行查找。我假设您来自 excel。如果使用 data.table

代码会更紧凑