Aplicar una función a cada fila de una matriz o un marco de datos

129

Supongamos que tengo una matriz by 2 y una función que toma un vector 2 como uno de sus argumentos. Me gustaría aplicar la función a cada fila de la matriz y obtener un n-vector. ¿Cómo hacer esto en R?

Por ejemplo, me gustaría calcular la densidad de una distribución Normal estándar 2D en tres puntos:

bivariate.density(x = c(0, 0), mu = c(0, 0), sigma = c(1, 1), rho = 0){
    exp(-1/(2*(1-rho^2))*(x[1]^2/sigma[1]^2+x[2]^2/sigma[2]^2-2*rho*x[1]*x[2]/(sigma[1]*sigma[2]))) * 1/(2*pi*sigma[1]*sigma[2]*sqrt(1-rho^2))
}

out <- rbind(c(1, 2), c(3, 4), c(5, 6))

¿Cómo aplicar la función a cada fila de out?

¿Cómo pasar valores para los otros argumentos además de los puntos a la función en la forma que especifique?

Tim
fuente

Respuestas:

180

Simplemente usa la apply()función:

R> M <- matrix(1:6, nrow=3, byrow=TRUE)
R> M
     [,1] [,2]
[1,]    1    2
[2,]    3    4
[3,]    5    6
R> apply(M, 1, function(x) 2*x[1]+x[2])
[1]  4 10 16
R> 

Esto toma una matriz y aplica una función (tonta) a cada fila. Pasa argumentos adicionales a la función como cuarto, quinto, ... argumentos a apply().

Dirk Eddelbuettel
fuente
¡Gracias! ¿Qué pasa si las filas de la matriz no son el primer argumento de la función? ¿Cómo especificar a qué argumento de la función se asigna cada fila de la matriz?
Tim
Lea la ayuda para apply(): barre por fila (cuando el segundo argumento es 1, si no por columna), y la fila actual (o col) es siempre el primer argumento. Así se definen las cosas.
Dirk Eddelbuettel
@Tim: si usa una función R interna y la fila no es el primer argumento, haga lo que hizo Dirk y cree su propia función personalizada donde fila es el primer argumento.
Joris Meys
3
El paquete plyr proporciona una amplia gama de estos tipos de funciones de aplicación. También proporciona más funcionalidad, incluido el procesamiento en paralelo.
Paul Hiemstra
66
@ cryptic0 esta respuesta llega tarde, pero para los googlers, el segundo argumento en apply es el MARGINargumento. Aquí significa aplicar la función a las filas (la primera dimensión en dim(M)). Si fuera 2, aplicaría la función a las columnas.
De Novo
17

En caso de que quiera aplicar funciones comunes, tales como suma o media, se debe utilizar rowSumso rowMeansya que son más rápido que el apply(data, 1, sum)enfoque. De lo contrario, quédate con apply(data, 1, fun). Puede pasar argumentos adicionales después del argumento FUN (como Dirk ya sugirió):

set.seed(1)
m <- matrix(round(runif(20, 1, 5)), ncol=4)
diag(m) <- NA
m
     [,1] [,2] [,3] [,4]
[1,]   NA    5    2    3
[2,]    2   NA    2    4
[3,]    3    4   NA    5
[4,]    5    4    3   NA
[5,]    2    1    4    4

Entonces puedes hacer algo como esto:

apply(m, 1, quantile, probs=c(.25,.5, .75), na.rm=TRUE)
    [,1] [,2] [,3] [,4] [,5]
25%  2.5    2  3.5  3.5 1.75
50%  3.0    2  4.0  4.0 3.00
75%  4.0    3  4.5  4.5 4.00
aL3xa
fuente
15

Aquí hay un breve ejemplo de cómo aplicar una función a cada fila de una matriz. (Aquí, la función aplicada normaliza cada fila a 1.)

Nota: El resultado de la apply()tuvo que ser transpuesta utilizando t()para obtener el mismo diseño que la matriz de entrada A.

A <- matrix(c(
  0, 1, 1, 2,
  0, 0, 1, 3,
  0, 0, 1, 3
), nrow = 3, byrow = TRUE)

t(apply(A, 1, function(x) x / sum(x) ))

Resultado:

     [,1] [,2] [,3] [,4]
[1,]    0 0.25 0.25 0.50
[2,]    0 0.00 0.25 0.75
[3,]    0 0.00 0.25 0.75
Viliam Simko
fuente
6

El primer paso sería hacer que la función sea objeto, luego aplicarla. Si desea un objeto de matriz que tenga el mismo número de filas, puede predefinirlo y usar el formulario object [] como se ilustra (de lo contrario, el valor devuelto se simplificará a un vector):

bvnormdens <- function(x=c(0,0),mu=c(0,0), sigma=c(1,1), rho=0){
     exp(-1/(2*(1-rho^2))*(x[1]^2/sigma[1]^2+
                           x[2]^2/sigma[2]^2-
                           2*rho*x[1]*x[2]/(sigma[1]*sigma[2]))) * 
     1/(2*pi*sigma[1]*sigma[2]*sqrt(1-rho^2))
     }
 out=rbind(c(1,2),c(3,4),c(5,6));

 bvout<-matrix(NA, ncol=1, nrow=3)
 bvout[] <-apply(out, 1, bvnormdens)
 bvout
             [,1]
[1,] 1.306423e-02
[2,] 5.931153e-07
[3,] 9.033134e-15

Si desea utilizar otros parámetros distintos a los predeterminados, la llamada debe incluir argumentos con nombre después de la función:

bvout[] <-apply(out, 1, FUN=bvnormdens, mu=c(-1,1), rho=0.6)

apply () también se puede usar en matrices de dimensiones superiores y el argumento MARGIN puede ser un vector y un solo entero.

IRTFM
fuente
4

Aplicar funciona bien, pero es bastante lento. Usar sapply y vapply podría ser útil. El rowwise de dplyr también podría ser útil Veamos un ejemplo de cómo hacer un producto de fila de cualquier marco de datos.

a = data.frame(t(iris[1:10,1:3]))
vapply(a, prod, 0)
sapply(a, prod)

Tenga en cuenta que asignar a variable antes de usar vapply / sapply / apply es una buena práctica ya que reduce mucho el tiempo. Veamos resultados de microbenchmark

a = data.frame(t(iris[1:10,1:3]))
b = iris[1:10,1:3]
microbenchmark::microbenchmark(
    apply(b, 1 , prod),
    vapply(a, prod, 0),
    sapply(a, prod) , 
    apply(iris[1:10,1:3], 1 , prod),
    vapply(data.frame(t(iris[1:10,1:3])), prod, 0),
    sapply(data.frame(t(iris[1:10,1:3])), prod) ,
    b %>%  rowwise() %>%
        summarise(p = prod(Sepal.Length,Sepal.Width,Petal.Length))
)

Observe detenidamente cómo se usa t ()

Pratham
fuente
Podría ser más justo comparar la familia de postulantes si la usó b <- t(iris[1:10, 1:3])y apply(b, 2 prod).
DaSpeeg
2

Otro enfoque si desea usar una porción variable del conjunto de datos en lugar de un solo valor es usar rollapply(data, width, FUN, ...). El uso de un vector de anchos le permite aplicar una función en una ventana variable del conjunto de datos. He usado esto para construir una rutina de filtrado adaptativo, aunque no es muy eficiente.

DWAHL
fuente