【问题标题】:Use an apply function with user defined function that adds variables to data frame将应用函数与用户定义的函数一起使用,将变量添加到数据框
【发布时间】:2018-12-23 05:53:36
【问题描述】:

我已经定义了一个函数,它将在数据框中动态创建新变量。对于此函数,输入是一个字符串,然后将其与其他字符串一起粘贴以创建数据框中已经存在的变量名称,然后在 mutate 中使用 case_when 进行比较。该函数的输出是数据帧,其末尾附加了新变量。我想将此函数应用于输入向量,并在数据框中创建多个新列。我已经使用 iris 数据集创建了一个与我正在做的非常相似的函数。

func <- function(x) {
  a <- paste0("Sepal.", x)
  b <- paste0("Petal.", x)
  x <- iris %>% 
    mutate(
      !!(paste0("Compare.", x)) :=
        case_when(
          iris[[a]] > iris[[b]] ~ "Sepal",
          iris[[a]] < iris[[b]] ~ "Petal",
          TRUE ~ "Equal"
      )
    )
  return(x)
}

inputVector <- c("Length", "Width")

我想将此函数应用于 inputVector 并返回一个数据帧,其中包含两个新变量,最好没有循环。我正在寻找使用类似的东西

iris <- lapply(inputVector, func)

但这会返回一个包含两个数据框的列表。我也知道我可以在func 中使用&lt;&lt;-,但我想避免这种情况。

【问题讨论】:

  • 小心,你运行的是"SepalLength" &gt; "PetalLength",而不是Sepal.Length &gt; Petal.Length。除了点之外,您需要将字符串转换为引号并取消引用它们以引用变量而不是比较字符串本身。
  • 已更新。感谢您指出这一点。我在实际工作中使用了 base r,但这个例子做得太快而错过了。

标签: r dplyr apply


【解决方案1】:

您想要构建输出的方式略有变化 -

func <- function(x) {
  a <- paste0("Sepal", x)
  b <- paste0("Petal", x)
  x1 <- iris %>% 
    mutate(
      !!(paste0("Compare.", x)) :=
        case_when(
          a > b ~ "Sepal",
          a < b ~ "Petal",
          TRUE ~ "Equal"
        )
    )
  return(x1[[paste0('Compare.',x)]])
}

inputVector <- c("Length", "Width")
op <- iris
op[,paste0('Compare.',inputVector)] <- lapply(inputVector, func)

输出

> head(op)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Compare.Length Compare.Width
1          5.1         3.5          1.4         0.2  setosa          Sepal         Sepal
2          4.9         3.0          1.4         0.2  setosa          Sepal         Sepal
3          4.7         3.2          1.3         0.2  setosa          Sepal         Sepal
4          4.6         3.1          1.5         0.2  setosa          Sepal         Sepal
5          5.0         3.6          1.4         0.2  setosa          Sepal         Sepal
6          5.4         3.9          1.7         0.4  setosa          Sepal         Sepal

【讨论】:

  • 谢谢!这就是我在您发布我正在回答我自己的问题之前刚刚想到的。你知道你更新数据框的方式是否比我在下面使用do.call 所做的更好吗?
  • @jamesguy0121 我会说 OP 中的输出格式与我现在的答案更一致。但是您始终可以使用colnames() 来解决此问题,并使用do.call()。我认为这两种解决方案都很好。您可以尝试对这两种解决方案进行计时,看看哪种解决方案对您来说更快,然后选择最好的解决方案
  • 您知道,mutate/transmute 返回 tibbles,并且由于变量在 func 中命名,因此在 do.call(cbind, ....) 中调用时会保留该名称。我也为我的案例计时了它们,它们在 0.1 时基本相同。我接受你的回答,因为我觉得接受我对我的问题的回答很奇怪。 :) 谢谢!
  • @jamesguy0121 我很高兴能帮上忙。干杯!
【解决方案2】:

一个更简单的选择是只生成函数中的新列,方法是用transmute 替换mutate,使用map_dfc 将它们迭代并加入一个数据框,然后使用bind_cols 将它们添加到原文:

library(tidyverse)

func <- function(x) {
  a <- sym(paste0("Sepal.", x))    # these need to be quosures to refer to variables
  b <- sym(paste0("Petal.", x))
  iris %>% transmute(
      !!paste0("Compare.", x) := case_when(
          !!a > !!b ~ "Sepal",    # unquote quosures
          !!a < !!b ~ "Petal",
          TRUE ~ "Equal"
      )
    )
}

inputVector <- c("Length", "Width")

iris %>% bind_cols(map_dfc(inputVector, func)) %>% head()
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species Compare.Length
#> 1          5.1         3.5          1.4         0.2  setosa          Sepal
#> 2          4.9         3.0          1.4         0.2  setosa          Sepal
#> 3          4.7         3.2          1.3         0.2  setosa          Sepal
#> 4          4.6         3.1          1.5         0.2  setosa          Sepal
#> 5          5.0         3.6          1.4         0.2  setosa          Sepal
#> 6          5.4         3.9          1.7         0.4  setosa          Sepal
#>   Compare.Width
#> 1         Sepal
#> 2         Sepal
#> 3         Sepal
#> 4         Sepal
#> 5         Sepal
#> 6         Sepal

稍微更优雅的方法是构造函数以获取向量,将所有迭代移动到内部。您可以使用上面的方法,或者只生成向量并在有多个时将它们组装成一个数据框:

func2 <- function(x) {
  columns <- map_dfc(x, function(y){
    a <- paste0("Sepal.", y)
    b <- paste0("Petal.", y)
    column <- list(case_when(
      iris[[a]] > iris[[b]] ~ "Sepal",    # base notation is simpler than quosures
      iris[[a]] < iris[[b]] ~ "Petal",
      TRUE ~ "Equal"
    ))
    names(column) <- paste0("Compare.", y)
    column
  })
  iris %>% bind_cols(columns)
}

func2(inputVector) %>% tail()
#>     Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
#> 145          6.7         3.3          5.7         2.5 virginica
#> 146          6.7         3.0          5.2         2.3 virginica
#> 147          6.3         2.5          5.0         1.9 virginica
#> 148          6.5         3.0          5.2         2.0 virginica
#> 149          6.2         3.4          5.4         2.3 virginica
#> 150          5.9         3.0          5.1         1.8 virginica
#>     Compare.Length Compare.Width
#> 145          Sepal         Sepal
#> 146          Sepal         Sepal
#> 147          Sepal         Sepal
#> 148          Sepal         Sepal
#> 149          Sepal         Sepal
#> 150          Sepal         Sepal

【讨论】:

  • 啊,太好了!谢谢你。当我问这个问题时,这更像是我的设想。我已经使用 R 几年了,但我只是潜入 tidyverse,所以还有很多未知数。特别是与qusures和非标准评估。我肯定会玩这个。谢谢。
【解决方案3】:

多玩一点,我确实找到了这个问题的答案。我没有让func 输出数据帧,而是将其更改为仅输出向量,然后使用do.calllapply

func <- function(x) {
  a <- paste0("Sepal", x)
  b <- paste0("Petal", x)
  x <- iris %>% 
    transmute(
      !!(paste0("Compare.", x)) :=
        case_when(
          a > b ~ "Sepal",
          a < b ~ "Petal",
          TRUE ~ "Equal"
      )
    )
  return(x)
}

do.call(cbind, c(iris, lapply(inputVector, func)))

我绝对愿意接受替代解决方案,因为我认为这可能不是最好的。

【讨论】:

    【解决方案4】:

    您的结果是两个数据框(如您所示):

    • iris[[1]] 是第一个数据帧
    • iris[[2]] 是第二个数据帧。

    您可以使用merge 将两个数据框合并为一个,如下所示:

    comp.iris <- lapply(inputVector, func) 
    comp.iris <- merge(comp.iris[[1]], comp.iris[[2]], sort = FALSE)
    

    希望对你有帮助。

    【讨论】:

    • 我没有按照您的要求进行操作,并且没有成功。看起来您只是将第一个元素合并到 x 的元素。你能详细说明一下吗?
    • 啊,好的,我现在明白了。这确实有效,但在我的实际用例中,我创建的变量比这两个变量要多得多,因此不太实用。谢谢。
    • 我明白你的意思。那时你可以考虑替代方案。祝你好运!
    猜你喜欢
    • 1970-01-01
    • 2020-10-01
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多