R 左外部加入 0 填充而不是 NA,同时在左侧保留有效的 NA Table

R Left Outer Join with 0 Fill Instead of NA While Preserving Valid NA's in Left Table

对两个数据 tables (dt1, dt2) 进行左外连接的最简单方法是什么,填充值为 0(或其他一些值)而不是 NA(默认值)而不覆盖左侧数据中的有效 NA 值 table?

一个常见的答案,例如在 this thread 中,是使用 dplyr::left_joindata.table::mergedata.table 的 dt2[dt1] keyed 进行左外连接列括号语法,然后是第二步,只需将连接数据 table 中的所有 NA 值替换为 0。例如:

library(data.table);
dt1 <- data.table(x=c('a', 'b', 'c', 'd', 'e'), y=c(NA, 'w', NA, 'y', 'z'));
dt2 <- data.table(x=c('a', 'b', 'c'), new_col=c(1,2,3));
setkey(dt1, x);
setkey(dt2, x);
merged_tables <- dt2[dt1];
merged_tables[is.na(merged_tables)] <- 0;

这种方法必然假设 dt1 中没有需要保留的有效 NA 值。然而,正如您在上面的示例中看到的,结果是:

   x new_col y
1: a       1 0
2: b       2 w
3: c       3 0
4: d       0 y
5: e       0 z

但期望的结果是:

   x new_col y
1: a       1 NA
2: b       2 w
3: c       3 NA
4: d       0 y
5: e       0 z

在这种微不足道的情况下,不用像上面那样使用 data.table 所有元素替换语法,只需替换 new_col 中的 NA 值即可:

library(dplyr);
merged_tables <- mutate(merged_tables, new_col = ifelse(is.na(new_col), 0, new_col));

但是,这种方法对于非常大的数据集并不实用,因为其中合并了数十个或数百个新列,有时还使用动态创建的列名。即使所有列名都提前已知,列出所有新列并对每个列进行变异样式替换也是非常难看的。

一定有更好的办法吧?如果 dplyr::left_joindata.table::mergedata.table 中任何一个的语法容易允许用户指定 NA 以外的 fill 值,该问题将得到简单解决。类似于:

merged_tables <- data.table::merge(dt1, dt2, by="x", all.x=TRUE, fill=0);

data.tabledcast 函数允许用户指定 fill 值,所以我想一定有更简单的方法来做到这一点,我只是没有想到.

建议?

编辑:@jangorecki 在评论中指出,目前在 data.table GitHug page 上有一个功能请求正在打开,以执行我刚才提到的操作,更新 nomatch=0 语法。应该在 data.table.

的下一版本中

您能否使用列索引仅引用新列,因为 left_join 它们都位于结果 data.frame 的右侧?在这里它会在 dplyr:

dt1 <- data.frame(x = c('a', 'b', 'c', 'd', 'e'),
                  y = c(NA, 'w', NA, 'y', 'z'),
                  stringsAsFactors = FALSE)
dt2 <- data.frame(x = c('a', 'b', 'c'),
                  new_col = c(1,2,3),
                  stringsAsFactors = FALSE)

merged <- left_join(dt1, dt2)
index_new_col <- (ncol(dt1) + 1):ncol(merged)
merged[, index_new_col][is.na(merged[, index_new_col])] <- 0

> merged
  x    y new_col
1 a <NA>       1
2 b    w       2
3 c <NA>       3
4 d    y       0
5 e    z       0

目前最干净的方法可能只是将要在左侧 table (dt1) 连接的值作为中介 table 的种子,链接 dt2 的合并,设置 NA 值到 0,将中介 table 与 dt1 合并。可以完全用 data.table 完成,不依赖于 data.frame 语法,中间步骤确保在第二次合并时不会有 nomatch NA 结果:

library(data.table);
dt1 <- data.table(x=c('a', 'b', 'c', 'd', 'e'), y=c(NA, 'w', NA, 'y', 'z'));
dt2 <- data.table(x=c('a', 'b', 'c'), new_col=c(1,2,3));
setkey(dt1, x);
setkey(dt2, x);
inter_table <- dt2[dt1[, list(x)]];
inter_table[is.na(inter_table)] <- 0;
setkey(inter_table, x);
merged <- inter_table[dt1];

> merged;
   x new_col  y
1: a       1 NA
2: b       2  w
3: c       3 NA
4: d       0  y
5: e       0  z

这种方法的好处是它不依赖于在右侧添加的新列并且保持在 data.table 键控速度优化内。将答案归功于@SamFirke,因为他的解决方案也有效,并且在其他情况下可能更有用。

我偶然发现了与 dplyr 相同的问题,并编写了一个小函数解决了我的问题。 (解决方案需要 tidyr 和 dplyr)

left_join0 <- function(x, y, fill = 0L, ...){
  z <- left_join(x, y, ...)
  new_cols <- setdiff(names(z), names(x))
  z <- replace_na(z, setNames(as.list(rep(fill, length(new_cols))), new_cols))
  z
}