apply, lapply, sapply, tapply, mapply.. Como é que é mesmo?

Acredito que a esta altura do campeonato todos já devem se questionar sobre a performance do R. Existe um Carma que é carregado de que não é eficiente em termos de gerenciamento de memória e velocidade. Tudo bem, temos que cofessar que estes itens não podem ser classificados como os pontos fortes da linguagem, porém nem tudo está perdido e dependendo da situação, contorná-la pode ser mais simples do que parece, em especial quando o assunto é velocidade.

Escrever loops da forma tradicional pode ser uma verdadeira tortura, tanto para o usuário quanto para o computador. Se você já tentou aninhar mais de 3 for's sabe do que estou falando. Claro que não existe uma ferramenta mágica que vai fazer com que seu loop passe de 2 horas paras 5 segundos, porém com as metafunções da família apply muita coisa pode ser melhorada. As funções da família apply são um conjunto muito poderoso de funções que sob certas condições permitem a aplicação de outras funções em objetos do R .  



Algumas vezes as funções que são utilizadas junto das funções apply são conhecidas como funções anônimas. Isto significa que elas são definidas apenas no contexto do apply e uma vez feito isso, não farão parte do ambiente global. Um exemplo é exibido quando a função lapply é abordada.

apply

Aplica uma função nas margens de um array qualquer. Geralmente é aplicado em uma matriz/dataframe de forma a aplicar uma mesma função em todas as linhas ou colunas daquele objeto. Recebe como argumento um array, a marginal sobre a qual a função será aplicada e a função.

 # apply.R
X = matrix(rnorm(200), ncol=20) # matriz com 20 colunas e 10 linhas
media_linha = apply(X, 1, mean) # média por linha
 [1]  0.04379803  0.16120193  0.18021305 -0.24121940  0.25072885 -0.35231844
 [7]  0.06052024  0.17069682 -0.11181799  0.04517407


Casos Especiais: As funções rowSums, rowMeans, rowSums e colMeans podem ser facilmente substituídas por uma função apply (por exemplo, rowSums(X) = apply(X,1, sum) ), porém no caso destas tarefas em particular, estas funções são super otimizadas.

Obs.: É importante lembrar que em termos de velocidade a função apply não supera tanto um loop normal, mas só o fato de poder escrever o loop em uma linha já é um bom motivo para usá-lo sempre que puder.

lapply

É uma função que é aplicada em cada elemento de um vetor ou cada nó de uma lista. O output é uma lista obrigatoriamente. Recebe como argumentos um vetor/lista e uma função.

 # lapply.R
X = list(x1=c(1,2,NA,4,5), x2=rnorm(100), x3=rnorm(100) )
lapply(X, mean)

$x1
[1] NA

$x2
[1] -0.1908649

$x3
[1] -0.1315371

Como podem ver, a saída é uma lista e o nó x1 ficou com NA. O resultado é claro, uma vez que a função mean por padrão não trata os valores faltantes. Para resolver isto, existem 2 caminhos. O primeiro é declarar a função anônima que foi citada anteriormente, e isto significa definir uma nova função com os argumentos alterados dentro do próprio lapply, e neste caso ficaria  lapply(X, function(x) mean(x, na.rm=T) ) . A outra solução é passar os argumentos via "..." da função lapply e neste caso a solução seria lapply(X, mean, na.rm=T) .

sapply

Similar ao lapply, porém a saída geralmente é simplificada, sendo apenas um vetor. Caso sua saída seja mais de um elemento, a saída deixa de ser um vetor e passa a ser uma matriz. Recebe como argumentos um vetor/lista e uma função. A diferença para o lapply é que o sapply tenta simplificar o resultado, retornando assim um vetor ou algo parecido.

 # sapply.R
sapply(X, function(x)mean(x, na.rm=T) ) # Cálculo da média de cada elemento da lista via função anônima.

tapply

Função com o objetivo de aplicar funções em grupos diferentes. Suponha que você tenha um dataframe com 2 colunas, uma com altura e outra com gênero, e você queira calcula a média de idade para cada um dos gẽneros, então neste caso a função tapply se aplica perfeitamente. Recebe como argumentos um vetor, um vetor com os fatores que irão estratificar o resultado e a função a ser aplicada em cada estrato.

 # tapply.R
X = rnorm(100) 
fator = sample(factor(c('a', 'b')), 100, rep=T) 
tapply(X, fator, mean) # A média dos valores por categoria
          a           b 
-0.08495240 -0.06317896 

mapply

É bem provável que seja a função da família apply menos utilizada, porém também tem sua importância na utilização do R. Esta é uma versão multivariada do sapply e recebe como argumentos uma função e um conjunto de argumentos. A função é aplicada em cada um dos argumentos sequencialmente.

 # mapply.R
mapply(rep, 1:4, 4:1)
[[1]]
[1] 1 1 1 1

[[2]]
[1] 2 2 2

[[3]]
[1] 3 3

[[4]]
[1] 4

No caso do exemplo acima, seria o mesmo que fazer list(rep(1,4), rep(2,3), rep(3,2), rep(4,1)) .

Um comentário: