Gramática dos gráficos com ggplot2

Aula 05 · Introdução à Linguagem R

Diogo B. Provete

PPG em Ecologia e Conservação · UFMS

De onde vem a ideia

Wilkinson, L. (2005). The Grammar of Graphics. 2ª ed. Springer. doi:10.1007/0-387-28695-0

Wickham, H. (2010). A Layered Grammar of Graphics. Journal of Computational and Graphical Statistics 19(1): 3–28. doi:10.1198/jcgs.2009.07098

A tese dos dois: um gráfico não é um tipo que você escolhe de um menu. É uma frase que você monta com peças que se combinam.

É por isso que o ggplot2 não tem uma função grafico_de_barras().

Onde aprender mais

Boa parte desta aula é adaptada do curso de Gustavo Paterno.

As três camadas

O que é um gráfico

  • Data — a tabela
  • Aesthetics — o mapeamento: qual coluna vira qual propriedade visual
  • Geometries — a representação: que forma desenha cada observação

Figura do curso de Gustavo Paterno

Os dados do exemplo

O mesmo arquivo da aula 04, já limpo:

            especie        serra altitude  massa ctmin ctmax
1 Rhinella icterica Serra do Mar      820 218.88   3.4  39.5
2 Rhinella icterica Serra do Mar      820 233.12   4.2  39.3
3 Rhinella icterica Serra do Mar      820  63.65   3.2  40.6
4 Rhinella icterica Serra do Mar      820  85.05   1.8  39.0
5 Rhinella icterica Serra do Mar      820 207.24   2.0  39.6

Bovo et al. (2023), Integrative Organismal Biology · doi:10.1093/iob/obad009 · 225 indivíduos, 5 espécies, 6 altitudes

Duas variáveis contínuas

  1. Dadosanuros
  2. Mapeamentox = ctmin, y = ctmax
  3. Geometriapoint
  4. Gráfico — um ponto por indivíduo
ggplot(anuros,
       aes(x = ctmin, y = ctmax)) +
  geom_point()

Uma variável contínua

  1. Dados — os mesmos
  2. Mapeamentox = ctmax. Só isso.
  3. Geometriahistogram
  4. Gráfico — o eixo Y aparece sozinho: é a contagem
ggplot(anuros, aes(x = ctmax)) +
  geom_histogram(bins = 25)

Repare: você não pediu o eixo Y. A geometria calculou.

A tradução para o R

Cada camada é um pedaço do comando

Camada No R
Dados objeto da classe data.frame
Mapeamento colunas do data.frame — X, Y, grupos, cor, preenchimento
Geometria uma função da família geom_
ggplot(data, aes(x, y)) + geom_point()
  • data — define o objeto com a tabela
  • aes() — define qual coluna é X e qual é Y
  • geom_ — define qual geometria plotar

O sinal de +

ggplot(data, aes(x, y)) +
  geom_point() +
  theme_classic() +
  labs(x = "Gravidade alta", y = "Total")

O + adiciona camadas. Uma camada por linha, sempre — facilita ler, facilita comentar uma linha para testar, e facilita achar o erro.

Erro clássico: pôr o + no começo da linha seguinte em vez do fim da anterior. O R não entende — ele precisa saber que a frase continua.

Mapeamento

Mapear é traduzir coluna em propriedade visual

Três colunas do nosso arquivo, e o que cada uma pode virar:

Coluna Tipo Vira bem
ctmin, ctmax numérica contínua posição em X ou Y, gradiente de cor
especie, serra categórica cor, forma, faceta
altitude numérica com 6 valores as duas coisas — você escolhe

Essa última linha é a decisão mais interessante do dia. Voltamos a ela.

O mesmo gráfico, mapeamentos diferentes

ggplot(anuros,
       aes(x = ctmin, y = ctmax)) +
  geom_point()

ggplot(anuros,
       aes(x = ctmin, y = ctmax,
           colour = especie)) +
  geom_point()

Mais um mapeamento

ggplot(anuros,
       aes(x = ctmin, y = ctmax,
           colour = especie,
           shape  = serra)) +
  geom_point(size = 2.4)

Os dados não mudaram. Só o que você pediu para o gráfico dizer sobre eles.

Dentro ou fora do aes()?

geom_point(aes(colour = especie))   # cor VEM DOS DADOS
geom_point(colour = "red")          # cor é uma DECISÃO SUA
  • Dentro do aes() → o valor da cor depende da linha. Ganha legenda.
  • Fora do aes() → todos os pontos ficam iguais. Sem legenda.

É o erro nº 1 de quem está começando. Legenda estranha? Confira se algo que deveria estar fora do aes() foi parar dentro.

Discretas vs. contínuas

  • O ggplot trata qualquer variável categórica como discreta
  • Variáveis de texto costumam virar fatores no R
  • Fatores são discretos: mapeados por níveis, não de forma contínua

A altitude é o caso perfeito: um número que na prática são seis sítios.

ggplot(anuros,
       aes(ctmin, ctmax,
           colour = altitude)) +
  geom_point()          # gradiente

ggplot(anuros,
       aes(ctmin, ctmax,
           colour = alt_fator)) +
  geom_point()          # níveis

factor() é o interruptor entre os dois mundos. Qual dos dois está certo depende da pergunta — nenhum dos dois é o padrão correto.

Os cinco gráficos básicos

Qual gráfico usar?

escolha VIZ Visualização de dados UMA Uma variável VIZ->UMA DUAS Duas variáveis VIZ->DUAS HIST Histograma UMA->HIST contínua BAR Barras UMA->BAR categórica DISP Dispersão DUAS->DISP X num, Y num vários Y por X LIN Linha DUAS->LIN X num, Y num um Y por X BOX Boxplot DUAS->BOX X categ, Y num BARF Barras facet / stack / dodge DUAS->BARF X categ, Y categ

Adaptado do curso de Gustavo Paterno

Histograma — geom_histogram()

Representa a distribuição de uma variável numérica.

ggplot(anuros, aes(x = massa)) +
  geom_histogram(bins = 30)

Uma variável numérica. O eixo Y é contagem, calculada pela geometria. A cauda longa à direita é a Rhinella icterica, muito maior que as outras.

Barras — geom_bar()

Representa variáveis categóricas com barras de comprimento proporcional ao valor.

ggplot(anuros, aes(y = especie)) +
  geom_bar()

Uma variável categórica. geom_bar() conta; geom_col() usa um valor que você já tem. Mapear em y em vez de x resolve nome comprido sem precisar girar rótulo.

Boxplot — geom_boxplot()

Quartis de uma variável numérica em função de uma variável categórica.

ggplot(anuros,
       aes(x = alt_fator, y = ctmax)) +
  geom_boxplot() +
  labs(x = "Altitude (m)")

Anatomia do boxplot

  • A linha do meio é a mediana, não a média
  • A caixa vai do 1º ao 3º quartil — 50% dos dados
  • As hastes vão até 1,5 × IQR; o que passa disso vira ponto

Dispersão — geom_point()

A relação entre duas variáveis numéricas.

ggplot(anuros,
       aes(x = massa, y = ewl)) +
  geom_point(alpha = .6)

Massa contra perda de água. Quase tudo empilhado à esquerda: a massa vai de 0,4 g a 373 g — três ordens de grandeza na mesma escala linear.

O mesmo dado, outra escala

ggplot(anuros,
       aes(x = massa, y = ewl)) +
  geom_point(alpha = .6) +
  scale_x_log10()

A escala é uma camada da gramática, como a geometria. Nenhum dado mudou; o gráfico passou a ser legível. E o padrão que aparece é real: os anuros menores perdem mais água por área — maior superfície por unidade de volume.

Linha — geom_line()

Dados em série, conectados por segmentos de reta.

Precisamos de um Y por X — então primeiro resumimos:

anuros %>%
  group_by(altitude) %>%
  summarise(bio5 = unique(bio5)) %>%
  ggplot(aes(altitude, bio5)) +
  geom_line() + geom_point()

Linha só faz sentido com um valor de Y por X, e com um X que tenha ordem. Repare no sítio de 1600 m: mais alto e mais quente que o de 1500 m, porque fica na outra serra. Altitude não é temperatura.

Agora vocês

10 minutos — três gráficos, do zero

Sem copiar dos slides. Se travar, ?geom_boxplot antes de pedir socorro.

  1. A distribuição de ctmin — escolha a geometria
  2. ewl por espécie, com a geometria que mostra os quartis
  3. massa contra ctmax, colorido por serra

No terceiro: a cor entrou dentro ou fora do aes()? Se apareceu legenda, está dentro — e é isso que vocês queriam aqui.

Combinando camadas

Desafio: como juntar os dois?

Você tem um gráfico de dispersão e quer a linha de tendência por cima.

ggplot(anuros,
       aes(altitude, tol_aquec)) +
  geom_point(alpha = .5) +
  geom_smooth(method = "lm",
              formula = y ~ x)

Repare que aes() foi declarado uma vez, no ggplot(). As duas geometrias herdam o mesmo mapeamento.

Facetas: uma janela por grupo

ggplot(anuros,
       aes(altitude, tol_aquec)) +
  geom_point(alpha = .6) +
  geom_smooth(method = "lm",
              formula = y ~ x) +
  facet_wrap(~ especie, ncol = 2,
    labeller = label_wrap_gen(16))

facet_wrap() reparte os dados e repete o gráfico — nenhuma linha do código do gráfico mudou. O labeller quebra o nome da espécie em duas linhas; sem ele, Dendropsophus minutus sai cortado.

A ordem das camadas importa

ggplot(anuros, aes(ctmin, ctmax)) +
  geom_point(size = 3) +
  geom_smooth()   # linha por cima

ggplot(anuros, aes(ctmin, ctmax)) +
  geom_smooth() +
  geom_point(size = 3)  # pontos por cima

É como empilhar transparências: a última entra na frente.

Ferramentas e exemplos

esquisse — montar o gráfico clicando

Um add-in do RStudio: você arrasta as colunas, ele monta o gráfico — e devolve o código do ggplot2.

install.packages("esquisse")
esquisse::esquisser()

Documentação do esquisse · Bom para explorar e para aprender a sintaxe. Ruim como muleta permanente — o código que fica no script é o que garante reprodutibilidade.

O que dá para fazer

Bovo, R.P.; Simon, M.N.; Provete, D.B.; Lyra, M.; Navas, C.A.; Andrade, D.V. (2023). Beyond Janzen’s Hypothesis: How Amphibians That Climb Tropical Mountains Respond to Climate Variation. Integrative Organismal Biology 5(1): obad009. doi:10.1093/iob/obad009

Mesmos dados, com geom_smooth() por espécie — Bovo et al. (2023). Sete variáveis fisiológicas, seis altitudes, cinco espécies, um bloco de código.

Distância de fuga por tipo de arena e tipo de estímulo, em 17 espécies de anuros. Citadini, J. et al. — manuscrito em avaliação.

E uma feita nesta semana

Crescimento de girinos: comprimento rostro-cloacal por estágio de Gosner, por sexo, em três zonas. Dados de campo de Javier.

O código que fez a figura anterior

library(ggplot2)
d <- read.table("javier_renacuajos.txt", header = TRUE)
d$zona <- factor(d$zona, labels = c("Zona 1", "Zona 2", "Zona 3"))

ggplot(d, aes(x = estadio, y = LHC, colour = sexo)) +
  geom_point(size = 1.9, alpha = .75) +
  geom_smooth(method = "lm", formula = y ~ x) +
  facet_wrap(~ zona) +
  labs(x = "Estágio de desenvolvimento (Gosner)",
       y = "Comprimento rostro-cloacal (mm)") +
  theme_minimal()

Dez linhas. Dados, mapeamento (x, y, colour), duas geometrias, uma estatística, facetas e rótulos — tudo o que vimos hoje, numa figura só.

Um último: o que está errado aqui?

Os rótulos do eixo X estão ilegíveis, sobrepostos. Um gráfico bonito que ninguém consegue ler não é um gráfico bonito. Como vocês consertariam?

Exercitando

Em Análises Ecológicas no R, capítulo 6:

Regra da aula: nada de esquisse nos exercícios. Escrevam o código.