¿Aplicar una función a cada fila de una tabla usando dplyr?

Cuando trabajo con plyr , a menudo me resultó útil usar adply para funciones escalares que tengo que aplicar a todas y cada una de las filas.

p.ej

 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 

Ahora estoy usando dplyr más, me pregunto si hay una forma ordenada / natural de hacer esto. Como esto NO es lo que quiero:

 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 

A partir de dplyr 0.2 (creo) rowwise() se implementa, por lo que la respuesta a este problema se convierte en:

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

Necesitas agrupar por fila:

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

Esto es lo que hice el 1 en adply .

El enfoque idiomático será crear una función adecuadamente vectorizada.

R proporciona pmax que es adecuado aquí, sin embargo, también proporciona Vectorize como un contenedor para mapply para permitirle crear una versión arbitraria vectorizada de una función arbitraria.

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

Tenga en cuenta que implementar la vectorización en C / C ++ será más rápido, pero no hay un paquete magicPony que escriba la función por usted.

Extendiendo la respuesta de BrodieG,

Si la función devuelve más de una fila, en lugar de mutate() , do() debe usarse. Luego, para combinarlo de nuevo, use rbind_all() del paquete dplyr .

En la versión dplyr_0.1.2 , usar 1:n() en la cláusula group_by() no funciona para mí. Esperemos que Hadley implemente rowwise() pronto.

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

Probando el rendimiento,

 library(dplyr) library(plyr) 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))} op <- microbenchmark( dplyr_version = d1 %>% group_by(1:nrow(d1)) %>% do(do_fn) %>% rbind_all(), plyrs_version = adply(d1, 1, do_fn), times=10) 

tiene los siguientes resultados:

 Unit: milliseconds expr min lq median uq max neval dplyr_version 474.8283 509.5577 517.4723 549.9897 703.3613 10 plyrs_version 830.1255 831.0652 862.5729 903.2783 1039.8510 10 

Actualización 2017-08-03

Después de escribir esto, Hadley cambió algunas cosas de nuevo. Las funciones que solían estar en purrr ahora están en un nuevo paquete mixto llamado purrrlyr , descrito como:

purrrlyr contiene algunas funciones que se encuentran en la intersección de purrr y dplyr. Han sido eliminados de Purrr para hacer que el paquete sea más liviano y porque han sido reemplazados por otras soluciones en el tidyverse.

Por lo tanto, deberá instalar + cargar ese paquete para que el siguiente código funcione.

Publicación original

Hadley frecuentemente cambia de opinión acerca de lo que deberíamos usar, pero creo que se supone que debemos cambiar a las funciones en purrr para obtener la funcionalidad por filas. Al menos, ofrecen la misma funcionalidad y tienen casi la misma interfaz que adply from plyr .

Hay dos funciones relacionadas, by_row y invoke_rows . Tengo entendido que usa by_row cuando quiere pasar por las filas y agregar los resultados al data.frame. invoke_rows se usa cuando invoke_rows filas de un data.frame y pasa cada col como argumento a una función. Solo usaremos el primero.

Ejemplos

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

Esto nos permite ver las adply internas (para que podamos ver lo que estamos haciendo), que es lo mismo que hacer con adply .

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

De forma predeterminada, by_row agrega una columna de lista basada en el resultado:

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

da:

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

si, en cambio, devolvemos un data.frame , obtenemos una lista con data.frame s:

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

da:

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

Cómo agregamos que la salida de la función está controlada por el .collate . Hay tres opciones: lista, filas, cols. Cuando nuestro resultado tiene longitud 1, no importa si utilizamos filas o columnas.

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

ambos producen:

 # A tibble: 150 × 6 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .out       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 

Si generamos un data.frame con 1 fila, solo importa un poco lo que usamos:

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

ambos dan:

 # A tibble: 150 × 8 Sepal.Length Sepal.Width Petal.Length Petal.Width Species .row new_col_mean new_col_median         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 

excepto que el segundo tiene la columna llamada .row y el primero no.

Finalmente, si nuestro resultado es más largo que la longitud 1 ya sea como un vector o como un data.frame con filas, entonces importa si usamos rows o cols para .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") 

produce, respectivamente:

 # A tibble: 32 × 3 mpg cyl .out    1 21.0 6  2 21.0 6  3 22.8 4  4 21.4 6  5 18.7 8  6 18.1 6  7 14.3 8  8 24.4 4  9 22.8 4  10 19.2 6  # ... with 22 more rows # A tibble: 160 × 4 mpg cyl .row .out     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        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 

Entonces, línea de fondo. Si desea la funcionalidad adply(.margins = 1, ...) , puede usar by_row .

¿Algo como esto?

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