mutate: использовать динамические имена переменных
Я хочу использовать dplyr mutate()
для создания нескольких новых столбцов в фрейме данных. Имена столбцов и их содержимое должны быть динамически сгенерированы.
Пример данных из диафрагмы:
require(dplyr)
data(iris)
iris <- tbl_df(iris)
Я создал функцию для изменения моих новых столбцов из переменной Petal.Width
:
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
df <- mutate(df, varname = Petal.Width * n) ## problem arises here
df
}
Теперь я создаю цикл для построения моих столбцов:
for(i in 2:5) {
iris <- multipetal(df=iris, n=i)
}
Однако, поскольку mutate считает, что varname является литеральным именем переменной, цикл создает только одну новую переменную (называемую varname) вместо четырех (называемый petal.2 - petal.5).
Как я могу получить mutate()
для использования моего динамического имени в качестве имени переменной?
Ответы
Ответ 1
Поскольку вы существенно строите имя переменной как символьное значение, имеет смысл выполнять присваивание с использованием стандартной индексации data.frame, которая допускает символьные значения для имен столбцов. Например:
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
df[[varname]] <- with(df, Petal.Width * n)
df
}
Функция mutate
позволяет очень легко именовать новые столбцы с помощью именованных параметров. Но это предполагает, что вы знаете имя при вводе команды. Если вы хотите динамически указать имя столбца, вам также необходимо создать именованный аргумент.
версия dplyr> = 0.7
Последняя версия dplyr (0.7) делает это, используя :=
для динамического присвоения имен параметров. Вы можете написать свою функцию как:
# --- dplyr version 0.7+---
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
mutate(df, !!varname := Petal.Width * n)
}
Для получения дополнительной информации см. Документацию доступной формы vignette("programming", "dplyr")
.
dplyr (> = 0,3 и <0,7)
Чуть более ранняя версия dplyr (> = 0,3 <0,7) поощряла использование альтернатив "стандартной оценки" для многих функций. См. Нестандартную оценочную виньетку для получения дополнительной информации (vignette("nse")
).
Итак, здесь ответ заключается в том, чтобы использовать mutate_()
вместо mutate()
и сделать:
# --- dplyr version 0.3-0.5---
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
varval <- lazyeval::interp(~Petal.Width * n, n=n)
mutate_(df, .dots= setNames(list(varval), varname))
}
dplyr <0,3
Обратите внимание, что это также возможно в более старых версиях dplyr, которые существовали, когда вопрос был задан изначально. Требует осторожного использования quote
и setName
:
# --- dplyr versions < 0.3 ---
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
pp <- c(quote(df), setNames(list(quote(Petal.Width * n)), varname))
do.call("mutate", pp)
}
Ответ 2
В новой версии dplyr
(0.6.0
в ожидании в апреле 2017 года) мы также можем выполнить присвоение (:=
) и передать переменные в виде имен столбцов путем unquoting (!!
), чтобы не оценивать его
library(dplyr)
multipetalN <- function(df, n){
varname <- paste0("petal.", n)
df %>%
mutate(!!varname := Petal.Width * n)
}
data(iris)
iris1 <- tbl_df(iris)
iris2 <- tbl_df(iris)
for(i in 2:5) {
iris2 <- multipetalN(df=iris2, n=i)
}
Проверка вывода на основе @MrFlick multipetal
, примененного к 'iris1'
identical(iris1, iris2)
#[1] TRUE
Ответ 3
После большого количества проб и ошибок я нашел шаблон UQ(rlang::sym("some string here")))
действительно полезный для работы со строками и глаголами dplyr. Кажется, он работает во многих неожиданных ситуациях.
Вот пример с mutate
. Мы хотим создать функцию, которая объединяет два столбца, в которых вы передаете функцию как имена столбцов, так и строки. Мы можем использовать этот шаблон вместе с оператором присваивания :=
, чтобы сделать это.
## Take column 'name1', add it to column 'name2', and call the result 'new_name'
mutate_values <- function(new_name, name1, name2){
mtcars %>%
mutate(UQ(rlang::sym(new_name)) := UQ(rlang::sym(name1)) + UQ(rlang::sym(name2)))
}
mutate_values('test', 'mpg', 'cyl')
Шаблон работает и с другими функциями dplyr
. Здесь filter
:
## filter a column by a value
filter_values <- function(name, value){
mtcars %>%
filter(UQ(rlang::sym(name)) != value)
}
filter_values('gear', 4)
Или arrange
:
## transform a variable and then sort by it
arrange_values <- function(name, transform){
mtcars %>%
arrange(UQ(rlang::sym(name)) %>% UQ(rlang::sym(transform)))
}
arrange_values('mpg', 'sin')
Для select
вам не нужно использовать шаблон. Вместо этого вы можете использовать !!
:
## select a column
select_name <- function(name){
mtcars %>%
select(!!name)
}
select_name('mpg')
Ответ 4
Здесь другая версия, и это, возможно, немного проще.
multipetal <- function(df, n) {
varname <- paste("petal", n, sep=".")
df<-mutate_(df, .dots=setNames(paste0("Petal.Width*",n), varname))
df
}
for(i in 2:5) {
iris <- multipetal(df=iris, n=i)
}
> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.2 petal.3 petal.4 petal.5
1 5.1 3.5 1.4 0.2 setosa 0.4 0.6 0.8 1
2 4.9 3.0 1.4 0.2 setosa 0.4 0.6 0.8 1
3 4.7 3.2 1.3 0.2 setosa 0.4 0.6 0.8 1
4 4.6 3.1 1.5 0.2 setosa 0.4 0.6 0.8 1
5 5.0 3.6 1.4 0.2 setosa 0.4 0.6 0.8 1
6 5.4 3.9 1.7 0.4 setosa 0.8 1.2 1.6 2
Ответ 5
Я также добавляю ответ, который немного дополняет это, потому что я пришел к этой записи, когда искал ответ, и у меня было почти то, что мне было нужно, но мне нужно было немного больше, что я получил через @MrFlik и R lazyeval vignettes.
Я хотел создать функцию, которая могла бы взять dataframe и вектор имен столбцов (как строки), которые я хочу преобразовать из строки в объект Date. Я не мог понять, как сделать as.Date()
принять аргумент, который является строкой, и преобразовать его в столбец, поэтому я сделал это, как показано ниже.
Ниже показано, как я это сделал через SE mutate (mutate_()
) и аргумент .dots
. Критики, которые делают это лучше, приветствуются.
library(dplyr)
dat <- data.frame(a="leave alone",
dt="2015-08-03 00:00:00",
dt2="2015-01-20 00:00:00")
# This function takes a dataframe and list of column names
# that have strings that need to be
# converted to dates in the data frame
convertSelectDates <- function(df, dtnames=character(0)) {
for (col in dtnames) {
varval <- sprintf("as.Date(%s)", col)
df <- df %>% mutate_(.dots= setNames(list(varval), col))
}
return(df)
}
dat <- convertSelectDates(dat, c("dt", "dt2"))
dat %>% str
Ответ 6
В то время как мне нравится использовать dplyr для интерактивного использования, я считаю чрезвычайно сложным сделать это с помощью dplyr, потому что вам нужно пройти через обручи, чтобы использовать lazyeval:: interp(), setNames и т.д. обходные пути.
Вот более простая версия, использующая базу R, в которой мне кажется более интуитивной, по крайней мере, для того, чтобы поместить цикл внутри функции и которая расширяет решение @MrFlicks.
multipetal <- function(df, n) {
for (i in 1:n){
varname <- paste("petal", i , sep=".")
df[[varname]] <- with(df, Petal.Width * i)
}
df
}
multipetal(iris, 3)
Ответ 7
Вы можете наслаждаться пакетом friendlyeval
который представляет упрощенный упрощенный API eval и документацию для более новых/случайных пользователей dplyr
.
Вы создаете строки, которые вы хотите mutate
чтобы обрабатывать имена столбцов. Поэтому, используя friendlyeval
вы можете написать:
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
df <- mutate(df, !!treat_string_as_col(varname) := Petal.Width * n)
df
}
for(i in 2:5) {
iris <- multipetal(df=iris, n=i)
}
Что под капотом вызывает функции rlang
которые проверяют varname
является законным как имя столбца.
friendlyeval
код может быть преобразован в эквивалентный простой порядок кода eval в любое время с добавлением RStudio.