Áp dụng một hàm cho mọi hàng của bảng bằng dplyr?


121

Khi làm việc với plyrtôi, tôi thường thấy hữu ích khi sử dụng adplycho các hàm vô hướng mà tôi phải áp dụng cho mỗi hàng.

ví dụ

data(iris)
library(plyr)
head(
     adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     5.1
2          4.9         3.0          1.4         0.2  setosa     4.9
3          4.7         3.2          1.3         0.2  setosa     4.7
4          4.6         3.1          1.5         0.2  setosa     4.6
5          5.0         3.6          1.4         0.2  setosa     5.0
6          5.4         3.9          1.7         0.4  setosa     5.4

Bây giờ tôi đang sử dụng dplyrnhiều hơn, tôi tự hỏi liệu có cách nào ngăn nắp / tự nhiên để làm điều này không? Vì đây KHÔNG phải là điều tôi muốn:

library(dplyr)
head(
     mutate(iris, Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     7.9
2          4.9         3.0          1.4         0.2  setosa     7.9
3          4.7         3.2          1.3         0.2  setosa     7.9
4          4.6         3.1          1.5         0.2  setosa     7.9
5          5.0         3.6          1.4         0.2  setosa     7.9
6          5.4         3.9          1.7         0.4  setosa     7.9

gần đây tôi đã hỏi liệu có tương đương với mdplydplyr không, và hadley gợi ý rằng họ có thể đang sản xuất thứ gì đó dựa trên do. Tôi đoán nó cũng sẽ hoạt động ở đây.
baptiste

4
Cuối cùng dplyr sẽ có cái gì đó như rowwise()đó sẽ nhóm bởi mỗi cá nhân hàng
hadley

@hadley thx, không phải nó chỉ hoạt động như adplykhi bạn không sử dụng nhóm sao? như chức năng tích hợp chặt chẽ của nó được gọi là group_byKHÔNGsplit_by
Stephen Henderson

@StephenHenderson không, bởi vì bạn cũng cần một số cách để thao tác trên bảng nói chung.
hadley

1
@HowYaDoing Có nhưng phương pháp đó không tổng quát hóa. Chẳng hạn như không có psum, pmean hay pmedian.
Stephen Henderson

Câu trả lời:


202

Khi dplyr 0.2 (tôi nghĩ) rowwise()được triển khai, vì vậy câu trả lời cho vấn đề này trở thành:

iris %>% 
  rowwise() %>% 
  mutate(Max.Len= max(Sepal.Length,Petal.Length))

Không rowwisethay thế

Năm năm (!) Sau câu trả lời này vẫn nhận được rất nhiều lưu lượng truy cập. Kể từ khi nó được đưa ra, rowwisengày càng không được khuyến khích, mặc dù nhiều người dường như thấy nó trực quan. Hãy tự giúp mình và xem qua quy trình làm việc theo định hướng Hàng của Jenny Bryan trong R với ngăn nắp tài liệu để xử lý tốt chủ đề này.

Cách đơn giản nhất mà tôi tìm thấy là dựa trên một trong những ví dụ của Hadley bằng cách sử dụng pmap:

iris %>% 
  mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))

Sử dụng cách tiếp cận này, bạn có thể cung cấp một số đối số tùy ý cho hàm ( .f) bên trong pmap.

pmap là một cách tiếp cận khái niệm tốt vì nó phản ánh thực tế là khi bạn thực hiện các thao tác khôn ngoan với hàng, bạn thực sự đang làm việc với các bộ giá trị từ danh sách các vectơ (các cột trong khung dữ liệu).


Tôi đã thay đổi điều này (từ phần trên) thành câu trả lời lý tưởng vì tôi nghĩ đây là mục đích sử dụng.
Stephen Henderson

1
có thể thêm các giá trị của khung dữ liệu được tạo động không? Vì vậy, trong khung dữ liệu này, tên các cột không được biết. Tôi có thể thêm nếu tên cột được biết.
Arun Raja

stackoverflow.com/questions/28807266/… vừa tìm thấy câu trả lời. Trong điều này, họ đang sử dụng tương quan thay vì tổng. Nhưng cùng một khái niệm.
Arun Raja

13
Nếu nó không làm việc, chắc chắn rằng bạn đang thực sự sử dụng dplyr :: đột biến không plyr :: đột biến - lái xe cho tôi hạt
Jan-GLX

Cảm ơn YAK, điều này cũng làm tôi buồn. Nếu bạn bao gồm cả hai plyrdplyrgói, bạn gần như chắc chắn đang sử dụng sai mutatetrừ khi bạn cung cấp phạm vi rõ ràng dplyr::mutate.
Chris Warth

22

Cách tiếp cận thành ngữ sẽ là tạo ra một hàm được vectorised một cách thích hợp.

Rcung cấp pmaxphù hợp ở đây, tuy nhiên nó cũng cung cấp Vectorizenhư một trình bao bọc mapplyđể cho phép bạn tạo một phiên bản tùy ý được vectorised của một hàm tùy ý.

library(dplyr)
# use base R pmax (vectorized in C)
iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length))
# use vectorize to create your own function
# for example, a horribly inefficient get first non-Na value function
# a version that is not vectorized
coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]}
# a vectorized version
Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b'))
# some example data
df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8))
df %>% mutate(ab =Coalesce(a,b))

Lưu ý rằng thực hiện vectơ hóa trong C / C ++ sẽ nhanh hơn, nhưng không có magicPonygói nào viết hàm cho bạn.


thx, đây là một câu trả lời tuyệt vời, là một vị tướng R phong cách tuyệt vời như bạn nói, nhưng tôi không nghĩ rằng nó thực sự giải quyết được câu hỏi của tôi liệu có dplyrcách nào ... vì nó sẽ đơn giản hơn nếu không có dplyr, ví dụ: with(df, Coalesce(a,b))Có lẽ, đó là một loại câu trả lời mặc dù - không sử dụng dplyrcho điều đó?
Stephen Henderson

4
Phải thừa nhận rằng tôi đã kiểm tra kỹ rằng không có magicPonygói hàng nào. Quá tệ
rsoren

21

Bạn cần phải nhóm theo hàng:

iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))

Đây là những gì 1đã làm trong adply.


Có vẻ như phải có một cú pháp đơn giản hơn hoặc "đẹp hơn".
Stephen Henderson

@StephenHenderson, có thể có, tôi không phải là dplyrchuyên gia. Hy vọng rằng ai đó sẽ đến cùng với một cái gì đó tốt hơn. Lưu ý rằng tôi đã làm sạch nó một chút với 1:n().
BrodieG

Tôi nghi ngờ bạn đúng, nhưng tôi cảm thấy như hành vi mặc định không có nhóm sẽ giống như group_by(1:n())hành vi. Nếu không ai có bất kỳ ý tưởng nào khác vào buổi sáng, tôi sẽ đánh dấu vào ý tưởng của bạn;)
Stephen Henderson

Ngoài ra, hãy lưu ý rằng điều này có phần trái với tài liệu hướng dẫn n: "Chức năng này được triển khai đặc biệt cho từng nguồn dữ liệu và chỉ có thể được sử dụng từ bên trong phần tóm tắt.", Mặc dù nó có vẻ hoạt động.
BrodieG

Bạn có thể tham khảo Sepal.Length và Petal.Length bằng số chỉ mục của chúng theo một cách nào đó không? Nếu bạn có nhiều biến thì sẽ rất hữu ích. Giống như ... Max.len = max ([c (1,3)])?
Rasmus Larsen

19

Cập nhật 2017-08-03

Sau khi viết cái này, Hadley lại thay đổi một số thứ. Các chức năng đã từng có trong purrr bây giờ nằm ​​trong một gói hỗn hợp mới có tên là purrrlyr , được mô tả là:

purrrlyr chứa một số hàm nằm ở giao điểm của purrr và dplyr. Chúng đã được loại bỏ khỏi purrr để làm cho gói nhẹ hơn và vì chúng đã được thay thế bằng các dung dịch khác trong ngăn nắp.

Vì vậy, bạn sẽ cần cài đặt + nạp gói đó để đoạn mã bên dưới hoạt động.

Bài gốc

Hadley thường xuyên thay đổi ý định về những gì chúng ta nên sử dụng, nhưng tôi nghĩ rằng chúng ta nên chuyển sang các chức năng trong purrr để có được chức năng theo hàng. Ít nhất, chúng cung cấp cùng một chức năng và có giao diện gần giống như adplytừ plyr .

Có hai chức năng liên quan, by_rowinvoke_rows. Tôi hiểu là bạn sử dụng by_rowkhi bạn muốn lặp qua các hàng và thêm kết quả vào data.frame.invoke_rowsđược sử dụng khi bạn lặp qua các hàng của data.frame và chuyển từng col làm đối số cho một hàm. Chúng tôi sẽ chỉ sử dụng đầu tiên.

Ví dụ

library(tidyverse)

iris %>% 
  by_row(..f = function(this_row) {
    browser()
  })

Điều này cho phép chúng tôi xem nội dung bên trong (để chúng tôi có thể thấy những gì chúng tôi đang làm), cũng giống như làm với nó adply.

Called from: ..f(.d[[i]], ...)
Browse[1]> this_row
# A tibble: 1 × 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1          5.1         3.5          1.4         0.2  setosa
Browse[1]> Q

Theo mặc định, by_rowthêm một cột danh sách dựa trên kết quả đầu ra:

iris %>% 
  by_row(..f = function(this_row) {
      this_row[1:4] %>% unlist %>% mean
  })

cho:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species      .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>    <list>
1           5.1         3.5          1.4         0.2  setosa <dbl [1]>
2           4.9         3.0          1.4         0.2  setosa <dbl [1]>
3           4.7         3.2          1.3         0.2  setosa <dbl [1]>
4           4.6         3.1          1.5         0.2  setosa <dbl [1]>
5           5.0         3.6          1.4         0.2  setosa <dbl [1]>
6           5.4         3.9          1.7         0.4  setosa <dbl [1]>
7           4.6         3.4          1.4         0.3  setosa <dbl [1]>
8           5.0         3.4          1.5         0.2  setosa <dbl [1]>
9           4.4         2.9          1.4         0.2  setosa <dbl [1]>
10          4.9         3.1          1.5         0.1  setosa <dbl [1]>
# ... with 140 more rows

nếu thay vào đó chúng ta trả về a data.frame, chúng ta sẽ nhận được một danh sách có data.frames:

iris %>% 
  by_row( ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

cho:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species                 .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>               <list>
1           5.1         3.5          1.4         0.2  setosa <data.frame [1 × 2]>
2           4.9         3.0          1.4         0.2  setosa <data.frame [1 × 2]>
3           4.7         3.2          1.3         0.2  setosa <data.frame [1 × 2]>
4           4.6         3.1          1.5         0.2  setosa <data.frame [1 × 2]>
5           5.0         3.6          1.4         0.2  setosa <data.frame [1 × 2]>
6           5.4         3.9          1.7         0.4  setosa <data.frame [1 × 2]>
7           4.6         3.4          1.4         0.3  setosa <data.frame [1 × 2]>
8           5.0         3.4          1.5         0.2  setosa <data.frame [1 × 2]>
9           4.4         2.9          1.4         0.2  setosa <data.frame [1 × 2]>
10          4.9         3.1          1.5         0.1  setosa <data.frame [1 × 2]>
# ... with 140 more rows

Cách chúng ta thêm đầu ra của hàm được điều khiển bởi .collatetham số. Có ba tùy chọn: danh sách, hàng, cột. Khi đầu ra của chúng ta có độ dài 1, không quan trọng là chúng ta sử dụng hàng hay cột.

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

cả hai sản xuất:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <dbl>
1           5.1         3.5          1.4         0.2  setosa 2.550
2           4.9         3.0          1.4         0.2  setosa 2.375
3           4.7         3.2          1.3         0.2  setosa 2.350
4           4.6         3.1          1.5         0.2  setosa 2.350
5           5.0         3.6          1.4         0.2  setosa 2.550
6           5.4         3.9          1.7         0.4  setosa 2.850
7           4.6         3.4          1.4         0.3  setosa 2.425
8           5.0         3.4          1.5         0.2  setosa 2.525
9           4.4         2.9          1.4         0.2  setosa 2.225
10          4.9         3.1          1.5         0.1  setosa 2.400
# ... with 140 more rows

Nếu chúng ta xuất data.frame có 1 hàng, thì việc chúng ta sử dụng chỉ quan trọng một chút:

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
      )
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

cả hai đều cho:

# A tibble: 150 × 8
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .row new_col_mean new_col_median
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <int>        <dbl>          <dbl>
1           5.1         3.5          1.4         0.2  setosa     1        2.550           2.45
2           4.9         3.0          1.4         0.2  setosa     2        2.375           2.20
3           4.7         3.2          1.3         0.2  setosa     3        2.350           2.25
4           4.6         3.1          1.5         0.2  setosa     4        2.350           2.30
5           5.0         3.6          1.4         0.2  setosa     5        2.550           2.50
6           5.4         3.9          1.7         0.4  setosa     6        2.850           2.80
7           4.6         3.4          1.4         0.3  setosa     7        2.425           2.40
8           5.0         3.4          1.5         0.2  setosa     8        2.525           2.45
9           4.4         2.9          1.4         0.2  setosa     9        2.225           2.15
10          4.9         3.1          1.5         0.1  setosa    10        2.400           2.30
# ... with 140 more rows

ngoại trừ cột thứ hai có cột được gọi .rowvà cột thứ nhất thì không.

Cuối cùng, nếu đầu ra của chúng ta dài hơn độ dài 1 là a vectorhoặc a data.framevới các hàng, thì điều quan trọng là chúng ta sử dụng hàng hay cột cho .collate:

mtcars[1:2] %>% by_row(function(x) 1:5)
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows")
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")

sản xuất, tương ứng:

# A tibble: 32 × 3
     mpg   cyl      .out
   <dbl> <dbl>    <list>
1   21.0     6 <int [5]>
2   21.0     6 <int [5]>
3   22.8     4 <int [5]>
4   21.4     6 <int [5]>
5   18.7     8 <int [5]>
6   18.1     6 <int [5]>
7   14.3     8 <int [5]>
8   24.4     4 <int [5]>
9   22.8     4 <int [5]>
10  19.2     6 <int [5]>
# ... with 22 more rows

# A tibble: 160 × 4
     mpg   cyl  .row  .out
   <dbl> <dbl> <int> <int>
1     21     6     1     1
2     21     6     1     2
3     21     6     1     3
4     21     6     1     4
5     21     6     1     5
6     21     6     2     1
7     21     6     2     2
8     21     6     2     3
9     21     6     2     4
10    21     6     2     5
# ... with 150 more rows

# A tibble: 32 × 7
     mpg   cyl .out1 .out2 .out3 .out4 .out5
   <dbl> <dbl> <int> <int> <int> <int> <int>
1   21.0     6     1     2     3     4     5
2   21.0     6     1     2     3     4     5
3   22.8     4     1     2     3     4     5
4   21.4     6     1     2     3     4     5
5   18.7     8     1     2     3     4     5
6   18.1     6     1     2     3     4     5
7   14.3     8     1     2     3     4     5
8   24.4     4     1     2     3     4     5
9   22.8     4     1     2     3     4     5
10  19.2     6     1     2     3     4     5
# ... with 22 more rows

Vì vậy, điểm mấu chốt. Nếu bạn muốn các adply(.margins = 1, ...)chức năng, bạn có thể sử dụng by_row.


2
by_rowkhông được dùng nữa, gọi nó là "sử dụng kết hợp của: inheritr :: nest (); dplyr :: mutate (); purrr :: map ()" github.com/hadley/purrrlyr/blob/…
momeara

Đó là rất nhiều của r.
qwr

14

Mở rộng câu trả lời của BrodieG,

Nếu hàm trả về nhiều hơn một hàng, thì thay vì mutate(), do()phải được sử dụng. Sau đó, để kết hợp lại với nhau, hãy sử dụng rbind_all()từ dplyrgói.

Trong dplyrphiên bản dplyr_0.1.2, sử dụng 1:n()trong group_by()mệnh đề không phù hợp với tôi. Hy vọng rằng Hadley sẽ thực hiệnrowwise() sớm.

iris %>%
    group_by(1:nrow(iris)) %>%
    do(do_fn) %>%
    rbind_all()

Kiểm tra hiệu suất,

library(plyr)    # plyr_1.8.4.9000
library(dplyr)   # dplyr_0.8.0.9000
library(purrr)   # purrr_0.2.99.9000
library(microbenchmark)

d1_count <- 1000
d2_count <- 10

d1 <- data.frame(a=runif(d1_count))

do_fn <- function(row){data.frame(a=row$a, b=runif(d2_count))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}

op <- microbenchmark(
        plyr_version = plyr::adply(d1, 1, do_fn),
        dplyr_version = d1 %>%
            dplyr::group_by(1:nrow(d1)) %>%
            dplyr::do(do_fn(.)) %>%
            dplyr::bind_rows(),
        purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
        times=50)

nó có các kết quả sau:

Unit: milliseconds
          expr       min        lq      mean    median        uq       max neval
  plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449    50
 dplyr_version  977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978    50
 purrr_version  609.5790  629.7565  643.8498  644.2505  656.1959  686.8128    50

Điều này cho thấy rằng purrrphiên bản mới là nhanh nhất


1

Một cái gì đó như thế này?

iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)

1
Vâng, đó là một câu trả lời rất cụ thể. Nhưng ví dụ và câu hỏi của tôi đang cố gắng tìm ra dplyrgiải pháp chung cho bất kỳ hàm vô hướng nào.
Stephen Henderson

Nói chung, các hàm nên được vectơ hóa - nếu đó là một hàm lập dị, bạn có thể viết wacky.function <- function(col.1, col.2){...}và sau đó iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length).
colcarroll

Thường thì tôi nên đoán là họ, nhưng tôi nghĩ khi bạn đang sử dụng một cái gì đó như dplyrhoặc plyrhoặc nói, data.tablebạn nên cố gắng sử dụng thành ngữ của họ để mã của bạn không trở thành một hỗn hợp khó chia sẻ các phong cách. Do đó câu hỏi.
Stephen Henderson

Dòng đầu tiên của plyrtài liệu là "plyr là một bộ công cụ giải quyết một loạt vấn đề phổ biến: bạn cần chia một vấn đề lớn thành các phần có thể quản lý được, thao tác trên mỗi phần và sau đó ghép tất cả các phần lại với nhau." Đây dường như là một vấn đề rất khác mà các phép toán cột cơ bản là công cụ tốt nhất. Điều này cũng có thể giải thích tại sao không có lệnh plyr/ "tự nhiên" dplyrđể thực hiện việc này.
colcarroll

5
Để mổ thịt một câu nói nổi tiếng: " Nếu tất cả các bạn có là một plyr bạn sẽ kết thúc sử dụng nó cho một cái búa và một tuốc nơ vít quá "
thelatemail
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.