Introdução ao ggplot2


***

Parte do livro Introdução à análise de dados com R.  Este trabalho está em andamento, o texto é bastante preliminar e sofrerá muitas alterações. 

Quer fazer sugestões? Deixe um comentário abaixo ou, se você sabe utilizar o github, acesse aqui.

Não copie ou reproduza este material sem autorização.

Volte para ver atualizações!

***

Utilizando gráficos para explorar sua base de dados

Os gráficos base do R são bastante poderosos e com eles é possível fazer muita coisa. Entretanto, eles podem ser um pouco demorados para explorar dinamicamente sua base de dados. O pacote ggplot2 é uma alternativa atraente para resolver este problema. O ggplot2 é um pouco diferente de outros pacotes gráficos pois não segue a lógica de desenhar elementos na tela; ao invés disso, a sintaxe do ggplot2 segue uma “gramática de gráficos estatísticos” baseada no Grammar of Graphics de Wilkinson (2005).

No começo, pode parecer um pouco diferente essa forma de construir gráficos. Todavia, uma aprendidos os conceitos básicos da gramática, você vai pensar em gráficos da mesma forma que pensa numa análise de dados, construindo seu gráfico iterativamente, com visualizações que ajudem a revelar padrões e informações interessantes gastando poucas linhas de código. É um investimento que vale a pena.

Nesta seção, faremos uma breve introdução ao pacote ggplot2, destacando seus principais elementos. Para um tratamento mais aprofundado, recomenda-se o livro do Hadley Wickham.

Antes de continuar, você precisa instalar e carregar os pacotes que vamos utilizar nesta seção. Além do próprio ggplot2, vamos utilizar também os pacotes ggthemes e gridExtra.

# Instalando os pacotes (caso não os tenha instalados)
install.packages(c("ggplot2","ggthemes", "gridExtra"))

# Carregando os pacotes
library(ggplot2)
library(ggthemes)
library(gridExtra)

Também vamos utilizar uma base de dados de anúncio de imóveis de Brasília que você pode baixar aqui ou carregar com o comando abaixo. Vamos utilizar apenas os dados de venda.

# Carrega arquivo
arquivo <- url("https://dl.dropboxusercontent.com/u/44201187/imoveis.rds")
con <- gzcon(arquivo)
dados <- readRDS(con)

#  Filtra apenas para venda
venda <- dados[dados$tipo == "venda", ]

A “gramática dos gráficos”

Mas o que seria essa gramática de gráficos estatísticos? Podemos dizer que um gráfico estatístico é um mapeamento dos dados para propriedades estéticas (cor, forma, tamanho) e geométricas (pontos, linhas, barras) da tela. O gráfico também pode conter transformações estatísticas e múltiplas facetas para diferentes subconjuntos dos dados. É a combinação de todas essas camadas que forma seu gráfico estatístico.

Deste modo, os gráficos no ggplot2 são construídos por meio da adição de camadas. Cada camada, grosso modo, é composta de:

  • Uma base de dados (um data.frame, preferencialmente no formato long);
  • Atributos estéticos (aesthetics);
  • Objetos geométricos;
  • Transformações estatísticas;
  • Facetas; e,
  • Demais ajustes.

Vejamos um exemplo simples de scatter plot com os dados de preço e metro quadrado dos imóveis da nossa base de dados.

ggplot(data=venda, aes(x=m2, y=preco)) + geom_point()

Traduzindo o comando acima do gpplot2, nós começamos chamando a função ggplot() que inicializa o gráfico com os seguintes parâmetros:

  • data: aqui indicamos que estamos usando a base de dados venda;
  • aes: aqui indicamos as estéticas que estamos mapeando. Mais especificamente, estamos dizendo que vamos mapear o eixo x na variável m2 e o eixo y na variável preco.

Em seguida, adicionamos um objeto geométrico:

  • geom_point(): estamos falando ao ggplot que queremos adicionar o ponto como objeto geométrico.

Com relação às transformações estatísticas, neste caso não estamos realizando nenhuma. Isto é, estamos plotando os dado sem quaisquer modificações. Em termos esquemáticos, nós estamos fazendo o seguinte mapeamento:

O que resulta no seguinte gráfico:

plot of chunk unnamed-chunk-5

aes: mapeando cor, tamanho, forma etc

Um gráfico no plano tem apenas duas coordenadas, x e y, mas nossa base de dados tem, em geral, vários colunas… como podemos representá-las? Uma forma de fazer isso é mapear variáveis em outras propriedades estéticas do gráfico, tais como cor, tamanho e forma. Isto é, vamos expandir as variáveis que estamos meapeando nos aesthetics.

Para exemplificar, vamos mapear cada bairro em uma cor diferente e o número de quartos no tamanho dos pontos.

ggplot(data=venda, aes(x = m2, y = preco, color = bairro, size = quartos)) + 
  geom_point()

Nosso esquema anterior ficaria da seguinte forma.

E o gráfico resultante:

plot of chunk unnamed-chunk-7

Note que este gráfico revela aspectos diferentes da base de dados, como alguns registros possivelmente errados (imóvel com 30 quartos) e concentração de imóveis grandes em determinados bairros.

Mapear é diferente de determinar

Uma dúvida bastante comum quando as pessoas começam a aprender o ggplot2 é a diferença entre mapear variáveis em certo atributo estético e determinar certo atributo estético.

Quando estamos mapeando variáveis, fazemos isso dentro do comando aes(). Quando estamos apenas mudando a estética do gráfico, sem vincular isso a alguma variávei, fazemos isso fora do comando aes().

Por exemplo, no comando abaixo mudamos a cor, o tamanho e a forma dos pontos do scatter plot. Entretanto, essas mudanças foram apenas cosméticas e não representam informações de variáveis da base de dados e, portanto, não possuem legenda.

# muda o tamanho, a cor e a forma dos pontos
# note que não há legenda, pois não estamos 
# mapeando os dados a atributos estéticos
ggplot(data=venda, aes(x=m2, y=preco)) + 
  geom_point(color="darkblue", shape=21, size = 5)

plot of chunk unnamed-chunk-8

geoms: pontos, retas, boxplots, regressões

Até agora vimos apenas o geom_poin(), mas o ggplot2 vem com vários geoms diferentes e abaixo listamos os mais utilizados:

Tipo de Gráfico geom
scatterplot (gráfico de dispersão) geom_point()
barchart (gráfico de barras) geom_bar()
boxplot geom_boxplot()
line chart (gráfico de linhas) geom_line()
histogram (histograma) geom_histogram()
density (densidade) geom_density()
smooth (aplica modelo estatístico) geom_smooth()

Aqui, em virtude do espaço, mostraremos apenas um exemplo de gráfico de densidade e boxplot. Experimente em seu computador diferentes geoms na base de dados de imóveis.

# Density
ggplot(data=venda, aes(x=preco)) + geom_density(fill = "darkred")

# Boxplot
ggplot(data=venda, aes(x=bairro, y=preco)) + geom_boxplot(aes(fill = bairro))

Combinando aes e geom

Os gráficos do ggplot2 são construídos em etapas e podemos combinar uma série de camadas compostas de aes e geoms diferentes, adicionando informações ao gráfico iterativamente.

Toda informação que você passa dentro do comando inicial ggplot() é repassada para os geoms() seguintes. Assim, as estéticas que você mapeia dentro do comando ggplot() valem para todas as comadas subsequentes; por outro lado, as estéticas que você mapeia dentro dos geoms valem apenas para aquele geom especificamente. Vejamos um exemplo.

O comando abaixo mapeia o bairro como cor dentro do comando ggplot(). Dessa forma, tanto nos pontos geom_point(), quanto nas regressões geom_smooth() temos cores mapeando bairros, resultando em várias regressões diferentes.

# aes(color) compartilhado
ggplot(venda, aes(m2, preco, color=bairro)) + geom_point() + 
  geom_smooth(method="lm") 

plot of chunk unnamed-chunk-11

Mas e se você quisesse manter os pontos com cores diferentes com apenas uma regressão para todas observações? Neste caso, temos que mapear os bairros nas cores apenas para os pontos. Note que no comando a seguir passamos a estética color = bairro apenas para geom_poin().

# aes(color) apenas nos pontos
ggplot(venda, aes(m2, preco)) + geom_point(aes(color=bairro)) + 
  geom_smooth(method="lm") 

plot of chunk unnamed-chunk-13

Revelando padrões

A combinação simples de estéticas e formas geométricas pode ser bastante poderosa para revelar padrões interessantes nas bases de dados. Vejamos um caso ilustrativo.

Cilindradas, cilindros e Milhas por Galão

A base de dados mpg contém informações sobre eficiência no uso de combustível para diversos modelos de carro de 1999 a 2008. Vejamos um scatter plot relacionando cilindradas e consumo medido por milhas por galão:

ggplot(mpg, aes(displ, hwy)) + geom_point()

plot of chunk unnamed-chunk-14

A imagem parece revelar uma relação não linear entre cilindradas e milhas por galão. Vejamos, todavia, o mesmo gráfico mapeando o número de cilindros nas cores:

ggplot(mpg, aes(displ, hwy, col=factor(cyl))) + geom_point() + 
  geom_smooth(method = "lm")

plot of chunk unnamed-chunk-15

Note que o gráfico parece revelar que, uma vez condicionada ao número de cilindros, a relação entre cilindradas e milhas por galão é razoavelmente linear!

Adicionando facetas

No ggplot2(), você pode dividir o gráfico em diversos subgráficos utilizando variáveis categóricas. Vejamos um exemplo utilizando facet_wrap().

ggplot(venda, aes(m2, preco)) + 
  geom_point(aes(col=factor(quartos))) + 
  geom_smooth(method="lm") + 
  facet_wrap(~bairro) 

plot of chunk unnamed-chunk-16

Personalizando seu o gráfico

Depois de chegar em um gráfico interessante, você provavelmente vai querer personalizar detalhes estéticos deste gráfico para apresentá-lo ao público. No ggplot2 é possível fazer o ajuste fino de diversos elementos do seu gráfico e o detalhamento disso fugiria ao escopo deste livro.

Entretanto, vejamos um exemplo de histograma com a personalização de alguns elementos, adicionando labels, títulos, e mudando o fundo para branco:

media <- mean(log(venda$preco))
dp <- sd(log(venda$preco))
ggplot(data=venda, aes(x=log(preco))) + 
  geom_histogram(aes(y = ..density..), binwidth=0.3, fill="lightblue", col="black") +
  stat_function(fun=dnorm, args=list(mean=media, sd=dp), color="red") +
  geom_rug() + # adiciona rug no eixo x
  xlab("Log do Preço") + # adiciona descrição do eixo x
  ylab("Densidade") + # adiciona descrição do eixo y
  ggtitle("Histograma Preços de Imóveis") + # adiciona título
  theme_bw() # adciona tema "Black and White"

plot of chunk unnamed-chunk-17

Ficou bonito, não?

Temas pré prontos – ggthemes

O pacote ggthemes já vem com vários temas pré-programados, replicando formatações de sites como The Economist, The Wall Street Journal, FiveThirtyEight, ou de outros aplicativos como o Stata, Excel entre outros. Esta é uma forma rápida e fácil de adicionar um estilo diferente ao seu gráfico.

Experimente com os temas abaixo (gráficos omitidos aqui):

grafico <- ggplot(mpg, aes(displ, hwy, col=factor(cyl))) + geom_point() + 
  geom_smooth(method = "lm", se = F) + ggtitle("Cilindradas, cilindros e Milhas por Galão") + 
  ylab("Milhas por galão") + xlab("Cilindradas")

# Gráfico original
grafico 
# Tema "The Economist" com respectiva escala de cores
grafico + theme_economist() + scale_color_economist()
# Tema "The Wall Street Journal" com respectiva escala de cores
grafico + theme_wsj() + scale_color_wsj()
# Tema "Excel" com respectiva escala de cores
grafico + theme_excel() + scale_color_excel()
# Tema "fivethirtyeight"
grafico + theme_fivethirtyeight() 
# Tema "highcharts" com respectiva escala de cores
grafico + theme_hc()  + scale_color_hc()
# Tema "Tufte" 
grafico + theme_tufte() 
# Tema "Stata" com respectiva escala de cores
grafico + theme_stata() + scale_color_stata()

Vários gráficos juntos

Por fim, uma última dica e como colocar vários gráficos juntos com a função grid.arrange().

g1 <- grafico + theme_fivethirtyeight() 
g2 <- grafico + theme_hc() + scale_color_hc()
g3 <- grafico + theme_tufte() 
g4 <- grafico + theme_stata() + scale_color_stata()
grid.arrange(g1, g2, g3, g4)

plot of chunk unnamed-chunk-19

Foda-se a nuance, entrevista com Alvin Roth, erro de medida no desemprego e Machine Learning no Airbnb.


Algumas leituras e vídeos interessantes

– Kieran Healy mandando um fuck nuance. (Abstract: Seriously, fuck it).

– Entrevista de Alvin Roth no Google:

– Sobre a acurácia das variáveis econômicas: quanto é o desemprego da China? Nessa linha, qual é a medida adequada para “desemprego”? Veja uma discussão interessante para o caso dos EUA no Econbrowser.

Como o Airbnb usa Machine Learning?

Site do useR! 2016 em Stanford já está online.


Ano que vem o useR! 2016 será em Stanford e o site já está online com as principais datas definidas:

Important Dates

Event Date
Tutorial Submissions Deadline January 3, 2016
Abstract Submissions Deadline March 3, 2016
Notification of Acceptance March 28, 2016
Early Registration Deadline March 1, 2016
Regular Registration Deadline June 1, 2016
Late registration Deadline June 20, 2016
Tutorials June 27, 2016
Conference Start June 28, 2016
Conference End June 30, 2016

Introdução ao dplyr


***

Parte do livro Introdução à análise de dados com R.  Este trabalho está em andamento, o texto é bastante preliminar e sofrerá muitas alterações. 

Quer fazer sugestões? Deixe um comentário abaixo ou, se você sabe utilizar o github, acesse aqui.

Não copie ou reproduza este material sem autorização.

Volte para ver atualizações!

***

Eficiente e intuitivo

Com as funções da família apply e similares, você consegue fazer praticamente tudo o que você precisa para explorar os dados e deixá-los no(s) formato(s) necessário(s) para análise. E é importante você ser exposto a essas funções para se familiarizar com o ambiente base do R.

Entretanto, muitas vezes essas funções podem ser pouco intuitivas para o iniciante e, além disso, deixar a desejar em performance. Como alternativa, existe um pacote bastante rápido para manipulação de data.frames e com sintaxe muito intuitiva chamado dplyr. É provável que para o grosso de suas necessidades o dplyr seja a solução mais rápida e mais eficiente.

Se você ainda não tem o dplyr instalado na sua máquina, rode o seguinte comando.

install.packages("dplyr", repos = "http://cran.r-project.org")

Depois carregue o pacote:

library(dplyr)

Nesta seção continuaremos utilizando a base de dados de web scraping de imóveis utilizada na seção anterior (você pode baixar aqui).

Funções principais

As funções do dplyr são todas em formas de verbo e, se você sabe um pouco de inglês, são razoavelmente auto-explicativas. Vamos ver aqui as seis principais funções que, em conjunto, já te permitem fazer bastante coisa. Todas as funções possuem a mesma estrutura: o primeiro argumento é o data.frame que você quer manipular e os argumentos subsequentes dizem o que fazer com os dados. Além disso, as funções do dplyr sempre retornam um data.frame, isto é, ela não vai tentar simplificar o resultado para um vetor ou outro tipo de objeto.

  • filter: filtra um data.frame com vetores lógicos. Em outras palavras, deixa apenas as linhas que satisfazem a certo critério. Por exemplo, nos nossos dados de imóveis, poderíamos filtrar somente aqueles anúncios de aluguel com valores de preço por metro quadrado menores ou maiores do que determinado nível.
# com dplyr
filter(dados, pm2 > 96, tipo == "aluguel") 

# equivalente com R base
dados[dados$pm2 > 96 & dados$tipo == "aluguel", ]
  • select: seleciona uma ou mais colunas de um data.frame. Por exemplo, poderíamos selecionar a coluna de preços e quartos do anúncio.
# com dplyr
select(dados, preco, quartos)

# equivalente com R base
dados[, c("preco", "quartos")]
  • mutate: cria uma nova coluna. Por exemplo, vamos criar a coluna pm2 como preco/m2.
# com dplyr
dados <- mutate(dados, pm2 = preco/m2)

# equivalente com R base
dados$pm2 <- dados$preco/dados$m2
  • arrange: orderna o data.frame com base em uma coluna. Por exemplo, ordernar observações dos apartamentos segundo os preços por metro quadrado.
# com dplyr
arrange(dados, pm2)

# equivalente com R base
dados[order(dados$pm2), ]

Os próximos dois verbos são group_by e summarise que, em geral, são utilizados em conjunto com outros verbos. Deixaremos, assim, para ver exemplos com essas estruturas logo a seguir, quando conectarmos todos os verbos do dplyr com o operador %>%.

  • group_by: agrupa um data.frame segundo um vetor de categorias. “Agrupar” aqui quer dizer que todas as operações subsequentes serão feitas separadas por grupos. É equivalente ao split, que vimos antes.
  • summarise: o summarise transforma um vetor com vários números (por exemplo, um vetor de preços) em um único número de acordo com uma função (por exemplo, preço médio ou preço mediano).

Conectando tudo com %>%

Até agora talvez você não tenha visto muita vantagem em utilizar o dplyr. Escrever filter(dados, pm2 > 96, tipo == "aluguel") pode ser mais intuitivo do que dados[dados$pm2 > 96 & dados$tipo == "aluguel", ] mas talvez não tanto assim. A grande vantagem do pacote e das funções em formas de verbo aparece quando concatenamos várias operações em sequência com o auxílio do operador %>%.

O dplyr foi desenhado para trabalhar com o pipe operator %>% do pacote magritrr. Basicamente, o operador faz com que você possa escrever x %>% f() ao invés de f(x). Pode não parecer muito, mas na prática esse pequeno detalhe tem uma grande utilidade: você vai poder escrever o código de manipulação dos dados da mesma forma que você pensa nas atividades.

Pense numa receita que tenha as seguintes instruções: junte os igredientes, misture e leve ao forno. Na forma usual do R, tais instruções provavelmente teriam a seguinte forma:

forno(misture(junte(ingredientes)))

Note que temos que pensar “de dentro para fora”. O primeiro comando que lemos é forno que, na verdade, é a última operação que será realizada! Com o pipe operator, por outro lado, você escreveria algo mais ou menos assim:

ingredientes %>% junte %>% misture %>% forno

É quase igual a ler instruções verbais da receita. Parece mais intuitivo, não?

Vejamos exemplos de manipulações de dados com o dplyr usando nossa base de dados de imóveis.

Filtrando, selecionando e criando colunas

Instrução: Filtre apenas os dados coletados de apartamento, selecione as colunas bairro e preco, crie uma coluna pm2 = preco/m2, ordene os dados de forma decrescente em pm2 e mostre apenas as 6 primeiras linhas (head).

Código:

dados %>% filter(imovel=="apartamento") %>%  
  select(bairro, preco, m2) %>% mutate(pm2 = preco/m2) %>% 
  arrange(desc(pm2)) %>% head
## Source: local data frame [6 x 4]
## 
##    bairro   preco     m2      pm2
##     (chr)   (dbl)  (dbl)    (dbl)
## 1 Asa Sul 4259579 215.72 19745.87
## 2 Asa Sul 4259579 215.72 19745.87
## 3 Asa Sul 4259579 215.72 19745.87
## 4 Asa Sul 4259579 215.72 19745.87
## 5 Asa Sul 4259579 215.72 19745.87
## 6 Asa Sul 4259579 215.72 19745.87

Agrupando e sumarizando

Instrução: Filtre apenas os dados de venda de apartamento. Agrupe os dados por bairro. Calcule as medianas do preco, m2 e pm2, bem como o número de observações. Filtre apenas os grupos com mais de 30 observações. Ordene de forma decrescente com base na mediana de pm2.

Código:

dados %>% 
  filter(imovel=="apartamento", tipo=="venda") %>% 
  group_by(bairro) %>% 
  summarise(Mediana_Preco = median(preco),
            Mediana_M2 = median(m2),
            Mediana_pm2 = median(pm2),
            Obs = length(pm2)) %>% 
  filter(Obs > 30) %>%
  arrange(desc(Mediana_pm2))
## Source: local data frame [6 x 5]
## 
##       bairro Mediana_Preco Mediana_M2 Mediana_pm2   Obs
##        (chr)         (dbl)      (dbl)       (dbl) (int)
## 1   Sudoeste        850000      86.00    9689.922 20356
## 2   Noroeste        950000     100.90    9654.000 36610
## 3    Asa Sul        950000     107.00    9066.667 35241
## 4  Asa Norte        780000      94.00    9000.000 40023
## 5 Lago Norte        470000      57.55    8329.250  5938
## 6   Lago Sul        488307      88.55    5199.120   477

Exercícios de fixação

Considerando a base de dados, responda:

  • Qual o bairro com o maior preço mediano de venda?
  • Qual o bairro com o maior preço por m2 de venda?
  • Qual o bairro com o maior preço mediano de venda para apartamentos?
  • Qual o bairro com o maior preço mediano de venda para lojas?

Soluções:

# Qual o bairro com o maior preço mediano de venda?
dados %>% 
  filter(tipo == "venda") %>% # Filtra tipo "venda"
  group_by(bairro) %>%  # agrupa por bairro
  summarise(mediana = median(preco)) %>% # calcula mediana do preco
  arrange(desc(mediana)) # ordena de forma decrescente
## Source: local data frame [6 x 2]
## 
##       bairro   mediana
##        (chr)     (dbl)
## 1   Lago Sul 2800000.0
## 2   Noroeste  950000.0
## 3    Asa Sul  834572.1
## 4 Lago Norte  679000.0
## 5  Asa Norte  600000.0
## 6   Sudoeste  520000.0
# Qual o bairro com o maior preço por m2 de venda?
dados %>% 
  filter(tipo == "venda") %>% # Filtra tipo "venda"
  group_by(bairro) %>%  # agrupa por bairro
  summarise(mediana = median(pm2)) %>% # calcula mediana do pm2
  arrange(desc(mediana)) # ordena de forma decrescente
## Source: local data frame [6 x 2]
## 
##       bairro  mediana
##        (chr)    (dbl)
## 1   Noroeste 9666.561
## 2   Sudoeste 9473.684
## 3    Asa Sul 9210.526
## 4  Asa Norte 9000.000
## 5 Lago Norte 6753.247
## 6   Lago Sul 5516.129
# Qual o bairro com o maior preço mediano de venda para apartamentos?
dados %>% 
  filter(tipo == "venda", imovel == "apartamento") %>% # Filtra tipo "venda" e imovel "apartamento"
  group_by(bairro) %>%  # agrupa por bairro
  summarise(mediana = median(preco)) %>% # calcula mediana do preco
  arrange(desc(mediana)) # ordena de forma decrescente
## Source: local data frame [6 x 2]
## 
##       bairro mediana
##        (chr)   (dbl)
## 1    Asa Sul  950000
## 2   Noroeste  950000
## 3   Sudoeste  850000
## 4  Asa Norte  780000
## 5   Lago Sul  488307
## 6 Lago Norte  470000
# Qual o bairro com o maior preço mediano de venda para lojas?
dados %>% 
  filter(tipo == "venda", imovel == "loja") %>% # Filtra tipo "venda" e imovel "loja"
  group_by(bairro) %>%  # agrupa por bairro
  summarise(mediana = median(preco)) %>% # calcula mediana do preco
  arrange(desc(mediana)) # ordena de forma decrescente
## Source: local data frame [6 x 2]
## 
##       bairro mediana
##        (chr)   (dbl)
## 1   Noroeste 1107735
## 2    Asa Sul 1050000
## 3  Asa Norte  550000
## 4 Lago Norte  430000
## 5   Sudoeste  330000
## 6   Lago Sul  320000