Análise de Dados em R (FNDE) - Módulo 2

Sessão 3 - Funções da família apply

Allan Vieira
março de 2018

Objetivos



  • aprender as principais funções da família apply;

  • saber quando utilizá-las para evitar loops.

Introdução (1)


  • No início do tópico anterior (Loops), dissemos que seria bom evitar laços sempre que possível;


  • No R, há uma família de funções que nos permitem substituir loops e deixar a programação muito mais eficiente e elegante;


  • elas são uma grande ferramenta para que façamos Análise de Dados em R, pois executam combinações complexas nos dados com poucas linhas de código;

Introdução (2)


  • As funções da família apply são consideradas funções de mais alta ordem no R (higher-order functions): são funções que recebem funções como argumentos;


  • São chamadas de Functionals;


  • Essa família de funções é uma especificidade do R;

Introdução (3)


  • A família apply é composta por uma série de funções que permitem manipular “fatias” de dados de matrizes, arrays, data.frames e listas de forma iterativa - ou seja, repetindo procedimentos;


  • Ao possibilitarem o cruzamento de dados em diversas formas, essas funções acabam evitando o uso explícito de loops n linguagem R;


  • Na verdade, dentro das funções da família apply há loops implementados em linguagem C;


  • Além de rapidez, ganhamos principalmente simplicidade de código ao usar a família apply: ao invés de termos que ficar digitando várias linhas com for, while, etc, precisaremos apenas de uma única linha com alguma das funções apply e seus argumentos;

Introdução (4)


  • O funcionamento básico de uma função do tipo apply é: receber uma matriz, array, data.frame ou lista como input e aplicar uma função (com um ou vários argumentos) a pedaços desse objeto de input;


  • A função chamada pode ser:
    • uma função que já existe no R: funções que geram um número/escalar como sum e mean; funções que façam um subsetting ou uma transformaçãos nos dados; ou ainda funções que se comportam de forma vetorizada e produzem um objeto mais complexo como uma matriz ou data.frame;
    • uma função anônima criada por você para efetuar alguma operação semelhante as supracitadas, mas de forma personalizada (veremos isso mais a frente).


  • A família apply é composta pelas seguintes funções: apply(), lapply(), sapply(), vapply(), mapply() e tapply(). Como e quando usar essas funções dependem do tipo de objeto em que vamos aplicá-las e também o tipo de objeto que desejamos ter na saída.

apply() (1)

  • É a patriarca da família apply;

  • Basicamente, apply() opera em arrays. Para simplificar o entendimento, podemos dizer que é utilizada para matrizes (que são arrays de duas dimensões como vimos no módulo 1);

  • apply() vai aplicar alguma função às linhas ou às colunas de uma matriz;

  • Exemplo: calcular a média das linhas ou das colunas de uma matriz.

apply() (1)

  • Vejamos a estrutura e argumentos da função apply():
args(apply)
function (X, MARGIN, FUN, ...) 
NULL
  • X é um array ou matriz (array de dimensão 2);

  • FUN é a função que você vai aplicar aos dados;

  • MARGIN define onde a função (a definir) será aplicada: se MARGIN=1, a função será aplicada nas linhas; se MARGIN=2, a função será aplicada nas colunas. Caso você deseje aplicar a função tanto às linhas quanto às colunas, você deve usar MARGIN=c(1,2).

apply() (2)

  • Veja um exemplo da utilização de apply():
# Construindo uma matriz 5x6
X <- matrix(1:30, nrow=5, ncol=6)
X
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    6   11   16   21   26
[2,]    2    7   12   17   22   27
[3,]    3    8   13   18   23   28
[4,]    4    9   14   19   24   29
[5,]    5   10   15   20   25   30
# somar o TOTAL de cada coluna com apply()
apply(X, 2, sum)
[1]  15  40  65  90 115 140
  • Para entendermos o que está sendo feito no código ao lado, vejamos a figura:

  • Figura 2 Fonte: Datacamp

apply(X, 2, sum) siginifica: aplique a função sum sobre as colunas (ou ao longo da margem/dimensão 2) da matriz X.

apply() - OBSERVAÇÕES (1)

  • Para somas e médias das dimensões de uma matriz, o R já possui alguns atalhos, os quais inclusive foram apresentados no módulo 1:

    • rowSums(X) equivale a apply(X,1, sum);
    • rowMeans(X) equivale a apply(X,1, mean);
    • colSums(X) equivale a apply(X,2, sum);
    • colMeans(X) equivale a apply(X,2, mean);
    • rowSums(X, dims=2) equivale a apply(X,c(1,2), sum);
    • rowMeans(X, dims=2) equivale a apply(X,c(1,2), mean);
  • As funções atalhos são mais rápidas que suas respectivas versões em apply. No entanto, há alguns casos em que não temos funções atalhos e fatalmente teremos que usar apply();

  • Há casos em que temos que criar funções anônimas dentro da chamada de apply() para executar um cálculo ou operação personalizada (daqui 2 slides !!).

apply() - OBSERVAÇÕES (2)

  • Caso você deseje especificar/passar algum parâmetro da função (FUN) a ser utilizada, pode proceder como no exemplo a seguir:
# Construindo uma matriz 5x6
X <- matrix(1:30, nrow=5, ncol=6)

# inserindo alguns NA's
X[1,2] <- NA
X[4,5] <- NA

X
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1   NA   11   16   21   26
[2,]    2    7   12   17   22   27
[3,]    3    8   13   18   23   28
[4,]    4    9   14   19   NA   29
[5,]    5   10   15   20   25   30
# somar o valor de cada coluna com apply(), agora incluindo o parâmetro na.rm= TRUE
apply(X, 2, sum, na.rm = TRUE)
[1]  15  34  65  90  91 140
  • Note que o parâmetro na.rm, embora pertencente a função sum(), não é passado dentro desta, mas sim como um argumento de apply(). Será a função apply() que se encarregará de direcionar este argumento para a função sum();

  • Veja a diferença caso não tivessemos especificado na.rm = TRUE

apply(X, 2, sum)
[1]  15  NA  65  90  NA 140

apply() - OBSERVAÇÕES (3)


  • E se queremos fazer uma operação específica com as linhas ou colunas da matriz, cuja função não está implementada em R?
  • Há duas opções:

    • criamos a nossa própria função dentro de apply - é o que se chama de anonymous function;
    • ou criamos uma função fora da função apply() e depois chamamos a função criada (utilizando o nome atribuído).
  • Vejamos um exemplo …

apply() - OBSERVAÇÕES (4)

  • Considere a situação em que desejamos pegar o valor MÍNIMO de cada linha e aplicarmos uma taxa de correção de 25%, salvando os resultados num vetor:
# criando matriz
set.seed(1984) # semente para reproducibilidade dos valores
M <- matrix(sample(1:1000, 30), 5)
M
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]  659  859  874   23  599  154
[2,]  437   33   12  295    4  826
[3,]  373  445  698  654  600  566
[4,]  330  824  711  906  839  493
[5,]  735  213  200  202  780  538
# 1º caso - utlizando função anônima
apply(M, 1, function(x) min(x)*1.025)
[1]  23.575   4.100 382.325 338.250 205.000
# 2º caso - criando uma função fora de apply()
myfun <- function(x) min(x)*1.025

apply(M, 1, myfun)
[1]  23.575   4.100 382.325 338.250 205.000
  • Os resultados são exatamente os mesmos!

  • Note que a criação da função anônima se dá da mesma forma em que criaríamos uma função normal, conforme visto no primeiro capítulo.

  • As únicas diferenças são: a função é criada dentro do argumento FUN da função da família apply e não atribuímos qualquer nome para a função que está sendo criada.

lapply() (1)

  • lapply() é uma das funções mais básicas da família apply;
  • Ela receberá como input uma lista, aplicará uma função aos elementos desta lista, retornando também uma lista;
  • Uma observação a ser feita é que, caso o objeto inputado não seja uma lista, internamente, lapply() forçará esse objeto a ser uma lista utilizando as.list();
  • Portanto, fica claro o motivo do l em lapply();
  • Veja a imagem exemplificando o funcionamento de lapply():

  • Figura 3

Fonte: Advanced R

lapply() (2)


  • data.frames também são em sua essência uma lista (uma lista de vetores-coluna);
  • Portanto, data.frames também podem ser passados como argumento de lapply();
  • Você pode passar também um vetor como input – internamente lapply() irá transformá-lo em uma lista.

lapply() (3)

  • A estrutura de argumentos de lapply() é bem parecida com a função apply();
  • A diferença é que desta vez não temos o argumento MARGIN.
args(lapply)
function (X, FUN, ...) 
NULL
  • Veja um exemplo do uso de lapply():
# criando 3 matrizes:
A <- matrix(1:9, 3)
B <- matrix(4:15, 4)
C <- matrix(rep(8:10, 2), 3)

# Criando lista de matrizes
MyList <- list(A,B,C)

# Extratraindo a primeira linha de todas as matrizes da lista `MyList`
lapply(MyList,"[", 1,) 
[[1]]
[1] 1 4 7

[[2]]
[1]  4  8 12

[[3]]
[1] 8 8
# note como é passado o argumento da primira linha
  • A figura a seguir auxilia na compreensão do exemplo:

  • Figura 3

Fonte: Datacamp.

  • Para extrair somente o segundo elemento da primeira linha de cada matriz, por exemplo, passaríamos o índice da coluna além da linha:
lapply(MyList,"[", 1,2) 
[[1]]
[1] 4

[[2]]
[1] 8

[[3]]
[1] 8

lapply() (4)

  • Vejamos como ficaria lapply, utilizando uma função anônima. Vamos tentar calcular a média de todas as primeiras colunas das matrizes A, B, C do exemplo anterior:
lapply(MyList, function(x) mean(x[,1],na.rm=TRUE))
[[1]]
[1] 2

[[2]]
[1] 5.5

[[3]]
[1] 9
# OU
lapply(MyList, function(x, ...) mean(x[,1]), na.rm=TRUE)
[[1]]
[1] 2

[[2]]
[1] 5.5

[[3]]
[1] 9
# perceba o uso de ... e o posicionamento de na.rm nas duas formas equivalentes acima
# OU AINDA:
# de uma maneira menos eficiente (porque teríamos que criar um vetor, ocupando mais memória) e mais complicada porque teríamos que usar 2 vezes lapply()

out <- lapply(MyList, "[", ,1) 
# esse só funciona com lapply. Com sapply não dá certo por causa da 3ª vírgula, me parece. Como sapply() é um wrapper para lapply, na hora de ela tentar mandar para lapply, ela entende a 3ª vírgula como se fosse um agumento e não um parâmetro.

lapply(out, mean)
[[1]]
[1] 2

[[2]]
[1] 5.5

[[3]]
[1] 9
# mean aplicado sobre uma lista não funciona
# ... então não adiantaria usar o código abaixo:
# mea