dplyr – mutate: usa nomes de variables ​​dinâmicas

Eu quero usar o mutate() dplyr para criar várias novas colunas em um quadro de dados. Os nomes das colunas e seus conteúdos devem ser gerados dinamicamente.

Exemplo de dados da íris:

 require(dplyr) data(iris) iris <- tbl_df(iris) 

Eu criei uma function para alterar minhas novas colunas da variável Petal.Width :

 multipetal <- function(df, n) { varname <- paste("petal", n , sep=".") df <- mutate(df, varname = Petal.Width * n) ## problem arises here df } 

Agora eu crio um loop para construir minhas colunas:

 for(i in 2:5) { iris <- multipetal(df=iris, n=i) } 

No entanto, como mutate acha que varname é um nome de variável literal, o loop cria apenas uma nova variável (chamada varname) em vez de quatro (chamada petal.2 – petal.5).

Como posso obter o mutate() para usar meu nome dynamic como nome de variável?

Como você está construindo um nome de variável como um valor de caractere, faz mais sentido fazer a atribuição usando a indexação padrão de dados.frame, que permite valores de caracteres para nomes de colunas. Por exemplo:

 multipetal < - function(df, n) { varname <- paste("petal", n , sep=".") df[[varname]] <- with(df, Petal.Width * n) df } 

A function mutate torna muito fácil nomear novas colunas por meio de parâmetros nomeados. Mas isso pressupõe que você saiba o nome quando digitar o comando. Se você deseja especificar dinamicamente o nome da coluna, também é necessário construir o argumento nomeado.

A versão mais recente do dplyr (0.7) faz isso usando := para atribuir dinamicamente nomes de parâmetros. Você pode escrever sua function como:

 # --- dplyr version 0.7+--- multipetal < - function(df, n) { varname <- paste("petal", n , sep=".") mutate(df, !!varname := Petal.Width * n) } 

Para mais informações, consulte a documentação disponível em vignette("programming", "dplyr") .

Uma versão ligeiramente anterior do dplyr (> = 0.3 <0.7) encorajou o uso de alternativas de "avaliação padrão" para muitas das funções. Veja a vinheta de avaliação não padrão para mais informações ( vignette("nse") ).

Então aqui, a resposta é usar mutate_() ao invés de mutate() e fazer:

 # --- 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)) } 

Versões mais antigas do dplyr

Note que isto também é possível em versões mais antigas do dplyr que existiam quando a questão foi originalmente colocada. Requer o uso cuidadoso de quote e 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) } 

Na nova versão do dplyr ( 0.6.0 aguardando em abril de 2017), também podemos fazer uma atribuição ( := ) e passar variables ​​como nomes de coluna por unquoting ( !! ) para não avaliá-lo

  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) } 

Verificar a saída com base no multipetal de @ multipetal aplicado em 'iris1'

 identical(iris1, iris2) #[1] TRUE 

Aqui está outra versão, e é sem dúvida um pouco mais simples.

 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 

Eu também estou adicionando uma resposta que aumenta um pouco isso porque eu cheguei a esta input quando procurei por uma resposta, e isso tinha quase o que eu precisava, mas eu precisava de um pouco mais, o que eu consegui via resposta do @MrFlik e Vinhetas R lazyeval.

Eu queria fazer uma function que poderia levar um dataframe e um vetor de nomes de coluna (como seqüências de caracteres) que eu quero ser convertido de uma seqüência de caracteres para um object Date. Eu não consegui descobrir como fazer o as.Date() pegar um argumento que é uma string e convertê-lo em uma coluna, então fiz como mostrado abaixo.

Abaixo está como eu fiz isso via SE mutate ( mutate_() ) e o argumento .dots . Críticas que tornam isso melhor são bem-vindas.

 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 

Depois de muita tentativa e erro, eu encontrei o padrão UQ(rlang::sym("some string here"))) realmente útil para trabalhar com strings e verbos dplyr. Parece funcionar em muitas situações surpreendentes.

Aqui está um exemplo com mutate . Queremos criar uma function que agregue duas colunas, onde você passa a function ambos os nomes de colunas como strings. Podemos usar esse padrão, junto com o operador de atribuição := , para fazer isso.

 ## 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') 

O padrão também funciona com outras funções do dplyr . Aqui está o filter :

 ## filter a column by a value filter_values < - function(name, value){ mtcars %>% filter(UQ(rlang::sym(name)) != value) } filter_values('gear', 4) 

Ou 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') 

Para select , você não precisa usar o padrão. Em vez disso você pode usar !! :

 ## select a column select_name < - function(name){ mtcars %>% select(!!name) } select_name('mpg') 

Embora eu goste de usar o dplyr para uso interativo, eu acho extraordinariamente difícil fazer isso usando o dplyr, porque você tem que passar por aros para usar as soluções alternativas do lazyeval :: interp (), setNames, etc.

Aqui está uma versão mais simples usando a base R, na qual parece mais intuitivo, pelo menos para mim, colocar o laço dentro da function, e que estende a solução do @ 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) 

Você pode apreciar o pacote friendlyeval que apresenta uma API eval simplificada e documentação para usuários dplyr mais novos / casuais.

Você está criando strings que você deseja mutate para tratar como nomes de colunas. Então, usando friendlyeval você poderia escrever:

 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) } 

Que sob o capô chama funções rlang que verificam varname é legal como nome da coluna.

friendlyeval código friendlyeval pode ser convertido em código eval simples e equivalente equivalente a qualquer momento com um suplemento do RStudio.