【问题标题】:Loop over character vectors and use elements as column names within lambda function循环字符向量并将元素用作 lambda 函数中的列名
【发布时间】:2021-10-18 21:50:01
【问题描述】:

我想用purrr 循环一个变量名向量,然后用dplyr 使用函数内部的变量,如下代码:

library(dplyr)
library(purrr)

#creating index
index<-c('Sepal.Length', 'Sepal.Width')

#mapping over index with lambda function
map(index, ~iris %>% filter (.x > mean(.x)))

我期待看到两个 data.frames 的列表,如

list(Sepal.Length = iris %>% filter (Sepal.Length > mean(Sepal.Length)),
     Sepal.Width = iris %>% filter (Sepal.Width > mean(Sepal.Width)))

有没有办法在 lambda 函数的 data.frames 中使用 .x 变量作为列名?

我认为这可能与数据屏蔽和非标准评估有关,我怀疑rlang 在这里可能会有所帮助,但我对这个主题并不熟悉。 谢谢

【问题讨论】:

  • 非常有趣的 GuedesBF。您能否详细说明在何种情况下我们可以使用此程序。背后的想法是什么。我真的很想知道??谢谢!
  • 嗨,@TarJae,谢谢。我有一个包含 300 多列的数据集,包括分布在多列上的 dummified 分组变量,以及其他几个 data 变量。我想为dummy_x==1dummy_y==1 定义的每个组添加summarise(across(data_variables, ~something),因此c("dummy_1", "dummy2"...) 的向量可以帮助预先确定变量。该问题旨在像 akrun 的第一个答案那样理解程序,这可以使事情变得更容易。
  • 实际的过程有点复杂,但我使用了一个最小的reprex来准确的vector of characters as variable names问题

标签: r dplyr purrr rlang non-standard-evaluation


【解决方案1】:

那些是字符串。我们需要转换为symbol 并评估 (!!)

library(purrr)
library(dplyr)
out <- map(index, ~iris %>%
       filter (!! rlang::sym(.x) > mean(!! rlang::sym(.x))))
names(out) <- index

-输出

> str(out)
List of 2
 $ Sepal.Length:'data.frame':   70 obs. of  5 variables:
  ..$ Sepal.Length: num [1:70] 7 6.4 6.9 6.5 6.3 6.6 5.9 6 6.1 6.7 ...
  ..$ Sepal.Width : num [1:70] 3.2 3.2 3.1 2.8 3.3 2.9 3 2.2 2.9 3.1 ...
  ..$ Petal.Length: num [1:70] 4.7 4.5 4.9 4.6 4.7 4.6 4.2 4 4.7 4.4 ...
  ..$ Petal.Width : num [1:70] 1.4 1.5 1.5 1.5 1.6 1.3 1.5 1 1.4 1.4 ...
  ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ Sepal.Width :'data.frame':   67 obs. of  5 variables:
  ..$ Sepal.Length: num [1:67] 5.1 4.7 4.6 5 5.4 4.6 5 4.9 5.4 4.8 ...
  ..$ Sepal.Width : num [1:67] 3.5 3.2 3.1 3.6 3.9 3.4 3.4 3.1 3.7 3.4 ...
  ..$ Petal.Length: num [1:67] 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.5 1.5 1.6 ...
  ..$ Petal.Width : num [1:67] 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.1 0.2 0.2 ...
  ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

-使用 OP 的预期测试

> expected <- list(Sepal.Length = iris %>% filter (Sepal.Length > mean(Sepal.Length)),
+      Sepal.Width = iris %>% filter (Sepal.Width > mean(Sepal.Width)))
> 
> identical(out, expected)
[1] TRUE

或带有cur_data()的子集

map(index, ~ iris %>%
     filter(cur_data()[[.x]] > mean(cur_data()[[.x]])))

或者使用across或者if_all,直接取字符串

map(index, ~ iris %>%
           filter(across(all_of(.x), ~ . > mean(.))))

【讨论】:

  • 太棒了,谢谢。是不是可以用{{ }}代替!!
  • @GuedesBF 与函数中未引用的输入一起使用。您的输入是字符串
  • {{}}enquo|| 类似
  • 好的,谢谢。我是新手,对rlang和非标准评估仍然很困惑
  • @GuedesBF 例如。当你创建一个函数f1 &lt;- function(dat, colnm) dat %&gt;% summarise(Mean = mean({{colnm}})); f1(iris, Sepal.Length)。输入列名不是字符串,而在您的情况下,它是一个字符向量,我们在 map 中循环
【解决方案2】:

类似的解决方案

map(index, function(i, x) filter(x, x[[i]] > mean(x[[i]])), iris)

似乎平衡了 dplyr 的功能(例如,使用 NA 值做明智的事情),而没有过多的非标准评估负担,同时还突出了一些有用的 R 习惯用法,例如使用 [[ 按名称提取列当非标准评估变得繁琐时,可能会有用。

就我个人而言,我会使用lapply() 而不是map(),这样我就不必学习另一个包了。如果我想要命名列表元素,我会“预先”做,而不是事后添加

names(index) <- index
lapply(index, function(i, x) filter(x, x[[i]] > mean(x[[i]])), iris)

或许

lapply(setNames(nm=index), function(i, x) filter(x, x[[i]] > mean(x[[i]])), iris)

如果这是我的代码中的常见场景(或者即使这是一次性的),我可能会编写一个简短的辅助函数

filter_greater_than_column_mean <- function(i, x)
    dplyr::filter( x, x[[i]] > mean(x[[i]]) )

lapply(index, filter_greater_than_column_mean, iris)

如果我以自己的方式成为一个外行并试图变得更一般,我可能会变得过于复杂

filter_by_column_mean <- function(i, x, op = `>`) {
    idx <- op(x[[i]], mean(x[[i]]))
    dplyr::filter(x, idx)
}
lapply(index, filter_by_column_mean, iris)
lapply(index, filter_by_column_mean, iris, `<=`)

甚至

filter_by_column <- function(i, x, op = `>`, aggregate = mean) {
    idx <- op(x[[i]], aggregate(x[[i]]))
    dplyr::filter(x, idx)
}
lapply(index, filter_by_column, iris, op = `<=`)
lapply(index, filter_by_column, iris, `<=`, median)

既然我没有使用非标准评估,我可能会瞄准基本 R 的subset(),它也可以用NA 做一些明智的事情。所以

filter_by_column <- function(i, x, aggregate = mean, op = `>`) {
    idx <- op(x[[i]], aggregate(x[[i]]))
    subset(x, idx)
}

我知道这意味着我已经学到了很多关于基础 R 的东西,也许我应该学习 !!!!! 与 ...,但无论如何我已经学会了

  • mean 这样的函数是“第一类”,我可以将表示函数的符号(例如mean)分配给一个变量(例如aggregate),然后将该变量用作函数(@987654339 @)。
  • &lt;这样的操作符其实是函数,而lhs &lt; rhs可以写成`&lt;`(lhs, rhs)(而且要写,我必须学会如何在markdown中写反引号!)

比较平淡

  • lapply()FUN 参数除了被迭代的参数外还接受参数。这些可以命名或不命名,并应用通常的参数匹配规则(首先按名称匹配,然后按位置匹配)。
  • [[ 可用于按名称进行子集化,从而避免需要 seq_along() 或其他依赖数字索引的不太健壮的操作。

【讨论】:

    【解决方案3】:

    基础R:

    index<-c('Sepal.Length', 'Sepal.Width')
    df <- iris
    
    setNames(
      lapply(
        seq_along(index),
        function(i){
           mu <- mean(df[,index[i]], na.rm = TRUE)
           df[df[,index[i],drop = TRUE] > mu, ]
        }
      ),
      index
    )
    

    【讨论】:

    • index 在data.frame 子集内的位置设置为子集,使用[index[i]] 非常简单且有用。谢谢。
    • 不用担心,可以简化为:setNames( lapply( index, function(x){ mu &lt;- mean(df[,x], na.rm = TRUE) df[df[ , x, drop = TRUE] &gt; mu,] } ), index )
    【解决方案4】:

    你可以使用.data -

    library(dplyr)
    library(purrr)
    
    index<-c('Sepal.Length', 'Sepal.Width')
    
    map(index, ~iris %>% filter (.data[[.x]] > mean(.data[[.x]])))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-10
      • 2015-03-24
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多