dplyr 加入定义 NA 值

dplyr join define NA values

我可以在 dplyr join 中为 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。在我的例子中,我加入了大量的专栏,并且会有很多不匹配的地方。所有不匹配项都应具有相同的值。我知道我可以分几步完成,但问题是可以一步完成吗? 谢谢!

首先,我建议不要使用组合data.frame(cbind(...))。原因如下:如果您只向它传递原子向量,cbind 默认会创建一个 matrix。 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"

虽然您可能期望最终数据框包含字符或因子列(比率)和数字列(值)。但是你得到的是:

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时字符串(字符)会转换为因数(您可以通过在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 

请注意,您最终得到一个字符列(比率)和一个数字列(值),并且所有 NA 都被 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

我偶然发现了与 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
}

最初回答于:

如果您无论如何都在使用 dplyr,不妨利用 dplyr::coalesce,并使用 dplyr 语法将 1 或 0 传递给它。我认为这看起来不错...

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

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

在问题的示例中,有带有因子的数据框。我相信人们不会将 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)

一个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)))