studio - ¿Aplicar una función a cada fila de una tabla usando dplyr?
seleccionar columnas en r (6)
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
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
<dbl> <dbl> <dbl> <dbl> <fctr>
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
<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
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
<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ó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
<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
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
<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
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
<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
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)
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))
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
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
.