Capítulo 8 Gráficos com o pacote ggplot2

“O gráfico simples trouxe mais informações à mente do analista de dados do que qualquer outro dispositivo.” — John Tukey

8.1 O pacote ggplot2

O ggplot2 (https://ggplot2.tidyverse.org/)\indt{ggplot2} é um pacote R para produção de gráficos que diferentemente da maioria dos outros pacotes, apresenta uma profunda gramática baseada no livro The grammar of graphics (Wilkinson 2005). Os gráficos originados em ggplot2 são baseados em camadas, e cada gráfico tem três componentes chave: data, os dados de onde o gráfico será criado; aes() (aesthetic mappings), que controla o mapeamento estético e as propriedades visuais do gráfico; e ao menos uma camada que irá descrever como cada observação será renderizada. Camadas são usualmente criadas utilizando uma função geom_(). A referência principal ao pacote é o livro Ggplot2 : elegant graphics for data analysis (Wickham 2009).

8.2 Meu primeiro gráfico em ggplot2

A seguir, vamos discutir os aspcetos básicos para a construção de gráficos utilizando o pacote ggplot2. A função plot_grid() do pacote cowplot23 foi utilizado aqui para organizar os gráficos em forma de painéis. O pacote qqplotr24 também é utilizado como uma extensão do pacote ggplot225 para confecção de gráficos do tipo Q-Q plots. Os dados contidos na aba gg do arquivo data_R.xlsx serão utilizados. Estes dados podem ser carregados pelo seguinte comando.

url <- "https://github.com/TiagoOlivoto/e-bookr/raw/master/data/data_R.xlsx"
dados_gg <- import(url, sheet = "gg")
str(dados_gg)
# 'data.frame': 120 obs. of  6 variables:
#  $ AMB  : chr  "E1" "E1" "E1" "E1" ...
#  $ GEN  : chr  "G1" "G1" "G1" "G2" ...
#  $ BLOCO: num  1 2 3 1 2 3 1 2 3 1 ...
#  $ RG   : num  2167 2503 2427 3208 2933 ...
#  $ PH   : num  44.9 46.9 47.8 45.2 45.3 ...
#  $ MMG  : num  31.3 32.9 32.3 29 30.5 ...

8.3 As camadas de um gráfico ggplot2

No ggplot2, os gráficos são construídos camada por camada (ou, layers, em inglês). Neste exemplo, vamos confecionar um gráfico onde o eixo x será representado pela variável RG e o eixo y pela variável PH.

p1 <- ggplot(dados_gg, aes(x = RG, y = PH)) +
      geom_point()

Este comando criou um gráfico e armazenou no objeto p1, que será plotado posteriormente. Observe que o primeiro argumento da função é o data frame onde nossos dados foram armazenados. A função aes() descreve como as variáveis são mapeadas (neste caso RG no eixo x e PH no eixo y). A função geom_point() definiu que a forma geométrica a ser utilizada é baseada em pontos, gerando, assim, um gráfico de dispersão. Isto é tudo que precisa ser feito para a confecção de um gráfico simples.

8.4 Aesthetics (estética)

“O maior valor de uma imagem é quando ela nos obriga a perceber o que nunca esperamos ver.” — John Tukey

Alterar a estética dos gráficos ggplot2 é uma tarefa relativamente simples. No gráfico anterior, os valores do PH e RG foram plotados sem nenhum tipo de mapeamento estético. Digamos que marcadores com diferentes cores para cada ambiente poderia nos ajudar a compreender melhor o padrão presente em nossos dados. Vamos confecionar este gráfico.

p2 <- ggplot(dados_gg, aes(x = RG, y = PH, colour = AMB)) +
      geom_point()

Ao incluirmos colour = AMB dentro da função aes, dizemos ao ggplot que os pontos devem ser mapeados esteticamente (neste caso utilziando cores) para cada nível do fator AMB presente em nossos dados. Digamos que em vez de utilizar diferentes cores, os ambientes deveriam ser representados por diferentes tipos de marcadores (quadrados, triângulo, etc.) Neste caso, o argumento colour = AMB deveria ser substituído por shape = AMB.

p3 <- ggplot(dados_gg, aes(x = RG, y = PH, shape = AMB)) +
      geom_point()
plot_grid(p1, p2, p3,
          ncol = 3,
          labels = c("p1", "p2", "p3"),
          rel_widths = c(1, 1.2, 1.2))
Gráfico de dispersão padrão (p1) e com pontos mapeados por cores (p2) e marcadores (p3) para cada nível do fator 'AMB'.

Figure 8.1: Gráfico de dispersão padrão (p1) e com pontos mapeados por cores (p2) e marcadores (p3) para cada nível do fator ‘AMB’.

Exercício 4

  • Constua um gráfico semelhante ao anterior, onde o tamanho dos pontos deve ser baseado em uma terceira variável do nosso conjunto de dados, neste exemplo, MMG.

Resposta

8.5 Facet (facetas)

Mapeando os diferentes níveis de AMB para diferentes cores, incluímos em um único gráfico os dados de todos os ambientes. Mas, e se nosso objetivo fosse realizar um gráfico para cada ambiente? O ggplot2 tem uma poderosa ferramenta para isto: as funções facet_. Ao utilziar estas funções, o conjunto de dados é subdividido e um gráfico é construído para cada um destes subconjuntos. Vamos ver como elas podem nos ajudar em nossso problema.

fac1 <- ggplot(dados_gg, aes(x = RG, y = PH)) +
        geom_point()+
        facet_wrap(~AMB)

Neste exemplo, um gráfico completamente diferente do anterior é gerado com apenas uma simples modificação: excluímos do mapeamento estético o argumento colour = AMB e incluímos uma nova função, facet_wrap(~AMB). Neste caso, informamos que um gráfico deveria ser realizado para cada ambiente. Simples, não?

No exemplo anterior, utilizamos a função facet_wrap() para confeccionar um gráfico foi criado para cada nível do fator AMB.

8.6 Theme (temas)

Cada gráfico criado com a função ggplot() tem um tema padrão. Tema, aqui, é toda propriedade relacionada ao aspecto visual do gráfico, que não foi definida na função aes() e que pode ser modificada utilizando a função theme() (veja ?theme). O ggplot2 já conta com alguns temas personalizados para facilitar nosso trabalho. Considerando o exemplo anterior, vamos utilziar a função theme_bw() (preto e branco) e a função theme() para modificar as propriedades visuais do gráfico.

fac2 <- ggplot(dados_gg, aes(x = RG, y = PH)) +
        geom_point() +
        facet_wrap(~AMB) +
        theme_bw() +
        theme(panel.grid = element_blank(), # remove as linhas do corpo do gráfico
             # sem bordas entre os painéis
              panel.spacing = unit(0, "cm"),
             # modifica o texto dos eixos
              axis.text = element_text(size = 12, colour = "black"),
             # cor dos marcadores
              axis.ticks = element_line(colour = "black"),
             # tamanho dos marcadores
              axis.ticks.length = unit(.2, "cm"), 
             #cor da borda
              panel.border = element_rect(colour = "black", fill = NA, size = 0.5))+
       # título dos eixos
       labs(x = "Rendimento de grãos", y = "Peso hectolitro") 

plot_grid(fac1, fac2, labels = c("f1", "f2"))
Gráfico de dispersão considerando a confecção de um gráfico para cada nível de um fator(f1) e modificações na propriedades do tema de um gráfico ggplot2 (f2)

Figure 8.2: Gráfico de dispersão considerando a confecção de um gráfico para cada nível de um fator(f1) e modificações na propriedades do tema de um gráfico ggplot2 (f2)

Os argumentos inseridos dentro das função theme() modificaram a aparência do nosso gráfico. Inúmeros outros argumentos são disponíveis, fazendo com que os gráficos originados sejam completamente personalizáveis. Digamos que precisamos confecionar diversos gráficos e gostaríamos de manter o mesmo tema do gráfico acima. Seria exaustivo e desinteressante informar cada vez estes argumentos para cada gráfico, não? Felizmente, outra poderosa ferramenta proporcionada pelo ggplot2 é a possibilidade de confecionarmos nossos próprios temas. Para isto, vamos executar o seguinte comando para criar um tema personalizado (my_theme()). Este tema pode então ser aplicado como uma camada adicional a cada gráfico que confecionarmos. Para evitar a necessidade da inclusão deste tema em cada gráfico gerado, iremos definir este tema como padrão utilizando a função theme_set() .

my_theme <- function () {
  theme_bw() %+replace% # permite que os valores informados possam ser sobescritos
    theme(axis.ticks.length = unit(.2, "cm"),
          axis.text = element_text(size = 12, colour = "black"),
          axis.title = element_text(size = 12, colour = "black"),
          axis.ticks = element_line(colour = "black"),
          panel.border = element_rect(colour = "black", fill = NA, size = 0.5),
          panel.grid =  element_blank())
}
theme_set(my_theme())

Exercício 5

  • Constua um gráfico semelhante ao observado acima, onde diferentes cores devem ser atribuídas para cada genótipo. Em adição, aplique o tema personalizado que acabamos de criar ao gráfico.

Resposta

8.7 Geoms (geometria)

As funções geom_ definem qual forma geométrica será utilizada para a visualização dos dados no gráfico. Até agora, utilizamos a função geom_point() para construir gráficos de dispersão. Basicamente, qualquer outro tipo de gráfico pode ser criado dependendo da função geom_ utilizada. Dentre as diversas disponíveis no pacote ggplot2 as funções geom_ mais utilizadas são:

Deste ponto em diante, vamos confeccionar alguns exemplos utilziando algumas destas funções (ou combinações destas funções) incluindo argumentos de mapeamento de estética e temas vistos até agora.


s1 <- ggplot(dados_gg, aes(x = RG, y = PH)) +
      geom_point()+
      geom_smooth(method = "lm", se = F)+ # estima uma regressão linear
      labs(x = "Rendimento de grãos", y = "Peso hectolitro")

s2 <- ggplot(dados_gg, aes(x = RG, y = PH, colour = AMB)) +
      geom_point()+
      geom_smooth(method = "lm", se = F)+
      labs(x = "Rendimento de grãos", y = "Peso hectolitro")
plot_grid(s1, s2, labels = c("s1", "s2"), rel_widths  = c(1, 1.2))
Gráfico de dispersão, combinando pontos e linhas de regressão.

Figure 8.3: Gráfico de dispersão, combinando pontos e linhas de regressão.

Exercício 6 No gráfico s1, uma regressão linear foi ajustada quando incluímos a função geom_smooth(method = "lm", se = F).

  • Como este gráfico pode nos ajudar a compreender a relação entre as variáveis RG e PH? Ao incluir o argumento colour = AMB, uma regressão para cada ambiente foi ajustada (s2).

  • Modifique o gráfico s2 para que os ambientes ainda continuem sendo mapeados por cores, mas uma única linha de regressão seja ajustada.

Resposta

  • Gráficos do tipo boxplot

mean_rg <- mean(dados_gg$RG) # calcula a média geral do RG
box1 <- ggplot(dados_gg, aes(x = GEN, y = RG)) +
        geom_boxplot()

box2 <- ggplot(dados_gg, aes(x = GEN, y = RG)) +
        geom_boxplot(width = 0.5, col = "black", fill = "gray")+ # boxplot
        # mostra a média por um ponto
        stat_summary(geom = "point", fun.y = mean) + 
        # adiciona uma linha na média geral
        geom_hline(yintercept = mean_rg, linetype = "dashed")+ 
        labs(x = "Rendimento de grãos", y = "Peso hectolitro")

plot_grid(box1, box2, labels = c("b1", "b2"))
Gráfico do tipo boxplot combinando mapeamentos estéticos e inclusão de linhas.

Figure 8.4: Gráfico do tipo boxplot combinando mapeamentos estéticos e inclusão de linhas.

A linha orizontal tracejada representa a média geral do GY. Seis estatísticas são mostradas neste boxplot. A mediana (linha horizontal), a média (ponto) as caixas inferior e superior correspondem ao primeiro e terceiro quartil (percentis 25 e 75, respectivamente). As linha vertical superior se estende da caixa até o maior valor, não maior que \(1,5 \times {IQR}\) (onde IQR é a amplitude interquartílica). A linha vertical inferior se estende da caixa até o menor valor, de no máximo, \(1,5 \times {IQR}\). Dados além das linhas horizontais podem ser considerados outliers.

\(~\)

  • Gráficos do tipo histograma

h1 <- ggplot(dados_gg, aes(x = RG)) +
      geom_histogram()

h2 <- ggplot(dados_gg, aes(x = RG)) +
      geom_histogram(binwidth = 200, colour = "black", 
                     aes(y = ..density.., fill = ..count..)) +
      geom_density() +
      stat_function(fun = dnorm,
                    color = "red",
                    size = 1,
                    args = list(mean = mean(dados_gg$RG),
                                sd = sd(dados_gg$RG))) +
      labs(x = "rendimento de grãos", y = "Densidade")

plot_grid(h1, h2, rel_widths = c(1, 1.4), labels = c("h1", "h2"))
Gráfico do tipo histograma com estimativas de função de probabilidade kernel e normal.

Figure 8.5: Gráfico do tipo histograma com estimativas de função de probabilidade kernel e normal.

No histograma (h1), a linha preta representa estimativa de densidade Kernel (Silverman 1998). A linha vermelha representa a estimativa da função de probabilidade normal. Para isto, a escala do eixo y foi mudada de contagem para densidade.

\(~\)

  • Gráficos do tipo barra

bar1 <- ggplot(dados_gg, aes(x = GEN, y = RG)) +
        geom_bar(stat = "summary",
                fun = mean,
                position = "dodge")

bar2 <- ggplot(dados_gg, aes(x = GEN, y = RG, fill = AMB)) +
        stat_summary(fun = mean,
                     geom = "bar",
                     col = "black",
                     width = 0.8,
                     position = position_dodge()) + 
        stat_summary(fun.data = mean_se,
                     geom = "errorbar",
                     width = 0.2,
                     position = position_dodge(0.8))
 
plot_grid(bar1, bar2, rel_widths = c(0.8, 1.2), labels = c("bar1", "bar2"))
Gráfico do tipo barras, com mapeamento estético e barras de erro.

Figure 8.6: Gráfico do tipo barras, com mapeamento estético e barras de erro.

A afirmação de que um gráfico ggplot2 é feito em camadas fica mais evidente aqui. No gráfico p1, as barras representam as médias geral dos híbridos em nosso conjunto de dados. No segundo gráfico, um novo argumento visto (fill = AMB). Isto informa que as barras devem ser coloridas para cada nível do fator AMB. A função stat_summary(), também vista pela primeira vez aqui, foi utilizada no segundo gráfico para substituir a função geom_bar(). Com isto, foi possível incluir as médias (fun = mean e geom = "bar), bem como as barras de erro (fun.data = mean_se e geom = "errorbar").

  • Gráficos de dispersão com linhas de valores preditos
#### Polinômio de segundo grau
dado_reg = tibble(dose = c(15,20,25,30,35,40),
                  prod = c(65,70,73,75,69,62))
l1 <- ggplot(dado_reg, aes(dose, prod))+
      geom_point()+
      stat_smooth(method = "lm",
                  formula = "y ~ poly(x, 1)",
                  se = FALSE)
l2 <- ggplot(dado_reg, aes(dose, prod))+
      geom_point()+
      stat_smooth(method = "lm",
                  formula = "y ~ poly(x, 2)",
                  linetype = "dashed",
                  col = "black",
                  level = 0.95)

plot_grid(l1, l2, labels = c("l1", "l2"))
Gráfico de dispersão combinado com inclusão de curvas ajustadas.

Figure 8.7: Gráfico de dispersão combinado com inclusão de curvas ajustadas.

  • Gráficos do tipo quantil-quantil (Q-Q plots)

Esta função é muito util para verificar a normalidade dos resíduos da ANOVA e regressões lineares ou não lineares. A programação abaixo foi utilizada no artigo de Lúcio, Santos, and Olivoto (2017) para demonstrar a intepretação dos gráficos. As funções stat_qq_band(), stat_qq_line() e stat_qq_point() do pacote qqplotr26 serão utilizadas. Este é uma das inúmeras extensões do pacote ggplot2 que podem ser encontradas aqui.27.

# simulando dados com diferentes assimetrias
assimetria <- tibble(esquerda = rbeta(5000,1,7),
                     direita = rbeta(5000,7,1),
                     normal = rnorm(5000,5,3))

assimetria_graf <- pivot_longer(assimetria, everything()) # organiza os dados para usar facete_wrap
ggplot(assimetria_graf, aes(sample = value))+
       facet_wrap(~name, scales = "free")+
       qqplotr::stat_qq_band(fill = "gray")+
       qqplotr::stat_qq_line(col = "red")+
       qqplotr::stat_qq_point()+
labs(x = "Theoretical quantiles", y = "Sample quantiles")
Gráfico quantil-quantil de conjuntos de dados com assimetria à esquerda, direita e com distribuição normal.

Figure 8.8: Gráfico quantil-quantil de conjuntos de dados com assimetria à esquerda, direita e com distribuição normal.

Nestes exemplos vimoss alguns gráficos simples que podem ser originados pelo ggplot2. As potencialidades deste pacote, no entanto vão muito além. Uma galeria com diversos exemplos de gráficos ggplot2 com códigos disponíveis pode ser vista aqui.28

Note que no gráfico acima, as funções do pacote qqplotr foram carregadas utilizando qqplotr::. Neste caso, indicamos que a função desejada é uma função deste pacote. Isto é útil, principalmente quando dois pacotes tem funções com o mesmo nome. Utilizando :: especificamos de qual pacote a função deve ser carregada.