Boxplots estão por toda parte, e muitas vezes são úteis quando queremos ver sumários de várias distribuições nos dados. Aqui vão algumas dicas para melhorar seu uso de boxplots no ggplot2.

Usaremos dados sobre Star Wars incluídos no pacote dplyr, que é parte do tidyverse. Os dados vem da Star Wars API e já estão no namespace se carregamos o tidyverse:

library(tidyverse)
theme_set(theme_bw())
glimpse(starwars)
## Observations: 87
## Variables: 13
## $ name       <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "L…
## $ height     <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, …
## $ mass       <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.…
## $ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey", "bro…
## $ skin_color <chr> "fair", "gold", "white, blue", "white", "light", "lig…
## $ eye_color  <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "…
## $ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, …
## $ gender     <chr> "male", NA, NA, "male", "female", "male", "female", N…
## $ homeworld  <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaa…
## $ species    <chr> "Human", "Droid", "Droid", "Human", "Human", "Human",…
## $ films      <list> [<"Revenge of the Sith", "Return of the Jedi", "The …
## $ vehicles   <list> [<"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <…
## $ starships  <list> [<"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanc…

Para conhecer mais sobre essas colunas, você pode fazer help("starwars")

Para facilitar alguns exemplos, vou criar uma coluna que tem a raça dos personagens, mas apenas dizendo se é humano, androide ou outro, e uma que diz em quantos filmes o personagem apareceu.

starwars = starwars %>% 
    mutate(species_short = if_else(species %in% c("Human", "Droid"), 
                                   species, 
                                   "Other"), 
           n_films = map_dbl(films, length)) 

Alguns problemas com o boxplot padrão

Passando nenhum parâmetro, temos o seguinte:

starwars %>% 
    filter(!is.na(height)) %>% 
    ggplot(aes(x = "", y = height)) + 
    geom_boxplot()

Aliás, o x = "" nesse caso significa que todas as observações estarão no mesmo ponto do eixo x, porque todos terão o valor "" definindo a posição no eixo x. É um truque se você quer ver o boxplot de tudo.

Mas os problemas:

  1. essa caixa está bastante larga
  2. Não vemos todos os pontos
  3. Há boas chances da sua audiência não saber a regra que diz até onde vai a linha fora das caixas, e de não entender porque alguns pontos estão destacados.

A largura da caixa pode ajudar na legibilidade

E na estética também. Caso você não esteja acostumado, .1 = 0.1

starwars %>% 
    filter(!is.na(height)) %>% 
    ggplot(aes(x = "star wars", y = height)) + 
    geom_boxplot(width = .1)

Todos os pontos

Sobrepor todos os pontos no boxplot com geom_jitter ou ggbeeswarm::geom_quasirandom dá os detalhes junto com sumário que é o boxplot. Nesse caso, é importante omitir os pontos plotados pelo boxplot, para não repetí-los. Também ajuda mudar a cor e transparência dos pontos.

starwars %>%
    filter(!is.na(height)) %>%
    ggplot(aes(x = "", y = height)) +
    geom_boxplot(width = .2, outlier.colour = NA) +
    geom_jitter(
        width = .05,
        alpha = .4,
        size = 1,
        color = "brown"
    )

Já sobre os bigodes to boxplot, temos duas opções além de usar o default: removê-los ou fazê-los indicar algo mais claro (e indicar isso junto com o gráfico!).

starwars %>%
    filter(!is.na(height)) %>%
    ggplot(aes(x = "", y = height)) +
    geom_boxplot(width = .2, outlier.colour = NA, coef = 0) +
    geom_jitter(
        width = .05,
        alpha = .4,
        size = 1,
        color = "brown"
    ) + 
    labs(
        x = "Personagens",
        y = "Altura (cm)",
        title = "Sem as linhas"
    )

starwars %>%
    filter(!is.na(height)) %>%
    ggplot(aes(x = "", y = height)) +
    geom_boxplot(width = .2, outlier.colour = NA, coef = 1000) +
    geom_jitter(
        width = .05,
        alpha = .4,
        size = 1,
        color = "brown"
    ) + 
    labs(
        x = "Personagens",
        y = "Altura (cm)",
        title = "As linhas indo ao máximo e mínimo"
    )

Nessa segunda opção, coef é o coeficiente que determina a quantas vezes o IQR no máximo a linha vai. Colocando um valor muito alto, ela irá até valores (ex: 1000*IQR) muito altos e baixos, e incluirá o máx e min.

Para vários grupos

As mesmas dicas valem para comparar vários boxplots:

starwars %>% 
    filter(!is.na(height)) %>% 
    ggplot(aes(x = species_short, y = height)) + 
    geom_boxplot(width = .2)

starwars %>% 
    filter(!is.na(height)) %>% 
    ggplot(aes(x = species_short, y = height)) + 
    geom_boxplot(width = .2, outlier.colour = NA, coef = 1000) + 
    geom_jitter(width = 0.05, alpha = 0.4, color = "orange")

Quando o eixo x é numérico

Quando o eixo x é um número, precisamos indicar ao boxplot que dados vão em cada caixa:

starwars %>%
    filter(!is.na(height)) %>%
    ggplot(aes(x = n_films, y = height, group = n_films)) +
    geom_boxplot(width = .2,
                 outlier.colour = NA,
                 coef = 1000) +
    geom_jitter(width = 0.05,
                alpha = 0.4,
                color = "orange")    

Caso queiramos criar nossos grupos, a função cut ajuda:

starwars %>%
    filter(!is.na(height)) %>%
    mutate(n_filmes_g = cut(n_films, breaks = c(0, 3, 5, 10))) %>%
    ggplot(aes(x = n_filmes_g, y = height, group = n_filmes_g)) +
    geom_boxplot(width = .2,
                 outlier.colour = NA,
                 coef = 1000) +
    geom_jitter(width = 0.05,
                alpha = 0.4,
                color = "orange")    

Bônus: pontos ao lado do box

Outra opção legal é colocar o boxplot ao lado dos pontos:

starwars %>%
    filter(!is.na(height)) %>%
    ggplot(aes(x = species_short, y = height)) +
    geom_boxplot(
        width = .2,
        outlier.colour = NA,
        coef = 1000,
        position = position_nudge(.2), 
        color = "grey"
    ) +
    geom_jitter(width = .05,
                height = 0,
                alpha = 0.4)