Gunakan nama variabel dinamis dalam `dplyr`

168

Saya ingin menggunakan dplyr::mutate()untuk membuat beberapa kolom baru dalam bingkai data. Nama kolom dan isinya harus dihasilkan secara dinamis.

Contoh data dari iris:

library(dplyr)
iris <- tbl_df(iris)

Saya telah membuat fungsi untuk mengubah kolom baru saya dari Petal.Widthvariabel:

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

Sekarang saya membuat lingkaran untuk membuat kolom saya:

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

Namun, karena mutate berpikir varname adalah nama variabel literal, loop hanya membuat satu variabel baru (disebut varname) alih-alih empat (disebut petal.2 - petal.5).

Bagaimana saya bisa mutate()menggunakan nama dinamis saya sebagai nama variabel?

Timm S.
sumber
1
Saya tidak bersikeras untuk bermutasi, saya bertanya apakah itu mungkin. Mungkin itu hanya tipuan kecil yang saya tidak tahu. Jika ada cara lain, mari kita dengarkan.
Timm S.
Saya percaya ada ruang untuk menonton dalam paket lazyeval
baptiste
1
Pada titik ini, dplyrmiliki sketsa lengkap tentang evaluasi non-standar
Gregor Thomas
16
Sketsa bahkan tidak disebutkan mutate_, dan itu benar-benar tidak jelas dari fungsi lain cara menggunakannya.
nacnudus

Jawaban:

191

Karena Anda secara dinamis membangun nama variabel sebagai nilai karakter, lebih masuk akal untuk melakukan penugasan menggunakan pengindeksan data.frame standar yang memungkinkan nilai karakter untuk nama kolom. Sebagai contoh:

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

The mutateFungsi membuatnya sangat mudah untuk nama kolom baru melalui parameter bernama. Tapi itu mengasumsikan Anda tahu nama saat mengetik perintah. Jika Anda ingin menentukan nama kolom secara dinamis, maka Anda juga harus membangun argumen yang dinamai.


versi dplyr> = 0,7

Versi terbaru dplyr(0.7) melakukan ini menggunakan dengan menggunakan :=untuk secara dinamis menetapkan nama parameter. Anda dapat menulis fungsi Anda sebagai:

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

Untuk informasi lebih lanjut, lihat formulir dokumentasi yang tersedia vignette("programming", "dplyr").


dplyr (> = 0,3 & <0,7)

Versi sedikit lebih awal dplyr(> = 0,3 <0,7), mendorong penggunaan alternatif "evaluasi standar" untuk banyak fungsi. Lihat sketsa evaluasi Non-standar untuk informasi lebih lanjut ( vignette("nse")).

Jadi di sini, jawabannya adalah menggunakan mutate_()daripada mutate()melakukan:

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

Perhatikan ini juga dimungkinkan dalam versi lama dplyryang ada saat pertanyaan awalnya diajukan. Ini membutuhkan penggunaan quotedan 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)
}
MrFlick
sumber
24
Terima kasih, itu sangat membantu. btw, saya selalu membuat variabel yang sangat dramatis.
Timm S.
27
Hehe. itu mungkin salah satu kesalahan ketik favorit saya. Saya pikir saya akan meninggalkannya.
MrFlick
1
do.call()mungkin tidak melakukan apa yang Anda pikirkan: rpubs.com/hadley/do-call2 . Lihat juga sketsa nse dalam versi dev dplyr.
hadley
4
Jadi jika saya mengerti maksud Anda @hadley, saya telah memperbarui yang di do.callatas untuk digunakan do.call("mutate")dan mengutip dfdalam daftar. Apakah itu yang Anda sarankan? Dan ketika lazyevalversi dplyradalah versi yang dirilis, maka mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))apakah akan menjadi solusi yang lebih baik?
MrFlick
1
Bagaimana jika saya memerlukan tajuk kolom variabel tidak hanya di sisi kiri penugasan tetapi juga di kanan? misalnya mutate(df, !!newVar := (!!var1 + !!var2) / 2)tidak bekerja :(
Mario Reutter
55

Dalam rilis baru dplyr( 0.6.0menunggu pada April 2017), kami juga dapat melakukan penugasan ( :=) dan meneruskan variabel sebagai nama kolom dengan tanda kutip ( !!) untuk tidak mengevaluasinya

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

Memeriksa output berdasarkan @ MrFlick yang multipetalditerapkan pada 'iris1'

identical(iris1, iris2)
#[1] TRUE
akrun
sumber
26

Setelah banyak trial and error, saya menemukan pola yang UQ(rlang::sym("some string here")))sangat berguna untuk bekerja dengan string dan kata kerja dplyr. Tampaknya bekerja dalam banyak situasi mengejutkan.

Berikut ini contoh dengan mutate. Kami ingin membuat fungsi yang menambahkan bersama dua kolom, tempat Anda meneruskan fungsi kedua nama kolom sebagai string. Kita dapat menggunakan pola ini, bersama dengan operator penugasan :=, untuk melakukan ini.

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

Pola tersebut juga berfungsi dengan dplyrfungsi lain . Inilah filter:

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

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

Sebab select, Anda tidak perlu menggunakan polanya. Sebagai gantinya Anda dapat menggunakan !!:

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')
Tom Roth
sumber
Kiat Anda bekerja dengan sangat baik, tetapi saya memiliki sedikit masalah. Saya mengubah kolom awal myColmenjadi url (misalnya), dan menyalin kolom lama myColInitialValuedi akhir kerangka data dfdengan nama baru. Tapi which(colnames(df)=='myCol')kirim kembali col # dari myColInitialValue. Saya belum menulis masalah karena saya belum menemukan reprex. Tujuan saya adalah untuk escapeparameter DT::datatable(). Saya menggunakan escape=FALSEmenunggu itu. Dengan konstanta tidak berfungsi juga, tetapi paket DT tampaknya juga mendapatkan kolom # yang buruk. :)
phili_b
Tampaknya variabel dinamis bukan penyebabnya. (btw reprex ditambahkan)
phili_b
Terima kasih atas jawaban ini! Berikut ini adalah contoh super sederhana tentang bagaimana saya menggunakannya:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest
Ini bekerja untuk saya di dalam formula di mana !! varname tidak berfungsi.
daknowles
12

Ini versi lain, dan ini bisa dibilang sedikit lebih sederhana.

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
pengguna2946432
sumber
8

Dengan rlang 0.4.0kami memiliki operator keriting-keriting ( {{}}) yang membuatnya sangat mudah.

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

Kami juga dapat memberikan nama variabel yang dikutip / tidak dikutip untuk ditugaskan sebagai nama kolom.

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

Ini bekerja sama dengan

multipetal(iris1, "temp", 3)
Ronak Shah
sumber
4

Saya juga menambahkan jawaban yang menambah ini sedikit karena saya datang ke entri ini ketika mencari jawaban, dan ini hampir apa yang saya butuhkan, tetapi saya membutuhkan sedikit lebih banyak, yang saya dapatkan melalui jawaban @MrFlik dan jawaban Sketsa R lazyeval.

Saya ingin membuat fungsi yang dapat mengambil dataframe dan vektor nama kolom (sebagai string) yang ingin saya konversi dari string ke objek Date. Saya tidak tahu bagaimana cara as.Date()mengambil argumen yang merupakan string dan mengubahnya menjadi kolom, jadi saya melakukannya seperti yang ditunjukkan di bawah ini.

Di bawah ini adalah bagaimana saya melakukan ini melalui SE bermutasi ( mutate_()) dan .dotsargumennya. Selamat datang kritik yang membuat ini lebih baik.

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
mpettis
sumber
3

Sementara saya menikmati menggunakan dplyr untuk penggunaan interaktif, saya merasa sangat sulit untuk melakukan ini menggunakan dplyr karena Anda harus melalui lingkaran untuk menggunakan lazyeval :: interp (), setNames, dll.

Ini adalah versi yang lebih sederhana menggunakan basis R, di mana tampaknya lebih intuitif, setidaknya bagi saya, untuk meletakkan loop di dalam fungsi, dan yang memperluas solusi @Fricks.

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) 
hackR
sumber
2
+1, meskipun saya masih menggunakan dplyrbanyak pengaturan non-interaktif, menggunakannya dengan input variabel di dalam fungsi menggunakan sintaks yang sangat kikuk.
Paul Hiemstra
3

Anda dapat menikmati paket friendlyevalyang menyajikan API evaluasi sederhana dan dokumentasi untuk yang lebih baru / kasualdplyr pengguna .

Anda membuat string yang ingin Anda mutateperlakukan sebagai nama kolom. Jadi menggunakan friendlyevalAnda bisa menulis:

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

Yang di bawah kap panggilan rlangfungsi yang memeriksavarname sah sebagai nama kolom.

friendlyeval kode dapat dikonversikan menjadi kode evalusi rapi yang setara kapan saja dengan addt RStudio.

MilesMcBain
sumber
0

Alternatif lain: gunakan {}tanda kutip di dalam untuk membuat nama dinamis dengan mudah. Ini mirip dengan solusi lain tetapi tidak persis sama, dan saya merasa lebih mudah.

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

Saya pikir ini berasal dari dplyr 1.0.0tetapi tidak yakin (saya juga punya rlang 4.7.0jika itu penting).

Bretauv
sumber