前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「r」dplyr 里的 join 与 base 里的 merge 存在差异

「r」dplyr 里的 join 与 base 里的 merge 存在差异

作者头像
王诗翔呀
发布2020-11-20 10:32:13
1.6K0
发布2020-11-20 10:32:13
举报
文章被收录于专栏:优雅R

今天在使用连接操作时发现:虽然都是合并操作函数,dplyr 包里的 *_join() 和基础包里面的 merge() 存在差异,不同的数据结构,结果也会存在偏差。

构造数据集

下面是一个可重复的例子,构造两个数据集,一个是基于 data.frame 的列表,另一个是就要 data.table 的列表:

代码语言:javascript
复制
x <- list(
  a = data.frame(r1 = c("S1", "S2"), r3 = c("S2", "S1")),
  b = data.frame(r1 = c("S1", "S2"), r5 = c("S2", "S1")),
  c = data.frame(r2 = c("S1", "S2"), r4 = c("S2", "S1")),
  d = data.frame(r4 = c("S1", "S2"), r5 = c("S2", "S1"))
)

str(x)
#> List of 4
#>  $ a:'data.frame':   2 obs. of  2 variables:
#>   ..$ r1: chr [1:2] "S1" "S2"
#>   ..$ r3: chr [1:2] "S2" "S1"
#>  $ b:'data.frame':   2 obs. of  2 variables:
#>   ..$ r1: chr [1:2] "S1" "S2"
#>   ..$ r5: chr [1:2] "S2" "S1"
#>  $ c:'data.frame':   2 obs. of  2 variables:
#>   ..$ r2: chr [1:2] "S1" "S2"
#>   ..$ r4: chr [1:2] "S2" "S1"
#>  $ d:'data.frame':   2 obs. of  2 variables:
#>   ..$ r4: chr [1:2] "S1" "S2"
#>   ..$ r5: chr [1:2] "S2" "S1"

x2 <- list(
  a = data.table::data.table(r1 = c("S1", "S2"), r3 = c("S2", "S1")),
  b = data.table::data.table(r1 = c("S1", "S2"), r5 = c("S2", "S1")),
  c = data.table::data.table(r2 = c("S1", "S2"), r4 = c("S2", "S1")),
  d = data.table::data.table(r4 = c("S1", "S2"), r5 = c("S2", "S1"))
)

str(x2)
#> List of 4
#>  $ a:Classes 'data.table' and 'data.frame':  2 obs. of  2 variables:
#>   ..$ r1: chr [1:2] "S1" "S2"
#>   ..$ r3: chr [1:2] "S2" "S1"
#>   ..- attr(*, ".internal.selfref")=<externalptr> 
#>  $ b:Classes 'data.table' and 'data.frame':  2 obs. of  2 variables:
#>   ..$ r1: chr [1:2] "S1" "S2"
#>   ..$ r5: chr [1:2] "S2" "S1"
#>   ..- attr(*, ".internal.selfref")=<externalptr> 
#>  $ c:Classes 'data.table' and 'data.frame':  2 obs. of  2 variables:
#>   ..$ r2: chr [1:2] "S1" "S2"
#>   ..$ r4: chr [1:2] "S2" "S1"
#>   ..- attr(*, ".internal.selfref")=<externalptr> 
#>  $ d:Classes 'data.table' and 'data.frame':  2 obs. of  2 variables:
#>   ..$ r4: chr [1:2] "S1" "S2"
#>   ..$ r5: chr [1:2] "S2" "S1"
#>   ..- attr(*, ".internal.selfref")=<externalptr>

从存储的信息来看,这两个列表是没有任何差异的。

相同的数据,不同的操作函数存在差异

在进行连接操作时,我们会发现 dplyr 的结果会报错!

代码语言:javascript
复制
purrr::reduce(x, dplyr::full_join)
#> Joining, by = "r1"
#> Error: `by` must be supplied when `x` and `y` have no common variables.
#> ℹ use by = character()` to perform a cross-join.
purrr::reduce(x, merge)
#>   r5 r4 r1 r3 r2
#> 1 S1 S2 S2 S1 S1
#> 2 S2 S1 S1 S2 S2

看起来似乎有点不可理喻,但实际上上面我构造的数据集是有点特别的:前 2 个子集和第 3 个子集是没有可以连接的列的,第 4 个子集起到桥梁作用。所以使用 dplyr 提供的连接函数报错是正常的,但有意思的是,基础包提供的 merge() 函数可以完成连接操作,真是优秀(感兴趣的朋友可以看下测试下 merge 函数源代码)!

代码语言:javascript
复制
x
#> $a
#>   r1 r3
#> 1 S1 S2
#> 2 S2 S1
#> 
#> $b
#>   r1 r5
#> 1 S1 S2
#> 2 S2 S1
#> 
#> $c
#>   r2 r4
#> 1 S1 S2
#> 2 S2 S1
#> 
#> $d
#>   r4 r5
#> 1 S1 S2
#> 2 S2 S1

data.table 不支持上述 merge 连接

我们可以再看下基于 data.table 构造的数据集结果:

代码语言:javascript
复制
purrr::reduce(x2, dplyr::full_join)
#> Joining, by = "r1"
#> Error: `by` must be supplied when `x` and `y` have no common variables.
#> ℹ use by = character()` to perform a cross-join.
purrr::reduce(x2, merge)
#> Error in merge.data.table(out, elt, ...): Elements listed in `by` must be valid column names in x and y

两个函数操作都报错了,说明对 data.table 是不适用的。本质上是 data.table 体格的泛型函数不支持类似基础包中的操作。

如何编写代码支持对上述数据集的连接操作?

一般工作情况下,不同的数据子集都存在可以连接的列,所以无论上述哪种方法都可以胜任工作。但特殊情况下,即类似我上述构造的数据集:数据子集不是所有但两两之间都存在共有的列,但按照一定的顺序确实能够将其合并。

下面给出探索后的解决代码:

代码语言:javascript
复制
to_join <- x2[[1]]
be_join <- x2[-1]

# https://stackoverflow.com/questions/30542128/circular-shift-of-vector-by-distance-n
shifter <- function(x, n = 1) {
  if (n == 0) x else c(tail(x, -n), head(x, n))
}

while (length(be_join) > 0) {
  col_exist <- colnames(be_join[[1]]) %in% colnames(to_join)
  if (any(col_exist)) {
    to_join <- merge(to_join, be_join[[1]], by = colnames(be_join[[1]])[col_exist])
    be_join[[1]] <- NULL
  } else {
    be_join <- shifter(be_join)
  }
}

上述代码中执行下面的操作:

  1. 构造两个集合 to_joinbe_jointo_join 初始化为数据集的第一个子集,而 be_join 为其他子集。
  2. 如果 be_join 不为空,进行如下的循环:
    1. 如果存在,则将这个子集和 to_join 按共同列合并
    2. 如果不存在,使用循环位移一位,将当前 be_join 的第 2 个子集移动为 第 1 个。
    3. 检查 be_join 第一个子集的列与 to_join 存在共同列
    4. 等待循环结束

我们可以查看结果:

代码语言:javascript
复制
to_join[, c("r1", "r2", "r3", "r4", "r5")]
#>    r1 r2 r3 r4 r5
#> 1: S1 S2 S2 S1 S2
#> 2: S2 S1 S1 S2 S1

对比下面结果是相同的(虽然顺序颠倒了)。

代码语言:javascript
复制
purrr::reduce(x, merge)[, c("r1", "r2", "r3", "r4", "r5")]
#>   r1 r2 r3 r4 r5
#> 1 S2 S1 S1 S2 S1
#> 2 S1 S2 S2 S1 S2

更新

在后面的一些使用过程中发现基础包的 merge() 函数在进行连接操作时会输出有问题的结果,所以建议使用的小伙伴仔细检查结果。下面更新了一个用于合并的函数:

代码语言:javascript
复制
reduceG <- function(G) {
  # Reduce elements of G if at least two elements
  # contain common column names
  # G >= 2 elements here
  if (length(G) < 2) {
    return(G)
  }
  cnames <- purrr::map(G, colnames)
  check_list <- combn(seq_along(cnames), 2, simplify = FALSE)
  common <- purrr::map(check_list, ~ intersect(cnames[[.[1]]], cnames[[.[2]]]))

  # Index to reduce
  ri <- purrr::map_lgl(common, ~ length(.) != 0)
  if (any(ri)) {
    purrr::map2(check_list[ri], common[ri], .f = function(x, y) {
      if (!is.na(G[x[1]]) & !is.na(G[x[2]])) {
        # Update global G in reduceG
        G[[min(x)]] <<- merge(G[[x[1]]], G[[x[2]]], by = y)
        # to make sure the data is removed and the index
        # is kept to avoid "subscript out of bounds" error
        G[[max(x)]] <<- NA
      }
    })
    # Remove elements set to NA
    G <- G[!is.na(G)]
    return(reduceG(G))
  } else {
    return(G)
  }
}

测试结果:

代码语言:javascript
复制
reduceG(x)[[1]]
#>   r5 r1 r3 r4 r2
#> 1 S1 S2 S1 S2 S1
#> 2 S2 S1 S2 S1 S2
reduceG(x2)[[1]]
#>    r5 r1 r3 r4 r2
#> 1: S1 S2 S1 S2 S1
#> 2: S2 S1 S2 S1 S2
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 优雅R 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构造数据集
  • 相同的数据,不同的操作函数存在差异
  • data.table 不支持上述 merge 连接
  • 如何编写代码支持对上述数据集的连接操作?
  • 更新
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档