ใช้ชื่อตัวแปรแบบไดนามิกใน `dplyr '


168

ฉันต้องการใช้dplyr::mutate()เพื่อสร้างคอลัมน์ใหม่หลายคอลัมน์ในกรอบข้อมูล ชื่อคอลัมน์และเนื้อหาควรถูกสร้างขึ้นแบบไดนามิก

ตัวอย่างข้อมูลจากม่านตา:

library(dplyr)
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
ฉันไม่ได้ยืนยันว่าจะกลายพันธุ์ฉันกำลังถามว่าเป็นไปได้หรือไม่ อาจเป็นเพียงกลลวงเล็กน้อยที่ฉันไม่รู้ หากมีวิธีอื่นมาฟังกัน
Timm S.



16
บทความสั้น ๆ ไม่ได้พูดถึงmutate_และจริงๆแล้วมันไม่ชัดเจนจากฟังก์ชั่นอื่น ๆ ในการใช้งาน
nacnudus

คำตอบ:


191

เนื่องจากคุณกำลังสร้างชื่อตัวแปรแบบไดนามิกเป็นค่าอักขระจึงเหมาะสมกว่าที่จะทำการกำหนดโดยใช้การทำดัชนี 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)
}

24
ขอบคุณนั่นเป็นประโยชน์ btw ฉันสร้างตัวแปรที่น่าทึ่งจริงๆ
Timm S.

27
ฮิฮิ. นั่นอาจเป็นหนึ่งในความผิดพลาดที่ฉันโปรดปราน ฉันคิดว่าฉันจะจากไป
MrFlick

1
do.call()อาจจะไม่ได้ทำในสิ่งที่คุณคิดว่ามันไม่: rpubs.com/hadley/do-call2 ดูเพิ่มเติมบทความสั้นในรุ่น dev ของ dplyr
hadley

4
ดังนั้นหากฉันเข้าใจประเด็นของคุณ @hadley ฉันได้อัปเดตdo.callข้างต้นเพื่อใช้do.call("mutate")และอ้างอิงdfในรายการ นั่นคือสิ่งที่คุณกำลังแนะนำ? และเมื่อlazyevalเวอร์ชันของรุ่นที่dplyrวางจำหน่ายแล้วmutate_(df, .dots= setNames(list(~Petal.Width * n), varname))จะเป็นทางออกที่ดีกว่า?
MrFlick

1
จะทำอย่างไรถ้าฉันต้องการส่วนหัวคอลัมน์ตัวแปรไม่เพียง แต่ที่ด้านซ้ายมือของการมอบหมาย แต่ยังอยู่ทางด้านขวาด้วย? เช่นmutate(df, !!newVar := (!!var1 + !!var2) / 2)ไม่ทำงาน :(
Mario Reutter

55

ในรีลีสใหม่ของ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 ที่multipetalใช้กับ 'iris1'

identical(iris1, iris2)
#[1] TRUE

26

หลังจากการทดลองและข้อผิดพลาดมากมายฉันพบว่ารูปแบบ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')

เคล็ดลับของคุณทำงานได้ดีมาก แต่ฉันมีปัญหาเล็กน้อย ฉันเปลี่ยนคอลัมน์เริ่มต้นmyColเป็น url (ตัวอย่าง) และคัดลอกคอลัมน์เก่าmyColInitialValueที่ส่วนท้ายของ dataframe dfด้วยชื่อใหม่ แต่which(colnames(df)=='myCol')ส่งกลับไปเทือกเขา # myColInitialValueของ ฉันยังไม่ได้เขียนปัญหาเพราะฉันไม่พบ reprex เป้าหมายของฉันคือสำหรับพารามิเตอร์ของescape DT::datatable()ฉันใช้escape=FALSEในการรอ ค่าคงที่มันใช้งานไม่ได้ แต่แพ็กเกจ DTดูเหมือนจะได้คอลัมน์ # ที่ไม่ดีด้วย :)
phili_b


ดูเหมือนว่าตัวแปรแบบไดนามิกไม่ใช่สาเหตุ (เพิ่ม btw reprex)
phili_b

ขอบคุณสำหรับคำตอบนี้! นี่เป็นตัวอย่างง่ายๆของวิธีที่ฉันใช้:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest

สิ่งนี้ใช้ได้กับฉันในสูตรที่ !! varname ไม่ทำงาน
daknowles

12

นี่เป็นอีกเวอร์ชั่นหนึ่งและมันค่อนข้างง่ายกว่า

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

8

ด้วย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)

4

ฉันยังเพิ่มคำตอบที่เพิ่มเล็กน้อยนี้เพราะฉันมาที่รายการนี้เมื่อค้นหาคำตอบและนี่มีเกือบสิ่งที่ฉันต้องการ แต่ฉันต้องการอีกเล็กน้อยซึ่งฉันได้ผ่าน @MrFlik คำตอบและ บทความสั้น ๆ ขี้เกียจ

ฉันต้องการสร้างฟังก์ชันที่สามารถใช้ 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

3

ในขณะที่ฉันเพลิดเพลินกับการใช้ dplyr สำหรับการใช้งานแบบโต้ตอบฉันพบว่ามันยากมากที่จะทำสิ่งนี้โดยใช้ dplyr เพราะคุณต้องผ่านห่วงเพื่อใช้ lazyeval :: interp () setNames และวิธีแก้ปัญหาอื่น ๆ

นี่คือเวอร์ชั่นที่เรียบง่ายกว่าโดยใช้ base 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) 

2
+1 แม้ว่าฉันจะยังคงใช้dplyrการตั้งค่าที่ไม่ใช่แบบอินเทอร์แอกทีฟมากมาย แต่การใช้มันกับอินพุตแบบหลากหลายในฟังก์ชั่นนั้นใช้ไวยากรณ์ที่ไม่น่าสนใจ
Paul Hiemstra

3

คุณสามารถเพลิดเพลินกับแพ็คเกจfriendlyevalซึ่งนำเสนอ 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 รหัสสามารถแปลงเป็นรหัส eval ที่เป็นระเบียบเรียบร้อยเทียบเท่าได้ตลอดเวลาด้วย RStudio addin


0

อีกทางเลือกหนึ่ง: ใช้{}ภายในเครื่องหมายคำพูดเพื่อสร้างชื่อแบบไดนามิกได้อย่างง่ายดาย นี้คล้ายกับโซลูชันอื่น ๆ แต่ไม่เหมือนกันทั้งหมดและฉันพบว่าง่ายขึ้น

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ถ้ามันสำคัญ)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.