【问题标题】:Performing simple lookup using 2 data frames in R在 R 中使用 2 个数据帧执行简单查找
【发布时间】:2017-04-04 19:20:00
【问题描述】:

在 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 的平均收入代替确切收入。我不想合并这两个数据框,而是想使用 Age、City 和 Gender 列执行类似查找的操作。

【问题讨论】:

标签: r dataframe merge lookup r-package


【解决方案1】:
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 以执行替换。

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

【讨论】:

    【解决方案2】:

    所以我认为这适用于收入。如果只有这 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
    

    如果其中一位常客有更好的方法来防止您重新输入列名,我不会感到惊讶。

    【讨论】:

      【解决方案3】:

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

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

      如果列名有空格,则必须使用“`”

      这类似于在 Excel 中使用索引和匹配进行查找。我假设你来自excel。如果使用data.table,代码会更紧凑

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-05
        • 2021-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-03
        • 1970-01-01
        相关资源
        最近更新 更多