【问题标题】:Dplyr join on by=(a = b), where a and b are variables containing strings?dplyr join on by=(a = b),其中a和b是包含字符串的变量?
【发布时间】:2015-04-08 13:27:19
【问题描述】:

我正在尝试使用 dplyr 执行两个表的内部连接,但我认为我被非标准评估规则绊倒了。当使用 by=("a" = "b") 参数时,当 "a" 和 "b" 是实际字符串时,一切都会按预期工作。这是一个有效的玩具示例:

library(dplyr)
data(iris)

inner_join(iris, iris, by=c("Sepal.Length" = "Sepal.Width"))

但是假设我将 inner_join 放入一个函数中:

library(dplyr)
data(iris)

myfn <- function(xname, yname) {
    data(iris)
    inner_join(iris, iris, by=c(xname = yname))
}

myfn("Sepal.Length", "Sepal.Width")

这会返回以下错误:

Error: cannot join on columns 'xname' x 'Sepal.Width': index out of bounds

我怀疑我可以做一些花哨的表达,去解析,引用或取消引用来完成这项工作,但我对这些细节有点模糊。

【问题讨论】:

  • Hadley 称其为 "non-standard evaluation" (NSE)
  • 似乎不像 NSE 那样以不同的方式提供“by.x”和“by.y”列名。 by-argument 变成 c("Sepal.Length" = "Sepal.Width") ,所以 by.xmerge 的参数变成了真正的 R 名称。事实上,在我看来,它几乎与 NSE 正好相反。

标签: r dplyr


【解决方案1】:

你可以使用

myfn <- function(xname, yname) {
    data(iris)
    inner_join(iris, iris, by=setNames(yname, xname))
}

?inner_join 文档中的建议语法

by = c("a"="b")   # same as by = c(a="b")

有点误导,因为这两个值都不是正确的字符值。您实际上创建了一个命名字符向量。动态设置等号左边的值不同于右边的值。您可以使用setNames()动态设置向量的名称。

【讨论】:

  • 请注意,通过使用setNames,参数的顺序与inner_join 中的原始用法相反。要具有相同的参数顺序,即首先xname 然后yname,您可以使用by=setNames(nm=xname, yname)
【解决方案2】:

我知道我迟到了,但是怎么样:

myfn <- function(byvar) {
  data(iris)
  inner_join(iris, iris, by=byvar)
}

这样你就可以做你想做的事了:

myfn(c("Sepal.Length"="Sepal.Width"))

【讨论】:

  • 我认为这与 MrFlick 的答案没有太大的不同。
  • 它们非常相似。唯一的区别是当您通过多个维度加入时。我认为使用myfn(c("a" = "b", "c" = "d")) 比使用myfn(c("a", "c"), c("b", "d")) 更清晰,但我猜这只是个人喜好问题。
  • 是的。我明白你的意思。您正在使用多余的引号。你可以用myfn(c(a = "b", c = "d")) 打电话给你,在我看来这会更清楚,因为它使用通常的名称输入,更不用说需要换档键的字符更少了。
【解决方案3】:

我喜欢 MrFlick 的回答和 fber 的附录,但我更喜欢 structure。对我来说setNames 感觉像是在管道的末端,而不是在运行中的构造函数。另一方面,setNamesstructure 都允许在函数调用中使用变量。

myfn <- function(xnames, ynames) {
  data(iris)
  inner_join(iris, iris, by = structure(names = xnames, .Data = ynames))
}

x <- "Sepal.Length"

myfn(x, "Sepal.Width")

命名向量参数会在这里遇到问题:

myfn <- function(byvars) {
  data(iris)
  inner_join(iris, iris, by = byvars)
}

x <- "Sepal.Length"

myfn(c(x = "Sepal.Width"))

不过,您可以通过在函数调用中使用 setNamesstructure 来解决这个问题。

【讨论】:

    【解决方案4】:

    我面临与@Peter 几乎相同的挑战,但需要同时传递多组不同的by = 连接参数。我选择使用 tidyverse 包中的map() 函数purrr

    这是我使用的 tidyverse 的子集。

    library(magrittr)
    library(dplyr)
    library(rlang)
    library(purrr)
    

    首先,我将myfn 改编为map() 用于Peter 发布的案例。 42 的评论和 Felipe Gerard 的回答清楚地表明 by 参数可以采用命名向量。 map() 需要一个要迭代的列表。

        myfn_2 <- function(xname, yname) {
          by_names <- list(setNames(nm = xname, yname ))
    
          data(iris)
    
          # map() returns a single-element list. We index to retrieve dataframe.
    
          map( .x = by_names, 
               .f = ~inner_join(x = iris, 
                                y = iris, 
                                by = .x)) %>% 
            `[[`(1)
        }
    
    myfn_2("Sepal.Length", "Sepal.Width")
    

    我发现我在构建函数时不需要quo_name() / !!

    然后,我调整了函数以获取by 参数列表。对于by_grps 中的每个by_i,我们可以扩展xy 以添加要加入的命名值。

    by_grps <- list(  by_1 = list(x = c("Sepal.Length"), y = c("Sepal.Width")), 
                      by_2 = list(x = c("Sepal.Width"), y = c("Petal.Width"))
                    )
    
    myfn_3 <- function(by_grps_list, nm_dataset) {
      by_named_vectors_list <- lapply(by_grps_list, 
                                      function(by_grp) setNames(object = by_grp$y,
                                                                nm = by_grp$x))
      map(.x = by_named_vectors_list, 
          .f = ~inner_join(nm_dataset, nm_dataset, by = .x))
    }
    
    myfn_3(by_grps, iris)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      • 2014-03-29
      • 2014-11-06
      • 2017-01-05
      • 1970-01-01
      • 2010-12-21
      相关资源
      最近更新 更多