Introdução aos Bump Plots
Um bump plot mostra diferentes valores de uma variável em contextos distintos. É similar a um gráfico de tendências paralelas mas com linhas mais suaves. Este tutorial demonstra como criar bump plots profissionais no R usando ggplot2 e o pacote especializado ggbump.
Os bump plots são particularmente úteis para visualizar rankings e comparações. Eles ajudam os visualizadores a acompanhar como diferentes entidades (países, empresas, times) mudam de posição em relação umas às outras através de diferentes métricas ou períodos de tempo.
Setup
Usaremos o pacote especializado {ggbump} desenvolvido especificamente para criar bump charts. O pacote está disponível tanto no CRAN quanto no GitHub.
Neste tipo de gráfico, queremos comparar o valor de uma variável em diferentes contextos. Podemos ter uma comparação entre os mesmos grupos ao longo do tempo ou os mesmos grupos ao longo de variáveis distintas. Em geral, estes gráficos são organizados em forma de rankings e têm como objetivo facilitar a comparação entre grupos.
Aplicações Comuns
- Medalhas de ouro nas olimpíadas por país ao longo dos anos
- Gêneros musicais mais populares por usuário ao longo do tempo
- Rankings populacionais de países ao longo de décadas
- Rankings imobiliários através de diferentes critérios
- Rankings de times em ligas esportivas semana a semana
- Rankings de riqueza de países usando diferentes métricas
Pacotes Necessários
Exemplo Básico
Este primeiro exemplo é emprestado da documentação do pacote e ilustra o básico da função geom_bump
. Os dados devem estar em formato ‘tidy’ (longitudinal) onde as posições dos pontos são especificadas pelos argumentos x e y e os grupos identificados via estética group.
x | y | group |
---|---|---|
2019 | 4 | A |
2020 | 2 | A |
2021 | 2 | A |
2019 | 3 | B |
2020 | 1 | B |
2021 | 4 | B |
2019 | 2 | C |
2020 | 3 | C |
2021 | 1 | C |
2019 | 1 | D |
2020 | 4 | D |
2021 | 3 | D |
Vale tirar um tempo para comparar, com calma, as entradas na tabela acima e o resultado no gráfico abaixo:
Exemplo: Venda de Imóveis no Texas
Podemos analisar as cidades com maior número de vendas ao longo dos anos usando a já conhecida txhousing
. Vamos criar um gráfico que mostra o ranking de vendas de imóveis ao longo dos anos.
Removo o ano de 2015, pois este ano não está completo na amostra. Como há mais de quarenta cidades na amostra, crio uma subamostra que contém apenas as cidades com maior número de vendas em 2014.
# Encontra as top-15 cidades com maior número de vendas em 2014
top_cities <- txhousing |>
# Seleciona apenas o ano de 2014
filter(year == 2014) |>
# Calcula o total de vendas em cada cidade
summarise(total = sum(listings, na.rm = TRUE), .by = "city") |>
# Seleciona o top-15
slice_max(total, n = 15) |>
pull(city)
# Calcula o total de vendas anuais na subamostra de cidades e faz o ranking anual
rank_housing <- txhousing |>
# Seleciona apenas cidades dentro da subamostra
filter(city %in% top_cities, year > 2005, year < 2015) |>
# Calcula o total de vendas a cada ano
summarise(
listing_year = sum(listings, na.rm = TRUE),
.by = c("city", "year")
) |>
# Faz o ranking das cidades dentro de cada ano
mutate(rank = rank(-listing_year, "first"), .by = "year")
No gráfico abaixo, cada cidade tem uma cor diferente, mas omito a legenda de cores. Note o uso de scale_y_reverse
já que, tipicamente, queremos mostrar os menores valores na parte superior do gráfico:
ggplot(rank_housing, aes(year, rank, group = city, color = city)) +
geom_bump() +
scale_y_reverse() +
guides(color = "none")
Para melhorar a legibilidade do gráfico podemos colocar o nome das cidades ao lado das linhas usando geom_text
:
ggplot() +
geom_bump(
data = rank_housing,
aes(year, rank, group = city, color = city)
) +
geom_text(
data = filter(rank_housing, year == max(year)),
aes(year, rank, label = city),
nudge_x = 0.1,
hjust = 0
) +
scale_y_reverse() +
scale_x_continuous(limits = c(NA, 2017)) +
guides(color = "none")
Mesmo com o nome das cidades, há muitas linhas para acompanhar no gráfico. Vamos destacar apenas algumas cidades. Em vez do top 4 (Houston, Dallas, San Antonio e Austin), que praticamente não se alternam no ranking, vamos destacar Bay Area, El Paso, Corpus Christi e Tyler.
O gráfico abaixo exige um código consideravelmente mais longo, mas melhora o gráfico original em vários aspectos. Agora temos um maior destaque para as cidades de interesse, eixos melhor definidos e linhas de fundo redundantes foram removidas:
Código
ggplot() +
# Linhas em cinza (sem destaque)
geom_bump(
data = filter(rank_housing, is_highlight == 0),
aes(year, rank, group = city, color = highlight),
linewidth = 0.8,
smooth = 8
) +
# Linhas coloridas (com destaque)
geom_bump(
data = filter(rank_housing, is_highlight == 1),
aes(year, rank, group = city, color = highlight),
linewidth = 2,
smooth = 8
) +
# Pontos
geom_point(
data = rank_housing,
aes(year, rank, color = highlight),
size = 4
) +
# Nomes sem destaque
geom_text(
data = filter(rank_housing, year == max(year), !(city %in% sel_cities)),
aes(year, rank, label = city),
nudge_x = 0.2,
hjust = 0,
color = "gray20"
) +
# Nome com destaque (em negrito)
geom_text(
data = filter(rank_housing, year == max(year), city %in% sel_cities),
aes(year, rank, label = city),
nudge_x = 0.2,
hjust = 0,
fontface = "bold"
) +
# Adiciona os eixos para melhorar leitura do gráfico
scale_y_reverse(breaks = 1:15) +
scale_x_continuous(limits = c(NA, 2017), breaks = 2006:2014) +
# Cores
scale_color_manual(
values = c("gray70", "#2f4858", "#86bbd8", "#f6ae2d", "#f26419")
) +
# Elementos temáticos
labs(x = NULL, y = NULL) +
theme_minimal() +
theme(
panel.grid = element_blank(),
legend.position = "none"
)
Replicando The Economist
Como exercício final vamos replicar um gráfico da revista The Economist. O artigo original discute diferentes maneiras de mensurar e comparar a riqueza entre países.
O gráfico mostra um ranking dos países mais ricos do mundo segundo três métricas:
- PIB per capita a preços correntes - a medida mais básica de riqueza
- PIB per capita em PPC (paridade do poder de compra) - ajustado pelo custo de vida
- PIB per capita em PPC ajustado por horas trabalhadas - mostra quão produtivos são os países por hora
Note como esta medida eleva consideravelmente a posição de países europeus como Bélgica, Alemanha, Áustria e Dinamarca, enquanto derruba alguns países como EUA e Singapura.
Os dados originais estão disponíveis no GitHub do The Economist, mas não consegui encontrar o código específico para este gráfico. Vou utilizar a fonte Lato do Google Fonts em vez da fonte proprietária do The Economist.
Preparação dos Dados
Boa parte da construção da visualização acima está na manipulação dos dados. Vamos fazer um passo-a-passo.
Primeiro defino alguns objetos úteis como o nome dos países que serão destacados e o nome das colunas que contém as variáveis de PIB:
A transformação essencial é converter os dados em formato tidy e ranquear as observações dentro de cada métrica de PIB:
Agora, mais por conveniência, eu crio algumas variáveis auxiliares que serão úteis para mapear os diferentes elementos estéticos:
ranking <- ranking |>
mutate(
highlight = if_else(country %in% countries_sel, country, ""),
highlight = factor(highlight, levels = c(countries_sel, "")),
is_highlight = factor(if_else(country %in% countries_sel, 1L, 0L)),
rank_labels = if_else(rank %in% c(1, 5, 10, 15, 20), rank, NA),
rank_labels = stringr::str_replace(rank_labels, "^1$", "1st"),
measure = factor(measure, levels = measures)
)
Por fim, eu defino as cores das linhas e crio uma tabela auxiliar que contém apenas o texto que vai em cima do gráfico:
cores <- c("#101010", "#f7443e", "#8db0cc", "#fa9494", "#225d9f", "#c7c7c7")
df_gdp <- tibble(
measure = measures,
measure_label = c(
"PIB per capita a preços de mercado",
"Ajustado por diferenças de custo*",
"Ajustado por custos e horas trabalhadas"
),
position = -1.2
)
df_gdp <- df_gdp |>
mutate(
measure = factor(measure, levels = measures),
measure_label = stringr::str_wrap(measure_label, width = 12),
measure_label = paste0(" ", measure_label)
)
A versão simplificada do gráfico está resumida no código abaixo. Vale notar o uso da coord_cartesian
para “cortar o gráfico” sem perder informação. Não é muito usual utilizar linewidth
como um elemento estético dentro de aes
mas pode-se ver como isto é bastante simples:
ggplot(ranking, aes(measure, rank, group = country)) +
geom_bump(aes(color = highlight, linewidth = is_highlight)) +
geom_point(shape = 21, color = "white", aes(fill = highlight), size = 3) +
geom_text(
data = filter(ranking, measure == measures[[3]]),
aes(x = measure, y = rank, label = country),
nudge_x = 0.05,
hjust = 0,
family = "Lato"
) +
coord_cartesian(ylim = c(21, -2)) +
scale_color_manual(values = cores) +
scale_fill_manual(values = cores) +
scale_linewidth_manual(values = c(0.5, 1.2))
O código final é bastante extenso, mas o resultado é muito satisfatório:
Código
ggplot(ranking, aes(measure, rank, group = country)) +
geom_bump(aes(color = highlight, linewidth = is_highlight)) +
geom_point(shape = 21, color = "white", aes(fill = highlight), size = 3) +
# Nome dos países sem destaque
geom_text(
data = filter(ranking, measure == measures[[3]], is_highlight != 1L),
aes(x = measure, y = rank, label = country),
nudge_x = 0.05,
hjust = 0,
family = "Lato"
) +
# Nome dos países com destaque (em negrito)
geom_text(
data = filter(ranking, measure == measures[[3]], is_highlight == 1L),
aes(x = measure, y = rank, label = country),
nudge_x = 0.05,
hjust = 0,
family = "Lato",
fontface = "bold"
) +
# "Eixo" na esquerda (1st, 5, 10, 15, 20)
geom_text(
data = filter(ranking, measure == measures[[1]]),
aes(x = measure, y = rank, label = rank_labels),
nudge_x = -0.15,
hjust = 0,
family = "Lato"
) +
# Texto descritivo acima do gráfico
geom_text(
data = df_gdp,
aes(x = measure, y = position, label = measure_label),
inherit.aes = FALSE,
hjust = 0,
family = "Lato",
fontface = "bold"
) +
# Posiciona as flechas apontando para baixo
annotate("text", x = 1, y = -2.2, label = expression("\u2193")) +
annotate("text", x = 2, y = -2.2, label = expression("\u2193")) +
annotate("text", x = 3, y = -2.2, label = expression("\u2193")) +
# Corta o gráfico
coord_cartesian(ylim = c(21, -2)) +
# Cores
scale_color_manual(values = cores) +
scale_fill_manual(values = cores) +
# Espessura das linhas
scale_linewidth_manual(values = c(0.5, 1.2)) +
# Elementos temáticos
labs(x = NULL, y = NULL) +
theme_minimal() +
theme(
panel.background = element_rect(fill = "#ffffff", color = NA),
plot.background = element_rect(fill = "#ffffff", color = NA),
panel.grid = element_blank(),
legend.position = "none",
axis.text = element_blank()
)
Resumo
Os bump plots são excelentes para visualizar rankings e comparações em diferentes contextos. Pontos principais:
- Use o pacote
ggbump
para bump charts suaves e profissionais - Dados devem estar em formato tidy/longitudinal
scale_y_reverse()
é útil para exibições típicas de ranking- Destacar grupos específicos melhora a legibilidade em gráficos complexos
- Considere usar
coord_cartesian()
para focar em ranges relevantes
O objetivo destes posts é de sempre fazer o máximo possível usando ggplot2
mas, na prática, as caixas de texto acima do gráfico podem ser feitas num software externo para designs mais complexos.
Pronto para criar visualizações mais avançadas? Confira nosso tutorial de Dashboards Interativos ou explore nosso guia completo de ggplot2.