Aplique uma função a todas as linhas de uma matriz ou quadro de dados

129

Suponha que eu tenha uma matriz 2 e uma função que use um vetor 2 como um de seus argumentos. Gostaria de aplicar a função a cada linha da matriz e obter um vetor n. Como fazer isso em R?

Por exemplo, eu gostaria de calcular a densidade de uma distribuição normal padrão 2D em três pontos:

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

Como aplicar a função a cada linha de out?

Como passar valores para os outros argumentos além dos pontos para a função da maneira que você especificar?

Tim
fonte

Respostas:

180

Você simplesmente usa a apply()função:

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> 

Isso pega uma matriz e aplica uma função (boba) a cada linha. Você passa argumentos extras para a função como quarto, quinto, ... argumentos para apply().

Dirk Eddelbuettel
fonte
Obrigado! E se as linhas da matriz não forem o primeiro argumento da função? Como especificar a qual argumento da função cada linha da matriz está atribuída?
Tim
Leia a ajuda de apply()- varre por linha (quando o segundo argumento é 1, mais por coluna), e a linha (ou coluna) atual é sempre o primeiro argumento. É assim que as coisas são definidas.
precisa saber é o seguinte
@ Tim: se você usar uma função R interna e a linha não for o primeiro argumento, faça como Dirk e faça sua própria função personalizada, em que linha é o primeiro argumento.
Joris Meys
3
O pacote plyr fornece uma ampla variedade desses tipos de funções aplicáveis. Ele também fornece mais funcionalidade, incluindo processamento paralelo.
Paul Hiemstra
6
@ cryptic0 essa resposta está atrasada, mas para os googlers, o segundo argumento em aplicar é o MARGINargumento. Aqui, significa aplicar a função às linhas (a primeira dimensão em dim(M)). Se fosse 2, aplicaria a função às colunas.
De Novo
17

Caso deseje aplicar funções comuns como soma ou média, você deve usar rowSumsou rowMeansuma vez que são mais rápidas que a apply(data, 1, sum)abordagem. Caso contrário, fique com apply(data, 1, fun). Você pode passar argumentos adicionais após o argumento FUN (como Dirk já sugeriu):

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

Então você pode fazer algo assim:

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
fonte
15

Aqui está um pequeno exemplo de aplicação de uma função a cada linha de uma matriz. (Aqui, a função aplicada normaliza todas as linhas para 1.)

Nota: O resultado do apply()teve que ser transposto usando t()para obter o mesmo layout que a 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
fonte
6

O primeiro passo seria criar o objeto de função e aplicá-lo. Se você deseja um objeto de matriz que tenha o mesmo número de linhas, é possível predefini-lo e usar o formulário [] conforme ilustrado (caso contrário, o valor retornado será simplificado para um vetor):

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

Se você deseja usar outros parâmetros que não sejam o padrão, a chamada deve incluir argumentos nomeados após a função:

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

apply () também pode ser usado em matrizes dimensionais mais altas e o argumento MARGIN pode ser um vetor e um único inteiro.

IRTFM
fonte
4

A aplicação faz bem o trabalho, mas é bastante lenta. Usar sapply e vapply pode ser útil. O rowwise do dplyr também pode ser útil. Vamos ver um exemplo de como criar produtos com linhas de qualquer quadro de dados.

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

Observe que atribuir a variável antes de usar vapply / sapply / apply é uma boa prática, pois reduz muito o tempo. Vamos ver os resultados da marca de microbench

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

Dê uma olhada cuidadosa em como t () está sendo usado

Pratham
fonte
Pode ser mais justo comparar a família de aplicativos se você usou b <- t(iris[1:10, 1:3])e apply(b, 2 prod).
DaSpeeg
2

Outra abordagem, se você deseja usar uma parte variável do conjunto de dados em vez de um único valor, é usar rollapply(data, width, FUN, ...). O uso de um vetor de larguras permite aplicar uma função em uma janela variável do conjunto de dados. Eu usei isso para criar uma rotina de filtragem adaptativa, embora não seja muito eficiente.

DWAHL
fonte