tbl 的 left_join:na_matches 不起作用

Hab*_*ert 6 sqlite r left-join dplyr dbplyr

left_join 对于小标题或数据帧上的 NA 值,它按预期工作,但在 tbl 上,它似乎与 NA 不匹配,即使使用选项 na_matches = "na"。

R 版本和包版本

> sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-apple-darwin18.6.0 (64-bit)
Running under: macOS Mojave 10.14.6
...
other attached packages:
 [1] reprex_0.3.0    dbplyr_1.4.2    lubridate_1.7.4 magrittr_1.5    forcats_0.4.0   stringr_1.4.0   dplyr_0.8.1     purrr_0.3.2     readr_1.3.1
[10] tidyr_0.8.3     tibble_2.1.3    ggplot2_3.2.0   tidyverse_1.2.1
...
Run Code Online (Sandbox Code Playgroud)

以下是 SQLite 的 reprex,但 PostgreSQL 也是如此(我实际上偶然发现了 PostgreSQL DB 的问题)。

最小的reprex。

(1) 我创建了 2 个数据框,将它们本地复制到 SQLite DB,然后作为 tbl 再次加载它们。

library(tidyverse)
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
df_1 <- tibble(A = c("a", "aa"), B = c("b", "bb"), D = c("d", NA))
df_2 <- tibble(A = c("a", "aa"), C = c("c", "cc"), D = c("d", NA))
copy_to(con, df_1, overwrite = T)
copy_to(con, df_2, overwrite = T)
dt_1 <- tbl(con, "df_1")
dt_2 <- tbl(con, "df_2")

df_1
#> # A tibble: 2 x 3
#>   A     B     D    
#>   <chr> <chr> <chr>
#> 1 a     b     d    
#> 2 aa    bb    <NA>

df_2
#> # A tibble: 2 x 3
#>   A     C     D    
#>   <chr> <chr> <chr>
#> 1 a     c     d    
#> 2 aa    cc    <NA>

dt_1
#> # Source:   table<df_1> [?? x 3]
#> # Database: sqlite 3.29.0 [:memory:]
#>   A     B     D    
#>   <chr> <chr> <chr>
#> 1 a     b     d    
#> 2 aa    bb    <NA>

dt_2
#> # Source:   table<df_2> [?? x 3]
#> # Database: sqlite 3.29.0 [:memory:]
#>   A     C     D    
#>   <chr> <chr> <chr>
#> 1 a     c     d    
#> 2 aa    cc    <NA>
Run Code Online (Sandbox Code Playgroud)

(2) 然后我left_join先在数据帧上使用,然后在 tbls 上使用:

left_join(df_1, df_2)
#> Joining, by = c("A", "D")
#> # A tibble: 2 x 4
#>   A     B     D     C    
#>   <chr> <chr> <chr> <chr>
#> 1 a     b     d     c    
#> 2 aa    bb    <NA>  cc

left_join(dt_1, dt_2, na_matches = "na")
#> Joining, by = c("A", "D")
#> # Source:   lazy query [?? x 4]
#> # Database: sqlite 3.29.0 [:memory:]
#>   A     B     D     C    
#>   <chr> <chr> <chr> <chr>
#> 1 a     b     d     c    
#> 2 aa    bb    <NA>  <NA>
Run Code Online (Sandbox Code Playgroud)

我们可以看到,在数据帧的情况下(默认情况下),第二行最后一列C具有预期的效果ccna_matches = "na"<NA>在 tbl 的情况下,即使使用显式选项na_matches = "na"(根据文档,这是默认值)。 这是出乎意料的

编辑

请注意,这与具有na_matches = "never"以下内容的数据帧的结果相同:


left_join(df_1, df_2, na_matches = "never")
#> Joining, by = c("A", "D")
#> # A tibble: 2 x 4
#>   A     B     D     C    
#>   <chr> <chr> <chr> <chr>
#> 1 a     b     d     c    
#> 2 aa    bb    <NA>  <NA>
Run Code Online (Sandbox Code Playgroud)

顺便说一句,标题提到left_join它是因为它是最常见的连接,但同样的问题出现在inner_join(full_join尚未用于数据表),如果我们同时保留na_matches = "na"两者,可能会更加明显:

inner_join(dt_1, dt_2, na_matches = "na")
#> Joining, by = c("A", "D")
#> # Source:   lazy query [?? x 4]
#> # Database: sqlite 3.29.0 [:memory:]
#>   A     B     D     C    
#>   <chr> <chr> <chr> <chr>
#> 1 a     b     d     c
inner_join(df_1, df_2, na_matches = "na")
#> Joining, by = c("A", "D")
#> # A tibble: 2 x 4
#>   A     B     D     C    
#>   <chr> <chr> <chr> <chr>
#> 1 a     b     d     c    
#> 2 aa    bb    <NA>  cc
Run Code Online (Sandbox Code Playgroud)

Hab*_*ert 4

为了响应 @philipxy \xe2\x80\x99s 在 left_join 进程中进一步挖掘的请求,我进入了调试模式left_join,首先在数据表上:

\n\n
debug(left_join)\nleft_join(dt_1, dt_2, na_matches = "na")\n#>  debugging in: left_join(dt_1, dt_2, na_matches = "na")\n#>  debug: {\n#>      UseMethod("left_join")\n#>  }\nBrowse[2]>  n\n#>  debug: UseMethod("left_join")\n#>  Browse[2]> n\n#>  debugging in: left_join.tbl_lazy(dt_1, dt_2, na_matches = "na")\n#>  debug: {\n#>      add_op_join(x, y, "left", by = by, sql_on = sql_on, copy = copy,\n#>          suffix = suffix, auto_index = auto_index, ...)\n#>  }\nBrowse[3]>\n#>  debug: add_op_join(x, y, "left", by = by, sql_on = sql_on, copy = copy,\n#>      suffix = suffix, auto_index = auto_index, ...)\nBrowse[3]> s\n#>  debugging in: add_op_join(x, y, "left", by = by, sql_on = sql_on, copy = copy,\n#>      suffix = suffix, auto_index = auto_index, ...)\n#>  debug: {\n#>      if (!is.null(sql_on)) {\n#>         by <- list(x = character(0), y = character(0), on = sql(sql_on))\n#>      }\n#>      else if (identical(type, "full") && identical(by, character())) {\n#>          type <- "cross"\n#>          by <- list(x = character(0), y = character(0))\n#>      }\n#>      else {\n#>          by <- common_by(by, x, y)\n#>      }\n#>      y <- auto_copy(x, y, copy = copy, indexes = if (auto_index)\n#>          list(by$y))\n#>      vars <- join_vars(op_vars(x), op_vars(y), type = type, by = by,\n#>          suffix = suffix)\n#>      x$ops <- op_double("join", x, y, args = list(vars = vars,\n#>          type = type, by = by, suffix = suffix))\n#>      x\n#>  }\nBrowse[4]> f\n#>  Joining, by = c("A", "D")\n#>  exiting from: add_op_join(x, y, "left", by = by, sql_on = sql_on, copy = copy,\n#>      suffix = suffix, auto_index = auto_index, ...)\n#>  exiting from: left_join.tbl_lazy(dt_1, dt_2, na_matches = "na")\n#>  exiting from: left_join(dt_1, dt_2, na_matches = "na")\n#>  # Source:   lazy query [?? x 4]\n#>  # Database: sqlite 3.29.0 [:memory:]\n#>    A     B     D     C\n#>    <chr> <chr> <chr> <chr>\n#>  1 a     b     d     c\n#>  2 aa    bb    NA    NA\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们看到使用选项left_join调用left_join.tbl_lazy数据表na_matches = \xe2\x80\x9cna\xe2\x80\x9d。\n然而,这后面是对其add_op_join定义的调用,其中没有任何提及na_matches

\n\n

相比之下,在数据帧上:

\n\n
left_join(df_1, df_2)\n#>  debugging in: left_join(df_1, df_2)\n#>  debug: {\n#>      UseMethod("left_join")\n#>  }\nBrowse[2]> n\n#>  debug: UseMethod("left_join")\nBrowse[2]>\n#>  debugging in: left_join.tbl_df(df_1, df_2)\n#>  debug: {\n#>      check_valid_names(tbl_vars(x))\n#>      check_valid_names(tbl_vars(y))\n#>      by <- common_by(by, x, y)\n#>      suffix <- check_suffix(suffix)\n#>      na_matches <- check_na_matches(na_matches)\n#>      y <- auto_copy(x, y, copy = copy)\n#>      vars <- join_vars(tbl_vars(x), tbl_vars(y), by, suffix)\n#>      by_x <- vars$idx$x$by\n#>      by_y <- vars$idx$y$by\n#>      aux_x <- vars$idx$x$aux\n#>      aux_y <- vars$idx$y$aux\n#>      out <- left_join_impl(x, y, by_x, by_y, aux_x, aux_y, na_matches,\n#>          environment())\n#>      names(out) <- vars$alias\n#>      reconstruct_join(out, x, vars)\n#>  }\nBrowse[3]>\n#>  debug: check_valid_names(tbl_vars(x))\nBrowse[3]>\n#>  debug: check_valid_names(tbl_vars(y))\nBrowse[3]>\n#>  debug: by <- common_by(by, x, y)\nBrowse[3]>\n#>  Joining, by = c("A", "D")\n#>  debug: suffix <- check_suffix(suffix)\nBrowse[3]>\n#>  debug: na_matches <- check_na_matches(na_matches)\nBrowse[3]>\n#>  debug: y <- auto_copy(x, y, copy = copy)\nBrowse[3]> na_matches\n#>  [1] TRUE\nBrowse[3]> f\n#>  exiting from: left_join.tbl_df(df_1, df_2)\n#>  exiting from: left_join(df_1, df_2)\n#>  # A tibble: 2 x 4\n#>    A     B     D     C\n#>    <chr> <chr> <chr> <chr>\n#>  1 a     b     d     c\n#>  2 aa    bb    NA    cc\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们看到它left_join调用left_join.tbl_df了数据框。再往下我们看到它na_matches被设置为TRUEbefore 被用作 中的参数left_join_impl。这一切都是有道理的。

\n\n

当输入?left_join.tbl_lazy文档时,会返回一个本地页面,其中join.tbl_sql {dbplyr}声明了未指定的参数(\xe2\x80\xa6):

\n\n

\xe2\x80\x9c传递给方法的其他参数,例如na_matches来控制 NA 值的匹配方式。更多信息请参见join.tbl_df\xe2\x80\x9d。

\n\n

join.tbl_df文档链接之后,它清楚地提到na_matches

\n\n

“使用 \'never\' 始终将两个 NA 或 NaN 值视为不同的值,就像数据库源的联接一样,类似于 merge(incomparables = FALSE)。默认值 \'na\' 始终将两个 NA 或 NaN 值视为不同的等于,如 merge()。用户和包作者可以通过调用 pkgconfig::set_config(\'dplyr::na_matches\' = \'never\') 来更改默认行为。

\n\n

因此,文档和数据表的代码之间似乎存在一些不一致。

\n\n

另外,@philipxy 提到了这个新闻链接,其中指出“要匹配 NA 值,请将 na_matches = \'na\' 传递给连接动词;这仅支持数据帧”。现在dt_1和df_1的类是:

\n\n
class(df_1)\n#>  [1] "tbl_df"     "tbl"        "data.frame"\nclass(dt_1)\n#>  [1] "tbl_SQLiteConnection" "tbl_dbi"              "tbl_sql"\n#>  [4] "tbl_lazy"             "tbl"\n
Run Code Online (Sandbox Code Playgroud)\n\n

我想术语“数据框”指的是类data.frametbl_df,我所说的“数据表”是其他tbl_*s 包括tbl_sqltbl_lazy。所以这个新闻链接也回答了这个问题。

\n\n

尽管如此,我认为当前的连接动词文档仍然令人困惑。它应该明确指出:

\n\n

默认值na_matches = \'na\'适用于数据框和na_matches = \'never\'(没有其他选择)数据表”。

\n\n

希望这种选择na_matches = "na"能够在不久的将来针对数据表实现。

\n