Hackeando o R: estratégia split-apply-combine
Veja como aplicar uma estratégia de leitura usando as opções: dplyr + tidyr + purrr
Post publicado em: https://gomesfellipe.github.io/post/2019-04-05-split-apply-combine/split-apply-combine/
O MÉTODO SPLIT-APPLY-COMBINE
Geral na análise de conhecimento de dados, incluindo o comportamento dos dados, o seu comportamento de concordância com alguns threads.
No famous paper A estratégia de combinação de aplicação dividida para análise de dados , Hadley Wickham descreve uma abordagem de divisão-aplicação-combinação (split-apply-combinar) como uma das mais comuns em uma análise de dados. Os serviços podem ser implementados como guias de tarefas, como por exemplo os caminhos de execução
split()
+lapply()
+do.call(rbind, ...)
ddply()
do pacoteplyr
group_by
+do()
split()
+map_dfr()
Todos esses exemplos atendem à maioria dos casos que deseja-se utilizar a abordagem “split-apply-combine”, porém, veja por exemplo este tópico na community.rstudio.com criado no final de 2017 em que ocorre um comunicado que a função do()
será descontinuada
Ou ainda, confira quando foi o último lançamento de atualização do pacote plyr
no CRAN (foi em junho de 2016).
Com a proposta de mais eficiência e legibilidade do código, atualmente existem maneiras mais sofisticadas e modernas de se realizar esta tarefa com pacotes que foram atualizados já este ano de 2019. Veja nas seções a seguir o aumento de produtividade que é possível se obter combinando os pacotes dplyr
, tidyr
e purrr
da coleção de pacotes do tidyverse
.
USANDO SÓ O DPLYR
Usamos “split-apply-combine” implicitamente o tempo todo quando utilizamos as funções groupy_by()
+ summarise()
do pacote dplyr
Poderíamos facilmente reproduzir o exemplo da imagem do post com os seguintes comandos:
library(dplyr)
data <- tibble(x = c("A", "A", "B", "B", "C", "C"),
y = c(0,1,2,3,4,5)) data %>% # input data
group_by(x) %>% # split
summarise(data = mean(y)) # apply/combine
Retorna:
## # A tibble: 3 x 2
## x data
## <chr> <dbl>
## 1 A 0.5
## 2 B 2.5
## 3 C 4.5
Essa sequência de códigos aplica a abordagem implicitamente, agrupando os dados de acordo com a variável selecionada e em seguida aplicando a operação e combinando os resultados em uma matriz resumida
USANDO DPLYR + TIDYR + PURRR
Poderíamos ter realizado a mesma operação de forma explícita com o auxílio das funções nest()
, map()
, mutate()
e unnest()
dos pacotes dplyrtidyr
e purrr
, veja:
# Pacotes necessários
library(tidyr)
library(purrr)# Dados
data <- tibble(x = c("A", "A", "B", "B", "C", "C"),
y = c(0,1,2,3,4,5))
# Codigos
data %>% # Input Data
nest(-x) %>% # Split
mutate(data = map(data, ~mean(.x$y))) %>% # Apply
unnest() # Combine
Retorna:
## # A tibble: 3 x 2
## x data
## <chr> <dbl>
## 1 A 0.5
## 2 B 2.5
## 3 C 4.5
Note que obtemos a mesma saída do código anterior
SPLIT-APPLY-COMBINE COM FUNÇÕES COMPLEXAS
Você deve estar se perguntando:
“Tá, eu tenho um atalho para usar a estratégia “split-apply-combine” com pacotedplyr
, por que eu preciso usar os dados aninhados?”
Trabalhar com dados aninhados permite aplicar qualquer tipo de função em partições do conjunto de dados e juntar os resultados em um objeto do tipo tibble
cujo print()
é um “método aprimorado que os torna mais fáceis de usar com grandes conjuntos de dados contendo objetos complexos”.
Veja o seguinte exemplo:
Primeiramente, imagine que você queira calcular a média de mpg
por cyl
dos dados mtcars
(nativos do R), bastaria utilizar a sequência de códigos:
mtcars %>% # input data
group_by(cyl) %>% # split
summarise(media = mean(mpg)) # apply/combine
Retorna:
## # A tibble: 3 x 2
## cyl media
## <dbl> <dbl>
## 1 4 26.7
## 2 6 19.7
## 3 8 15.1
Vejamos a seguir o uso da estratégia em situações mais complexas
EM AJUSTES DE MODELOS
E se precisássemos calcular algo mais elaborado, como por exemplo ajustar k=3 regressões lineares: y_k=b_0k+b_1k∗x_k (com y_k= mpg
, x_k= disp
para cada k= cyl
) para estudar os coeficientes estimados, o que aconteceria se utilizássemos o código abaixo ?
Spoiler: Note que pelo fato da saída da função lm
não retornar apenas uma única variável para sumarizar obteremos um Error
:
mtcars %>% # input data
group_by(cyl) %>% # split
summarise(lm = lm(mpg ~ disp)) # apply/combine
Retorna:
## Error: Column `lm` must be length 1 (a summary value), not 12
O erro nos diz: “A coluna lm
deve ter o comprimento 1, não 12” ou seja, o resultado precisa ser um valor de resumo e não todo o resultado do ajuste dos modelos.
Agora vejamos utilizando a abordagem split-apply-combine
que irá nos permitir aplicar qualquer tipo de função nos dados agrupados por pela variável cyl
:
as_tibble(mtcars) %>% # input data
nest(-cyl) %>% # split
mutate(lm = map(data, ~lm(mpg ~ disp, data = .x) %>% # apply
broom::tidy())) %>%
unnest(lm) # combine
Retorna:
## # A tibble: 6 x 6
## cyl term estimate std.error statistic p.value
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 6 (Intercept) 19.1 2.91 6.55 0.00124
## 2 6 disp 0.00361 0.0156 0.232 0.826
## 3 4 (Intercept) 40.9 3.59 11.4 0.00000120
## 4 4 disp -0.135 0.0332 -4.07 0.00278
## 5 8 (Intercept) 22.0 3.35 6.59 0.0000259
## 6 8 disp -0.0196 0.00932 -2.11 0.0568
Com o auxílio do pacote broom
obtemos saídas de dados arrumados e juntamos os resultados finais da regressão em uma única tabela de maneira prática.
NA CONSTRUÇÃO DE GRÁFICOS
Veja um outro exemplo de uso aplicando uma função para criar gráficos, agora com ggplot:
library(ggplot2)
library(gridExtra)
library(magrittr)plot_list <-
mtcars %>% # input data
nest(-cyl) %>% # split/apply ↓
mutate(plots = map(data, ~ ggplot(.x, aes(x=disp, y=mpg))+
geom_point()+
geom_smooth(method = "lm"))) %$%
plots# Combine para printar:
invoke(grid.arrange,plot_list, ncol=1) # ou: grid.arrange(grobs = plot_list, ncol=1)
# Combine para salvar:
walk2(paste0("plot",1:3,".png"), plot_list, ~ggsave(.x,.y))
Retorna:
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image
Isso informa que as imagens foram salvas no diretório de trabalho com estas dimensões
CRIANDO TABELAS
Por fim, um exemplo utilizando o pacote flextable.
flextable_custom()
Utilize uma função que possa ser adaptada para gerar uma tabela já customizada com o pacote flexível e uma função save_flextable()
inspirada em uma tarefa que não tenha stackoverflowing Como salvar uma tabela como flextable como png no R? .
Veja:
library(flextable)
source("https://raw.githubusercontent.com/gomesfellipe/functions/master/flextable_custom.R")
source("https://raw.githubusercontent.com/gomesfellipe/functions/master/save_flextable.R")tabela_list <-
head(mtcars,7) %>% # input data
nest(-cyl) %$% data %>% # apply
map(~flextable_custom(.x)) # apply / combine
# Veja a tabela:
tabela_list[[1]]
# Combine para salvar:
walk2(paste0("tab",1:3,".png"), tabela_list, ~save_flextable(.y,.x))
CONCLUSÃO
Vimos aqui como funciona uma estratégia e alguns exemplos de uso, porém, existem infinitas outras aplicações para esse tipo de abordagem com os dados arrumados. Esta tarefa pode ser bem produtiva e poupar muitas linhas de código!
REFERÊNCIAS
Além das referências deixadas aqui algumas sugestões de leitura: