在 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
代码会更紧凑
在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
代码会更紧凑