dplyr - 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()
использовать мое динамическое имя в качестве имени переменной?
10 ответов
Поскольку вы существенно строите имя переменной как символьное значение, имеет смысл выполнять присваивание с использованием стандартной индексации data.frame, которая допускает символьные значения для имен столбцов. Например:
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
df[[varname]] <- with(df, Petal.Width * n)
df
}
mutate
Функция позволяет очень легко называть новые столбцы с помощью именованных параметров. Но это предполагает, что вы знаете имя при вводе команды. Если вы хотите динамически указать имя столбца, вам также необходимо создать именованный аргумент.
Последняя версия 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) поощряла использование альтернатив "стандартной оценки" для многих функций. См. Нестандартную оценочную виньетку для получения дополнительной информации (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
Обратите внимание, что это также возможно в более старых версиях 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)
}
В новом выпуске dplyr
(0.6.0
ожидая в апреле 2017 года), мы также можем сделать задание (:=
) и передайте переменные как имена столбцов без кавычек (!!
не оценивать
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's multipetal
применяется к "iris1"
identical(iris1, iris2)
#[1] TRUE
После долгих проб и ошибок я нашел образец 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')
С rlang 0.4.0
у нас есть фигурно-фигурные операторы ({{}}
), что делает это очень просто.
library(dplyr)
library(rlang)
iris1 <- tbl_df(iris)
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
mutate(df, {{varname}} := Petal.Width * n)
}
multipetal(iris1, 4)
# A tibble: 150 x 6
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.4
# <dbl> <dbl> <dbl> <dbl> <fct> <dbl>
# 1 5.1 3.5 1.4 0.2 setosa 0.8
# 2 4.9 3 1.4 0.2 setosa 0.8
# 3 4.7 3.2 1.3 0.2 setosa 0.8
# 4 4.6 3.1 1.5 0.2 setosa 0.8
# 5 5 3.6 1.4 0.2 setosa 0.8
# 6 5.4 3.9 1.7 0.4 setosa 1.6
# 7 4.6 3.4 1.4 0.3 setosa 1.2
# 8 5 3.4 1.5 0.2 setosa 0.8
# 9 4.4 2.9 1.4 0.2 setosa 0.8
#10 4.9 3.1 1.5 0.1 setosa 0.4
# … with 140 more rows
Мы также можем передать имена переменных в кавычках / без кавычек, которые будут назначены в качестве имен столбцов.
multipetal <- function(df, name, n) {
mutate(df, {{name}} := Petal.Width * n)
}
multipetal(iris1, temp, 3)
# A tibble: 150 x 6
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species temp
# <dbl> <dbl> <dbl> <dbl> <fct> <dbl>
# 1 5.1 3.5 1.4 0.2 setosa 0.6
# 2 4.9 3 1.4 0.2 setosa 0.6
# 3 4.7 3.2 1.3 0.2 setosa 0.6
# 4 4.6 3.1 1.5 0.2 setosa 0.6
# 5 5 3.6 1.4 0.2 setosa 0.6
# 6 5.4 3.9 1.7 0.4 setosa 1.2
# 7 4.6 3.4 1.4 0.3 setosa 0.900
# 8 5 3.4 1.5 0.2 setosa 0.6
# 9 4.4 2.9 1.4 0.2 setosa 0.6
#10 4.9 3.1 1.5 0.1 setosa 0.3
# … with 140 more rows
То же самое работает с
multipetal(iris1, "temp", 3)
Вот другая версия, и, возможно, она немного проще.
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
Вы можете наслаждаться пакетом friendlyeval
который представляет упрощенный Tidy Eval API и документацию для новых / казуальных 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
код может быть преобразован в эквивалентный простой код в любое время с помощью надстройки RStudio.
Я также добавляю ответ, который немного увеличивает это, потому что я пришел к этой записи при поиске ответа, и это было почти то, что мне нужно, но мне нужно было немного больше, что я получил через ответ @MrFlik и Р лазевал виньетками.
Я хотел создать функцию, которая могла бы принимать фрейм данных и вектор имен столбцов (в виде строк), которые я хочу преобразовать из строки в объект 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
Несмотря на то, что мне нравится использовать 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)
Другая альтернатива: использовать {}
внутри кавычек, чтобы легко создавать динамические имена. Это похоже на другие решения, но не совсем то же самое, и мне это проще.
library(dplyr)
library(tibble)
iris <- as_tibble(iris)
multipetal <- function(df, n) {
df <- mutate(df, "petal.{n}" := Petal.Width * n) ## problem arises here
df
}
for(i in 2:5) {
iris <- multipetal(df=iris, n=i)
}
iris
Я думаю это исходит от dplyr 1.0.0
но не уверен (у меня тоже есть rlang 4.7.0
если это имеет значение).
Если вам нужна одна и та же операция несколько раз, это обычно говорит вам, что ваш формат данных не оптимален. Вы хотите более длинный формат с
n
будучи столбцом в data.frame, который может быть достигнут с помощью перекрестного соединения:
library(tidyverse)
iris %>% mutate(identifier = 1:n()) %>% #necessary to disambiguate row 102 from row 143 (complete duplicates)
full_join(tibble(n = 1:5), by=character()) %>% #cross join for long format
mutate(petal = Petal.Width * n) %>% #calculation in long format
pivot_wider(names_from=n, values_from=petal, names_prefix="petal.width.") #back to wider format (if desired)
Результат:
# A tibble: 150 x 11
Sepal.Length Sepal.Width Petal.Length Petal.Width Species identifier petal.width.1 petal.width.2 petal.width.3
<dbl> <dbl> <dbl> <dbl> <fct> <int> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2 setosa 1 0.2 0.4 0.6
2 4.9 3 1.4 0.2 setosa 2 0.2 0.4 0.6
3 4.7 3.2 1.3 0.2 setosa 3 0.2 0.4 0.6
4 4.6 3.1 1.5 0.2 setosa 4 0.2 0.4 0.6
5 5 3.6 1.4 0.2 setosa 5 0.2 0.4 0.6
6 5.4 3.9 1.7 0.4 setosa 6 0.4 0.8 1.2
7 4.6 3.4 1.4 0.3 setosa 7 0.3 0.6 0.9
8 5 3.4 1.5 0.2 setosa 8 0.2 0.4 0.6
9 4.4 2.9 1.4 0.2 setosa 9 0.2 0.4 0.6
10 4.9 3.1 1.5 0.1 setosa 10 0.1 0.2 0.3
# ... with 140 more rows, and 2 more variables: petal.width.4 <dbl>, petal.width.5 <dbl>