6 min read

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 1.

Table 1: 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 1) ou densidade (Figura 2). 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.

Figure 1: Histograma de frequência por companhia aérea.

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

Figure 2: 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 3). 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.

Figure 3: 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.

Figure 4: 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.

(#fig:ridge_freq)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 5).

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é_.

Figure 5: 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.

Figure 6: 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 7). 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.

Figure 7: 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.

Figure 8: 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.

Figure 9: 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.