Gráficos alternativos para grandes conjuntos de dados

Recentemente, ao tentar fazer gráficos exploratórios de um grande conjunto de dados (~300 mil observações), fiz uma rápida busca na internet e achei alternativas interessantes aos histogramas de frequência e densidade e também aos famosos boxplots. Abaixo, apresento exemplos do que encontrei.

Nesse roteiro, todos os gráficos utilizarão como base o pacote ggplot2, e algumas manipulações dos dados com o pacote dplyr. Ah, e eu também uso o pacote cowplotpara tornar os plots do ggplot estéticamente melhores.

library(ggplot2)
library(dplyr)
library(cowplot)
library(lvplot)

Vamos usar o conjunto de dados ontime do pacote lvplot, que detalha o desempenho no horário dos vôos nacionais dos EUA em janeiro de 2015, veja uma amostra dos dados na Tabela @ref(tab:dados).

Algumas linhas da tabela de dados.
FlightDate UniqueCarrier FlightNum DepTime ArrTime TaxiOut TaxiIn
2015-01-01 AA 1 0855 1237 17 7
2015-01-02 AA 1 0850 1211 15 9
2015-01-03 AA 1 0853 1151 15 13
2015-01-04 AA 1 0853 1218 14 19
2015-01-05 AA 1 0853 1222 27 24
2015-01-06 AA 1 0856 1300 85 4
2015-01-07 AA 1 0859 1221 29 12
2015-01-08 AA 1 0856 1158 26 3
2015-01-09 AA 1 0901 1241 43 4
2015-01-10 AA 1 0903 1235 37 10

Comecemos com os histogramas!

Histogramas

O primeiro gráfico para comparar a distribuição de variáveis contínuas é o histograma de frequência (Figura @ref(fig:freq)) ou densidade (Figura @ref(fig:densi)). Muitas vezes o que queremos fazer é comparar os histogramas em função de algum variável categórica, como no exemplo abaixo, onde criamos os histogramas da variável TaxiOut1 em funcão das companhias aéreas (UniqueCarrier).

hi <- ggplot(ontime, aes(x = TaxiOut, fill = UniqueCarrier)) + 
  facet_wrap(~UniqueCarrier, scales = "free") + #deixei as escalas livres
  scale_x_log10() +
  theme(legend.position = "none")
hi + geom_histogram()
Histograma de frequência por companhia aérea.

Histograma de frequência por companhia aérea.

hi + geom_density()
Histograma de densidade por companhia aérea.

Histograma de densidade por companhia aérea.

Como os histogramas separados em cada plot, às vezes fica difícil compará-los para entender possíveis diferenças entre os grupos analisados.

Alternativa 1: Ridgeline plots

Uma alternativa ao histograma de densidade são os ridgeline plots do pacote ggridges que são gráficos parcialmente sobrepostos criando a impressão de uma cadeia de montanhas (Figura @ref(fig:ridge)). São gráficos particularmente bons para visualizar as mudanças na distribuição no tempo e espaço, pois permite uma comparação visualmente mais fácil entre os grupos. Não deixe de olhar a vinheta do pacote para exemplos de customização.

Neste exemplo, primeiro eu ordeno as companhias aéreas por sua mediana2.

library(ggridges)

rplot <- ontime %>%
  mutate(group = reorder(UniqueCarrier, TaxiOut, median)) %>%
  ggplot(aes(x = TaxiOut, y = UniqueCarrier, fill=UniqueCarrier)) + 
  scale_x_log10(limits=c(5,50), breaks= c(5,10,25,50)) +
  theme(legend.position = "none")

rplot +  geom_density_ridges()
Ridges plots formando uma cadeia de montanhas das densidades das distribuições dos dados para cada companhia aérea.

Ridges plots formando uma cadeia de montanhas das densidades das distribuições dos dados para cada companhia aérea.

A altura de cada densidade pode ser ajustada para haver menos sobreposição:

rplot + geom_density_ridges(scale=1)
Uma alternativa caso não queiramos as montanhas sobrepostas.

Uma alternativa caso não queiramos as montanhas sobrepostas.

Também podemos usar as “montanhas” como frequências e não densidade, utilizando o argumento stat = "linline".

rplot + geom_density_ridges2(stat="binline")
Usando frequência ao invés da densidade.

Usando frequência ao invés da densidade.

Alternativa 2: violin plots

Outra alternativa, mais parecida com boxplots (tratados a seguir) é o violin plot (do próprio pacote do ggplot2), que é um plot de densidade espelhado (lados simétricos) mas disposto como se fosse um boxplot (Figura @ref(fig:vio)).

vio <- ontime %>% mutate(group = reorder(UniqueCarrier, TaxiOut, median)) %>%
  ggplot(aes(y = TaxiOut, x = UniqueCarrier, fill = UniqueCarrier)) +
  scale_y_log10() +
  theme(legend.position = "none")
vio + geom_violin()
Um plot alternativo que combina a forma de boxplots com a informação de histogramas de densidade _em pé_.

Um plot alternativo que combina a forma de boxplots com a informação de histogramas de densidade em pé.

Uma possibilidade é colocar boxplots (sem valores extremos para não poluir a figura) dentro do violin plot para facilitar encontrar a mediana e quartis:

vio + geom_violin() + geom_boxplot(width=0.4, outlier.alpha = 0)
Há quem goste de colocar um boxplot dentro do violin plot para conseguir visualizar os quartis.

Há quem goste de colocar um boxplot dentro do violin plot para conseguir visualizar os quartis.

Boxplots

Os boxplots deixam de ser úteis quando o volume de dados aumenta (Hofmann et al. 2017), pois o que eles consideram valores extremos (outliers) aumenta linearmente com o tamanho amostral (Figura @ref(fig:boxplot)). E é por isso que no violin plot nós removemos os outliers do gráfico. Ainda sim é um dos gráficos mais utilizados para se observar distribuição dos dados (veja aqui sobre 40 anos de história do boxplot).

ontime %>% mutate(group = reorder(UniqueCarrier, TaxiOut, median)) %>%
  ggplot(aes(y = TaxiOut, x = UniqueCarrier, fill = UniqueCarrier)) +
  scale_y_log10() +
  theme(legend.position = "none") +
  geom_boxplot()
Um boxplot para muitos dados fica poluído com tantos outliers.

Um boxplot para muitos dados fica poluído com tantos outliers.

Alternativa 3: letter-value plot

Uma recém criada alternativa aos boxplots convencionais, são os letter-value plots (veja Hofmann et al. 2017), que estão disponíveis no pacote lvplot. Este plot é baseado na estimativa de quantis (outros que não o primeiro e terceiro como no boxplot). Entretanto, as estimativas somente são confiáveis se há bastante dados!

Abaixo alguns exemplos de uso dos lvplots, com algumas opções para a largura das caixas:

  1. linear: torna a largura de cada caixa inversamente proporcional ao quantil (letter-value) que ela representa, ou seja, começando com os quartis, cada caixa subsequente será um passo mais fina do que a anterior.

  2. area: torna a área de cada caixa proporcional ao número de observações nela.

  3. height: torna a largura de cada caixa proporcional ao número de pontos nela.

library(lvplot)
library(patchwork) # para fazer painel com vários plots
lv <- ontime %>% mutate(group = reorder(UniqueCarrier, TaxiOut, median)) %>%
  ggplot(aes(y = TaxiOut, x = UniqueCarrier)) +
  scale_y_log10() +
  theme(legend.position = "none")

lv + geom_lv(aes(fill=..LV..), width.method = "height") +
  scale_fill_lv() + ggtitle("Largura da caixa `height`")
Um exemplo de lvplot com largura da caixa height.

Um exemplo de lvplot com largura da caixa height.

lv + geom_lv(aes(fill=..LV..), width.method = "linear") +
  scale_fill_lv() + ggtitle("Largura da caixa `linear`")
Um lvplot com largura da caixa linear.

Um lvplot com largura da caixa linear.

Eu particularmente acho o gráfico com largura height o mais bonito3!

Compare a diferença no número de valores extremos encontrados no boxplot comum e no lvplot!

Referências

Hofmann, H., Wickham, H., Kafadar, K., 2017. Letter-Value Plots: Boxplots for Large Data. Journal of Computational and Graphical Statistics 26, 469–477. https://doi.org/10.1080/10618600.2017.1305277 link alternativo


  1. O tempo de Taxi-out é definido como o tempo gasto por um vôo entre o tempo de desligamento real (AOBT) e o tempo de descolagem real (ATOT). Variável numérica do tempo de táxi em minutos.

  2. como as medianas são bem parecidas, o efeito não fica tão legal quanto poderia…

  3. Apesar de ainda achar as cores padrão não tão bonitas, mas com umas linhas a mais de código resolvemos esse problema.

Melina de Souza Leite http://melinaleite.weebly.com/

07 de Fevereiro de 2018