Перебирайте векторы символов и используйте элементы в качестве имен столбцов в лямбда-функции

#r #dplyr #purrr #rlang #non-standard-evaluation

Вопрос:

Я хотел бы перебрать вектор имен переменных с 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)))
 

Я ожидал увидеть список из двух данных.кадров, как в

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

Есть ли способ использовать .x переменные в качестве имен столбцов в data.frames в лямбда-функции?

Я думаю, что это может быть как-то связано с маскировкой данных и нестандартной оценкой, и я подозреваю rlang , что это может быть полезно здесь, но я не знаком с предметом. Спасибо

Комментарии:

1. Очень интересный ГЕДЕСБФ. Не могли бы вы, пожалуйста, уточнить, в какой ситуации мы могли бы использовать эту процедуру. В чем заключается стоящая за этим идея. Я действительно хочу знать?? Спасибо!

2. Привет, @TarJae, спасибо. У меня есть набор данных с более чем 300 столбцами, включая dummified группирующую переменную, распределенную по нескольким столбцам, и несколько других data переменных. Я хотел бы summarise(across(data_variables, ~something) для каждой группы, определенной dummy_x==1 , dummy_y==1 , чтобы вектор c("dummy_1", "dummy2"...) мог помочь заранее определить переменные. Вопрос был направлен на то, чтобы понять процедуру, как и в первом ответе акруна, что могло бы облегчить задачу.

3. Фактическая процедура немного сложнее, но я использовал минимальный репрекс для точной vector of characters as variable names проблемы

Ответ №1:

Это струны. Нам нужно преобразовать в sym bol и оценить ( !! )

 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 ...
 

-тестирование с ожидаемым ОП

 > 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(.))))
 

Комментарии:

1. Потрясающе, спасибо. Разве нельзя использовать {{ }} вместо !! этого ?

2. @GuedesBF, который используется с некотируемыми входными данными в функции. Ваши входные данные-это строки

3. То {{}} делает похожее на enquo с ||

4. хорошо, спасибо. Я новичок в этом, и все еще очень смущен, когда дело доходит до rlang нестандартной оценки

5. @GuedesBF например. при создании функции f1 <- function(dat, colnm) dat %>% 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, и, возможно, вместо этого мне следовало бы узнать о !! versus !!! versus…, но, во всяком случае, я узнал

  • Такие функции, как mean «первый класс», я могу присвоить символ, представляющий функцию (например, mean ), переменной (например, aggregate ), а затем использовать переменную как функцию ( aggregate(...) ).
  • Операторы типа < на самом деле являются функциями и lhs < rhs могут быть записаны как `<`(lhs, rhs) (и чтобы написать это, мне пришлось научиться писать обратные ссылки в markdown!)

Более прозаично

  • FUN Аргумент to lapply() принимает аргументы в дополнение к повторяемому аргументу. Они могут быть именованными или безымянными, при этом применяются обычные правила сопоставления аргументов (сначала совпадают по имени, затем по должности).
  • [[ может использоваться для подмножества по имени, избегая необходимости в 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
)
 

Комментарии:

1. подмножество index по позиции внутри данных.подмножество кадров с [index[i]] помощью действительно просто и полезно. Спасибо.

2. Не беспокойтесь, можно упростить до: setNames( lapply( index, function(x){ mu <- mean(df[,x], na.rm = TRUE) df[df[ , x, drop = TRUE] > mu,] } ), index )

Ответ №4:

Вы можете использовать .data

 library(dplyr)
library(purrr)

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

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