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