dplyr连接定义NA值

36

我可以在dplyr连接中为NA定义一个“fill”值吗?例如,在连接中定义所有NA值应该为1?

require(dplyr)
lookup <- data.frame(cbind(c("USD","MYR"),c(0.9,1.1)))
names(lookup) <- c("rate","value")
fx <- data.frame(c("USD","MYR","USD","MYR","XXX","YYY"))
names(fx)[1] <- "rate"
left_join(x=fx,y=lookup,by=c("rate"))
以上代码将为值为“XXX”和“YYY”的数值创建NA。在我的情况下,我正在连接大量列,并且会有很多不匹配项。所有不匹配项应具有相同的值。我知道可以分几个步骤完成,但问题是是否可以一步完成? 谢谢!
4个回答

24

如果您已经在使用dplyr,那么最好利用 dplyr::coalesce 并使用dplyr语法将1或0传递给它。我认为这看起来很不错...

... %>%
mutate_if(is.numeric,coalesce,0)

其中0是传递给dplyr :: coalesce以替换NA的参数。

在问题的示例中,有带有因子的数据框。我相信人们不会将FX汇率或另一个向量作为因子,您需要用零替换NA,因此我继续在下面添加了这一步骤,只是为了使提供的示例可执行。

# replace NAs with zeros for all numeric columns
#
# ... code from question above
left_join(x=fx,y=lookup,by=c("rate")) %>%
    # ignore if factors in value column are because it's a toy example
    mutate(value = as.numeric(as.character(value))) %>%
    # the good stuff here
    mutate_if(is.numeric,coalesce,0)

1
不错!语法很漂亮。 - thc
4
或者,使用更新的tidy-select语法:mutate(across(where(is.numeric), coalesce, 0)) - merv

22

首先,我建议不要使用组合data.frame(cbind(...))。原因如下:cbind默认情况下会创建一个matrix,如果你只传入 atomic vectors 给它。在 R 中,矩阵只能有一种类型的数据(把矩阵看作是具有维数属性的向量,即行数和列数)。因此,你的代码

cbind(c("USD","MYR"),c(0.9,1.1))
创建一个字符矩阵:
str(cbind(c("USD","MYR"),c(0.9,1.1)))
# chr [1:2, 1:2] "USD" "MYR" "0.9" "1.1"

尽管你可能期望得到一个最终的数据框,其中包含一个字符或因子列(rate)和一个数字列(value)。但实际上你得到的是:

str(data.frame(cbind(c("USD","MYR"),c(0.9,1.1))))
#'data.frame':  2 obs. of  2 variables:
# $ X1: Factor w/ 2 levels "MYR","USD": 2 1
# $ X2: Factor w/ 2 levels "0.9","1.1": 1 2

因为默认情况下使用 data.frame 时会将字符串(字符)转换为因子(factor)(您可以通过在 data.frame() 调用中指定 stringsAsFactors = FALSE 来规避这一问题)。

我建议采用以下替代方法创建样本数据(同时还请注意,您可以在同一调用中轻松地指定列名):

lookup <- data.frame(rate = c("USD","MYR"), 
                     value = c(0.9,1.1))

fx <- data.frame(rate = c("USD","MYR","USD","MYR","XXX","YYY"))

现在,针对您的实际问题,如果我理解正确的话,您想要用 1 替换连接数据中的所有 NA。如果是这样的话,下面是使用 left_joinmutate_each 进行操作的自定义函数:

library(dplyr)
left_join_NA <- function(x, y, ...) {
  left_join(x = x, y = y, by = ...) %>% 
    mutate_each(funs(replace(., which(is.na(.)), 1)))
}

现在,您可以像这样将其应用于您的数据:

> left_join_NA(x = fx, y = lookup, by = "rate")
#  rate value
#1  USD   0.9
#2  MYR   1.1
#3  USD   0.9
#4  MYR   1.1
#5  XXX   1.0
#6  YYY   1.0
#Warning message:
#joining factors with different levels, coercing to character vector 

注意,您将得到一个字符列(rate)和一个数字列(value),所有的NAs都将被替换为1。

str(left_join_NA(x = fx, y = lookup, by = "rate"))
#'data.frame':  6 obs. of  2 variables:
# $ rate : chr  "USD" "MYR" "USD" "MYR" ...
# $ value: num  0.9 1.1 0.9 1.1 1 1

3
首先感谢您提供了详尽的数据创建提示,对于未来的问题会很有帮助。其次,您的公式很简洁,我会使用它。非常感谢! - Triamus
2
为什么不直接使用 fx[is.na(fx)] <- 1 呢? - rrs
@rrs 因为这可能是一个长的 dplyr 链的中途。 - Nelson Auner
13
@nelsonauner 还有 tidyr 库中的 replace_na()fill() 命令。 - rrs
funs is deprecated as of dplyr 0.8.0 - momeara

8

我在使用dplyr时遇到了相同的问题,并编写了一个小函数来解决它。(该解决方案需要使用tidyr和dplyr)

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

最初回答来自:R左外连接,填充0而不是NA,同时保留左表中有效的NA值


这是一个关于R语言中左外连接的问题。当使用左外连接时,有时会出现NA值,而我们希望用0来填充这些NA值。但同时,我们也希望保留左表中真正的NA值。如何实现这个目标呢?

7
一个 tidyverse 的解决方案是在连接后使用 tidyr::replace_na:
left_join(x = fx, y = lookup, by = c("rate")) %>% 
  replace_na(list(value = 0))

或者,对于更一般的情况:

left_join(x = fx, y = lookup, by = c("rate")) %>% 
  mutate(across(where(is.numeric), ~ replace_na(.x, 0)))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接