Sử dụng tên biến động trong `dplyr`


168

Tôi muốn sử dụng dplyr::mutate()để tạo nhiều cột mới trong khung dữ liệu. Tên cột và nội dung của chúng phải được tạo động.

Dữ liệu ví dụ từ iris:

library(dplyr)
iris <- tbl_df(iris)

Tôi đã tạo một hàm để thay đổi các cột mới của mình từ Petal.Widthbiến:

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

Bây giờ tôi tạo một vòng lặp để xây dựng các cột của mình:

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

Tuy nhiên, vì mutate nghĩ varname là tên biến theo nghĩa đen, nên vòng lặp chỉ tạo một biến mới (được gọi là varname) thay vì bốn (được gọi là petal.2 - petal.5).

Làm cách nào tôi có mutate()thể sử dụng tên động của mình làm tên biến?


1
Tôi không khăng khăng đòi đột biến, tôi hỏi liệu có thể không. Có lẽ đó chỉ là một mẹo nhỏ mà tôi không biết. Nếu có cách khác, hãy nghe nó.
Timm S.



16
Các họa tiết thậm chí không đề cập đến mutate_, và nó thực sự không rõ ràng từ các chức năng khác làm thế nào để sử dụng nó.
xà cừ

Câu trả lời:


191

Vì bạn đang tự động xây dựng một tên biến làm giá trị ký tự, nên thực hiện phép gán bằng cách sử dụng lập chỉ mục data.frame chuẩn cho phép giá trị ký tự cho tên cột. Ví dụ:

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

Các mutatechức năng làm cho nó rất dễ dàng để đặt tên cho các cột mới qua các thông số được đặt tên. Nhưng điều đó giả sử bạn biết tên khi bạn gõ lệnh. Nếu bạn muốn tự động chỉ định tên cột, thì bạn cũng cần xây dựng đối số được đặt tên.


phiên bản dplyr> = 0,7

Phiên bản mới nhất của dplyr(0.7) thực hiện điều này bằng cách sử dụng :=để gán động các tên tham số. Bạn có thể viết chức năng của bạn như:

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

Để biết thêm thông tin, xem các tài liệu có sẵn mẫu vignette("programming", "dplyr").


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

Phiên bản hơi sớm hơn của dplyr(> = 0,3 <0,7), khuyến khích sử dụng các phương án "đánh giá tiêu chuẩn" cho nhiều chức năng. Xem họa tiết đánh giá không chuẩn để biết thêm thông tin ( vignette("nse")).

Vì vậy, ở đây, câu trả lời là sử dụng mutate_()chứ không phải mutate()và làm:

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

Lưu ý điều này cũng có thể có trong các phiên bản cũ hơn dplyrđã tồn tại khi câu hỏi ban đầu được đặt ra. Nó đòi hỏi sử dụng cẩn thận quotesetName:

# --- 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
Cảm ơn bạn, điều đó hữu ích. btw, tôi luôn tạo ra các biến thực sự ấn tượng.
Timm S.

27
Hehe. đó có lẽ là một trong những lỗi chính tả yêu thích của tôi trong một thời gian. Tôi nghĩ rằng tôi sẽ rời khỏi nó.
MrFlick

1
do.call()có thể không làm những gì bạn nghĩ: rpub.com/hadley/do-call2 . Xem thêm các họa tiết nse trong phiên bản dev của dplyr.
hadley

4
Vì vậy, nếu tôi hiểu quan điểm của bạn @hadley, tôi đã cập nhật những điều do.calltrên để sử dụng do.call("mutate")và trích dẫn dftrong danh sách. Đó có phải là những gì bạn đã đề nghị? Và khi lazyevalphiên bản của phiên bản dplyrđược phát hành, thì đó mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))sẽ là một giải pháp tốt hơn?
MrFlick

1
Điều gì xảy ra nếu tôi cần tiêu đề cột biến không chỉ ở phía bên trái của bài tập mà còn ở bên phải? ví dụ: mutate(df, !!newVar := (!!var1 + !!var2) / 2)không hoạt động :(
Mario Reutter

55

Trong bản phát hành mới của dplyr( 0.6.0đang chờ vào tháng 4 năm 2017), chúng tôi cũng có thể thực hiện một phép gán ( :=) và chuyển các biến dưới dạng tên cột bằng cách bỏ qua ( !!) để không đánh giá nó

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

Kiểm tra đầu ra dựa trên @ MrFlick multipetalđược áp dụng trên 'iris1'

identical(iris1, iris2)
#[1] TRUE

26

Sau rất nhiều thử nghiệm và sai sót, tôi thấy mô hình UQ(rlang::sym("some string here")))thực sự hữu ích để làm việc với các chuỗi và động từ dplyr. Nó dường như làm việc trong rất nhiều tình huống đáng ngạc nhiên.

Đây là một ví dụ với mutate. Chúng tôi muốn tạo một hàm cộng hai cột lại với nhau, trong đó bạn chuyển hàm cả hai tên cột thành chuỗi. Chúng ta có thể sử dụng mẫu này, cùng với toán tử gán :=, để làm điều này.

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

Các mô hình hoạt động với các dplyrchức năng khác là tốt. Đây là filter:

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

Hoặc 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')

Đối với select, bạn không cần phải sử dụng mô hình. Thay vào đó bạn có thể sử dụng !!:

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

Lời khuyên của bạn hoạt động rất tốt, nhưng tôi có một vấn đề nhỏ. Tôi thay đổi một cột ban đầu myColthành một url (ví dụ) và sao chép cột cũ myColInitialValueở cuối khung dữ liệu dfvới một tên mới. Nhưng which(colnames(df)=='myCol')gửi lại col # của myColInitialValue. Tôi chưa viết một vấn đề nào vì tôi không tìm thấy reprex. Mục tiêu của tôi là cho các escapetham số của DT::datatable(). Tôi sử dụng escape=FALSEtrong chờ đợi đó. Với các hằng số nó cũng không hoạt động nhưng gói DT dường như cũng nhận được cột # xấu. :)
phili_b


Có vẻ như các biến động không phải là nguyên nhân. (btw reprex đã thêm)
phili_b

Cảm ơn câu trả lời này! Đây là một ví dụ siêu đơn giản về cách tôi sử dụng nó:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemomon

Điều này làm việc cho tôi trong một công thức mà !! varname không hoạt động.
daknowles

12

Đây là một phiên bản khác, và nó được cho là đơn giản hơn một chút.

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

Với rlang 0.4.0chúng tôi có các toán tử xoăn-xoăn ( {{}}) làm cho điều này rất dễ dàng.

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

Chúng ta cũng có thể chuyển tên biến được trích dẫn / không trích dẫn để được chỉ định làm tên cột.

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

Nó hoạt động tương tự với

multipetal(iris1, "temp", 3)

4

Tôi cũng đang thêm một câu trả lời làm tăng thêm một chút bởi vì tôi đã đến mục này khi tìm kiếm câu trả lời, và đây gần như là những gì tôi cần, nhưng tôi cần thêm một chút, tôi đã nhận được qua câu trả lời của @MrFlik và R họa tiết lười biếng.

Tôi muốn tạo một hàm có thể lấy một khung dữ liệu và một vectơ tên cột (dưới dạng chuỗi) mà tôi muốn được chuyển đổi từ một chuỗi thành một đối tượng Ngày. Tôi không thể tìm ra làm thế nào để đưa ra as.Date()một đối số là một chuỗi và chuyển đổi nó thành một cột, vì vậy tôi đã làm nó như được hiển thị bên dưới.

Dưới đây là cách tôi đã làm điều này thông qua SE mutate (mutate_() ) và .dotsđối số. Những lời phê bình làm cho điều này tốt hơn được chào đón.

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

Trong khi tôi thích sử dụng dplyr để sử dụng tương tác, tôi thấy thật khó khăn khi thực hiện việc này bằng cách sử dụng dplyr vì bạn phải trải qua các vòng để sử dụng các cách giải quyết lazyeval :: interp (), setNames, v.v.

Đây là một phiên bản đơn giản hơn sử dụng cơ sở R, trong đó có vẻ trực quan hơn, đối với tôi, để đặt vòng lặp bên trong hàm và mở rộng giải pháp của @ 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, mặc dù tôi vẫn sử dụng dplyrrất nhiều trong cài đặt không tương tác, nhưng sử dụng nó với đầu vào variabel bên trong một hàm sử dụng cú pháp rất khó hiểu.
Paul Hiemstra

3

Bạn có thể thưởng thức gói friendlyevaltrình bày tài liệu và API tài liệu gọn gàng đơn giản cho dplyrngười dùng mới / người dùng thông thường .

Bạn đang tạo các chuỗi mà bạn muốn mutatecoi là tên cột. Vì vậy, bằng cách sử dụng friendlyevalbạn có thể viết:

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

Mà dưới mui xe gọi các rlangchức năng kiểm tra varnamelà hợp pháp như tên cột.

friendlyeval mã có thể được chuyển đổi thành mã eval gọn gàng tương đương bất cứ lúc nào với một addin RStudio.


0

Một cách khác: sử dụng {}bên trong dấu ngoặc kép để dễ dàng tạo tên động. Điều này tương tự với các giải pháp khác nhưng không hoàn toàn giống nhau, và tôi thấy nó dễ dàng hơn.

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

Tôi nghĩ rằng điều này đến từ dplyr 1.0.0nhưng không chắc chắn (tôi cũng có rlang 4.7.0nếu nó quan trọng).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.