Manipulação de Textos – Parte 1


***

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!

***

Criando textos

No R, textos são representados por vetores do tipo character. Você pode criar manualmente um elemento do tipo character colocando o texto entre aspas, podendo ser tanto aspas simples (‘texto’) quanto aspas duplas (“texto”).

# criando um vetor de textos
# aspas simples
x1 <- 'texto 1'

# aspas duplas
x2 <- "texto 2"

Como já vimos, para saber se um objeto é do tipo texto você pode utilizar a função is.character() e também é possível converter objetos de outros tipos para textos utilizando a função as.character().

# criando um vetor de inteiros
x3 <- 1:10

# É texto? Não.
is.character(x3)
## [1] FALSE
# Convertendo para texto
x3 <- as.character(x3)

# Agora é texto? Sim.
is.character(x3)
## [1] TRUE

Operações com textos

Operações como ordenação e comparações são definidas para textos. A ordenação de um texto é feita de maneira lexicográfica, tal como em um dicionário.

# ordenação de letras
sort(c("c", "d", "a", "f"))
## [1] "a" "c" "d" "f"
# ordenação de palavras
# tal como um dicionário
sort(c("cor", "casa", "árvore", "zebra", "branco", "banco"))
## [1] "árvore" "banco"  "branco" "casa"   "cor"    "zebra"

Como a comparação é lexicográfica, é preciso tomar alguns cuidados. Por exemplo, o texto “2” é maior do que o texto “100”. Se por acaso seus números forem transformados em texto, você não vai receber uma mensagem de erro na comparação "2" > "100" mas sim um resultado errado: TRUE.

# CUIDADO!
2   >  100
## [1] FALSE
"2" > "100"
## [1] TRUE
# b > a
"b" > "a"
## [1] TRUE
# A > a
"A" > "a"
## [1] TRUE
# casa > banana
"casa" > "banana"
## [1] TRUE

Imprimindo textos

Se você estiver usando o R de modo interativo, chamar o objeto fará com que ele seja exibido na tela usando print().

# Imprime texto na tela
print(x1)
## [1] "texto 1"
# Quando em modo interativo
# Equivalente a print(x1)
x1
## [1] "texto 1"

Se você não estiver usando o R de modo interativo — como ao dar source() em um script ou dentro de um loop — é preciso chamar explicitamente uma função que exiba o texto na tela.

# sem print não acontece nada
for(i in 1:3) i

# com print o valor de i é exibido
for(i in 1:3) print(i)
## [1] 1
## [1] 2
## [1] 3

Existem outras opções para “imprimir” e formatar textos além do print(). Uma função bastante utilizada para exibir textos na tela é a função cat() (concatenate and print).

cat(x1)
## texto 1
cat("A função cat exibe o texto sem aspas:", x1)
## A função cat exibe o texto sem aspas: texto 1

Por padrão, cat() separa os textos com um espaço em branco, mas é possível alterar este comportamento com o argumento sep.

cat(x1, x2)
## texto 1 texto 2
cat(x1, x2, sep = " - ")
## texto 1 - texto 2

Outra funções úteis são sprintf() e format(), principalmente para formatar e exibir números. Para mais detalhes sobre as funções, olhar a ajuda ?sprintf e ?format.

# %.2f (float, 2 casas decimais)
sprintf("R$ %.2f", 312.12312312)
## [1] "R$ 312.12"
# duas casas decimais, separador de milhar e decimal
format(10500.5102, nsmall=2, big.mark=".", decimal.mark=",")
## [1] "10.500,51"

Caracteres especiais

Como fazemos para gerar um texto com separação entre linhas no R? Criemos a separação de linhas manualmente para ver o que acontece:

texto_nova_linha <- "texto
com nova linha"

texto_nova_linha
## [1] "texto\ncom nova linha"

Note que aparece um \n no meio do texto. Isso é um caractere especial: \n simboliza justamente uma nova linha. Quando você exibe um texto na tela com print(), caracteres especiais não são processados e aparecem de maneira literal. Já se você exibir o texto na tela usando cat(), os caracteres especiais serão processados. No nosso exemplo, o \n será exibido como uma nova linha.

# print: \n aparece literalmente
print(texto_nova_linha)
## [1] "texto\ncom nova linha"
# cat: \n aparece como nova linha
cat(texto_nova_linha)
## texto
## com nova linha

Caracteres especiais são sempre “escapados” com a barra invertida \ . Além da nova linha (\n), outros caracteres especiais recorrentes são o tab (\t) e a própria barra invertida, que precisa ela mesma ser escapada (\\). Vejamos alguns exemplos:

cat("colocando uma \nnova linha")
## colocando uma 
## nova linha
cat("colocando um \ttab")
## colocando um     tab
cat("colocando uma \\ barra")
## colocando uma \ barra
cat("texto com novas linhas e\numa barra no final\n\\")
## texto com novas linhas e
## uma barra no final
## \

Para colocar aspas simples ou duplas dentro do texto há duas opções. A primeira é alternar entre as aspas simples e duplas, uma para definir o objeto do tipo character e a outra servido literalmente como aspas.

# Aspas simples dentro do texto
aspas1 <- "Texto com 'aspas' simples dentro"
aspas1 
## [1] "Texto com 'aspas' simples dentro"
# Aspas duplas dentro do texto
aspas2  <- 'Texto com "aspas" duplas dentro'
cat(aspas2)
## Texto com "aspas" duplas dentro

Outra opção é colocar as aspas como caracter expecial. Neste caso, não é preciso alternar entre aspas simples e duplas.

aspas3 <- "Texto com \"aspas\" duplas"
cat(aspas3)
## Texto com "aspas" duplas
aspas4 <- 'Texto com \'aspas\' simples'
cat(aspas4)
## Texto com 'aspas' simples

Utilidade das funções de exibição

Qual a utilidade de funções que exibam coisas na tela?

Um caso bastante comum é exibir mensagens durante a execução de alguma rotina ou função. Por exemplo, você pode exibir o percentual de conclusão de um loop a cada 25 rodadas:

for(i in 1:100){ 

  # imprime quando o resto da divisão
  # de i por 25 é igual a 0
  if(i %% 25 == 0){
    cat("Executando: ", i, "%\n", sep = "")
  }

  # alguma rotina
  Sys.sleep(0.01)
}
## Executando: 25%
## Executando: 50%
## Executando: 75%
## Executando: 100%

Outro uso frequente é criar métodos de exibição para suas próprias classes. Vejamos um exemplo simples de uma fução base do R, a função rle(), que computa tamanhos de sequências repetidas de valores em um vetor. O resultado da função é uma lista, mas ao exibirmos o objeto na tela, o print não é igual ao de uma lista comum:

x <- rle(c(1,1,1,0))

# resultado é uma lista
str(x)
## List of 2
##  $ lengths: int [1:2] 3 1
##  $ values : num [1:2] 1 0
##  - attr(*, "class")= chr "rle"
# print do objeto na tela 
# não é como uma lista comum
x
## Run Length Encoding
##   lengths: int [1:2] 3 1
##   values : num [1:2] 1 0
# tirando a classe do objeto
# veja que o print agora é como uma lista comum
unclass(x)
## $lengths
## [1] 3 1
## 
## $values
## [1] 1 0

Isso ocorre porque a classe rle tem um método de print próprio, print.rle():

print.rle <- function (x, digits = getOption("digits"), prefix = "", ...) 
{
    if (is.null(digits)) 
        digits <- getOption("digits")
    cat("", "Run Length Encoding\n", "  lengths:", sep = prefix)
    utils::str(x$lengths)
    cat("", "  values :", sep = prefix)
    utils::str(x$values, digits.d = digits)
    invisible(x)
}

Tamanho do texto

A função nchar() retorna o número de caracteres de um elemento do tipo texto. Note que isso é diferente da função length() que retorna o tamanho do vetor.

# O vetor x1 tem apenas um elemento
length(x1)
## [1] 1
# O elemento do vetor x1 tem 7 caracteres
# note que espaços em brancos contam
nchar(x1) 
## [1] 7

A função nchar() é vetorizada.

# vetor do tipo character
y <- c("texto 1", "texto 11")

# vetor tem dois elementos
length(y)
## [1] 2
# O primeiro elemento tem 7 caracteres
# O segundo 8.
nchar(y) # vetorizada
## [1] 7 8

Manipulando textos

Manipulação de textos é uma atividade bastante comum na análise de dados. O R possui uma série de funções para isso e suporta o uso de expressões regulares. Nesta seção veremos exemplos simples das principais funções de manipulação de textos. Na próxima seção abordaremos um pouco de expressões regulares.

Colando (ou concatenando) textos

A função paste() é uma das funções mais úteis para manipulação de textos. Como o próprio nome diz, ela serve para “colar” textos.

# Colando textos
tipo <- "Apartamento"
bairro <- "Asa Sul"
texto <- paste(tipo,"na", bairro )
texto
## [1] "Apartamento na Asa Sul"

Por default, paste() separa os textos com um espaço em branco. Você pode alterar isso modificando o argumento sep. Caso não queira nenhum espaço entre as strings, basta definir sep = "" ou utilizar a função paste0(). Como usual, todas essas funções são vetorizadas.

# separação padrão
paste("x", 1:5)
## [1] "x 1" "x 2" "x 3" "x 4" "x 5"
# separando por ponto
paste("x", 1:5, sep=".")
## [1] "x.1" "x.2" "x.3" "x.4" "x.5"
# sem separação
paste("x", 1:5, sep ="")
## [1] "x1" "x2" "x3" "x4" "x5"
# sem separação, usando paste0.
paste0("x", 1:5)
## [1] "x1" "x2" "x3" "x4" "x5"

Note que foram gerados 5 elementos diferentes nos exemplos acima. É possível “colar” todos os elementos em um único texto com a opção collapse().

paste("x", 1:5, sep="", collapse=" ")
## [1] "x1 x2 x3 x4 x5"

Separando textos

Outra atividade frequente em análise de dados é separar um texto em elementos diferentes. Por exemplo, suponha que você tenha que trabalhar com um conjunto de números, mas que eles estejam em um formato de texto separados por ponto e vírgula:

dados <- "1;2;3;4;5;6;7;8;9;10"
dados
## [1] "1;2;3;4;5;6;7;8;9;10"

Com a função strsplit() é fácil realizar essa tarefa:

dados_separados <- strsplit(dados, split=";")
dados_separados
## [[1]]
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

Note que o resultado da função é uma lista. Agora é possível converter os dados em números e trabalhar normalmente.

# convertendo o resultado em número
dados_separados <- as.numeric(dados_separados[[1]])

# agora é possível trabalhar com os números
# média
mean(dados_separados)
## [1] 5.5
# soma
sum(dados_separados)
## [1] 55

Encontrando partes de um texto

Quando você estiver trabalhando com suas bases de dados, muitas vezes será preciso encontrar certas palavras ou padrões dentro do texto. Por exemplo, imagine que você tenha uma base de dados de aluguéis de apartamentos e você gostaria de encontrar imóveis em um certo endereço. Vejamos este exemplo com dados online de aluguel em Brasília.

# Carrega arquivo de anúncios de aluguel (2014)
arquivo <- url("https://dl.dropboxusercontent.com/u/44201187/aluguel.rds")
con <- gzcon(arquivo)
aluguel <- readRDS(con)
close(con)

Vejamos a estrutura da nossa base de dados:

str(aluguel)
## 'data.frame':    2612 obs. of  5 variables:
##  $ bairro  : chr  "Asa Norte" "Asa Norte" "Sudoeste" "Asa Norte" ...
##  $ endereco: chr  "CLN 310 BLOCO A " "SCRN 716 BLOCO G ENT. 26 3º ANDAR" "QMSW 06 ED.STUDIO IN" "CLN 406 BLOCO D - ED. POP CENTER (APARTAMENTO)" ...
##  $ quartos : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ m2      : num  22.9 26 30 30 30 30 30 30 28 30 ...
##  $ preco   : num  650 750 800 800 800 820 850 850 850 860 ...
##  - attr(*, "na.action")=Class 'omit'  Named int [1:120] 15943 16001 17264 17323 18600 18659 19935 19996 21278 22617 ...
##   .. ..- attr(*, "names")= chr [1:120] "15943" "16001" "17264" "17323" ...

Temos mais de 2 mil anúnciso, como encontrar aqueles apartamentos que queremos, como, por exemplos, os que contenham “CLN 310” no endereço? Neste caso você pode utilizar a função grep() para encontrar padrões dentro do texto. A função retornará o índice das observações que contém o texto:

busca_indice <- grep(pattern = "CLN 310", aluguel$endereco)
busca_indice
## [1]    1 1812
aluguel[busca_indice, ]
##         bairro                            endereco quartos   m2 preco
## 1    Asa Norte                    CLN 310 BLOCO A        1 22.9   650
## 1812 Asa Norte CLN 310 BLOCO E ENTRADA 52 SALA 216       0 30.0   900

Uma variante da função grep() é a função grepl(), que realiza a mesma coisa, mas ao invés de retornar um índice numérico, retorna um vetor lógico:

busca_logico <- grepl(pattern = "CLN 310", aluguel$endereco)
str(busca_logico)
##  logi [1:2612] TRUE FALSE FALSE FALSE FALSE FALSE ...
aluguel[busca_indice, ]
##         bairro                            endereco quartos   m2 preco
## 1    Asa Norte                    CLN 310 BLOCO A        1 22.9   650
## 1812 Asa Norte CLN 310 BLOCO E ENTRADA 52 SALA 216       0 30.0   900

Nossa busca é útil, mas ainda é simples. Quando aprendermos expressões regulares, essas buscas ficarão bem mais poderosas. Lá também aprenderemos outras funções como regexpr(), gregexpr(), regexec() e regmatches().

Substituindo partes de um texto

A função sub() substitui o primeiro padrão (pattern) que encontra:

texto2 <- paste(texto, ", Apartamento na Asa Norte")
texto2
## [1] "Apartamento na Asa Sul , Apartamento na Asa Norte"
# Vamos substituir "apartamento" por "Casa"
# Mas apenas o primeiro caso
sub(pattern = "Apartamento",
    replacement = "Casa",
    texto2) 
## [1] "Casa na Asa Sul , Apartamento na Asa Norte"

Já a função gsub() substitui todos os padrões que encontra:

# Vamos substituir "apartamento" por "Casa"
# Agora em todos os casos
gsub(pattern="Apartamento",
    replacement="Casa",
    texto2)
## [1] "Casa na Asa Sul , Casa na Asa Norte"

Você pode usar as funções sub() e gsub() para “deletar” partes indesejadas do texto, basta colocar como replacement um caractere vazio "". Um exemplo bem corriqueiro, quando se trabalha com com nomes de arquivos, é a remoção das extensões:

# nomes dos arquivos
arquivos <- c("simulacao_1.csv","simulacao_2.csv")

# queremos eliminar a extensão .csv
# note que o ponto precisa ser escapado
nomes_sem_csv <- gsub("\\.csv", "", arquivo)
nomes_sem_csv
## [1] "5"

Extraindo partes específicas de um texto

Às vezes você precisa extrair apenas algumas partes específicas de um texto, em determinadas posições. Para isso você pode usar as funções substr() e substring().

Para essas funções, você basicamente passa as posições dos caracteres inicial e final que deseja extrair.

# extraindo caracteres da posição 4 à posição 8
x <- "Um texto de exemplo"
substr(x, start = 4, stop = 8)
## [1] "texto"

É possível utilizar essas funções para alterar partes específicas do texto.

# substituindo caracteres da posição 4 à posição 8
substr(x, start = 4, stop = 8) <- "TEXTO"
x
## [1] "Um TEXTO de exemplo"

A principal diferença entre substr() e substring() é que a segunda permite você passar vários valores iniciais e finais:

# pega caracteres de (4 a 8) e de (10 a 11)
substring(x, first = c(4, 10), last = c(8, 11))
## [1] "TEXTO" "de"
# pega caracteres de (1 ao último), (2 ao último) ...
substring("abcdef", first = 1:6)
## [1] "abcdef" "bcdef"  "cdef"   "def"    "ef"     "f"

**** A SEGUIR ****

  • expressões regulares
    • regmatches, regexpr, gregexpr, regexc
  • fuzzy matching
  • stringr

Simulações – Parte 1


***

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!

***

Distribuições de probabilidade

O R vem com diversas funções para simular distribuições estatísticas. Em geral essas funções têm o seguinte formato: rnomedadistribuicao, dnomedadistribuicao, pnomedadistribuicao ou qnomedadistribuicao. Mais detalhadamente, a primeira letra da função, que pode ser r, d, p ou q, indica, respectivamente, se a função é: (i) geradora de variáveis aleatórias; (ii) de densidade; (iii) de distribuição acumulada; ou, (iv) de quantil. E, logo em seguida, temos um nome abreviado da distribuição de probabilidade.

Captura de Tela 2016-01-17 às 13.58.01

Dessa forma, por exemplo, se você quiser gerar dados aleatórios de uma distribuição normal a função para tanto é rnorm (r pois trata-se de um gerador de números aleatórios e norm pois trata-se da distribuição normal).

Na tabela abaixo temos várias das distribuições presentes de forma nativa no R:

Captura de Tela 2016-01-17 às 13.58.19

Sementes para as simulações

Durante todo o livro nós utilizamos o comando set.seed quando fizemos simulações. Isso garante que os resultados obtidos possam ser reproduzidos em qualquer computador.

Veja que, se rodarmos o comando rnorm sem definir o estado do gerador de números aleatórios com set.seed, você não conseguirá obter os mesmos valores em seu computador:

rnorm(1)
## [1] 0.5748481

rnorm(1)
## [1] 0.4052027

Contudo, uma vez definida a semente, obteremos sempre o mesmo valor:

set.seed(1)
rnorm(1)
## [1] -0.6264538

set.seed(1)
rnorm(1)
## [1] -0.6264538

O básico de r,d p,q com a distribuição normal

Para começar a entender o que cada função do R faz, trabalhemos cada uma delas usando a distribuição normal. A função densidade da distribuição normal-padrão (uma normal com média zero e desvio-padrão igual a um) tem a seguinte forma:

plot of chunk grafico densidade

A primeira dúvida que alguém pode ter é: como extrair valores aleatoriamente desta distribuição? Vejamos:

# semente para reproducibilidade
set.seed(2)

# gerando 5 variáveis aleatórias da distribuição Normal(0,1)
x1 <- rnorm(5)
x1
## [1] -0.89691455  0.18484918  1.58784533 -1.13037567 -0.08025176

Com o comando acima geramos 5 valores da normal-padrão.

Mas e se quisermos valores de uma normal com média e desvio-padrão diferentes? Para isso, basta mudarmos os parâmetros mean sd (standard deviation) da função rnorm:

# semente para reproducibilidade
set.seed(2)

# gerando 6 variáveis aleatórias da distribuição Normal(10,2)
x2 <- rnorm(5, mean = 10, sd = 2)
x2
## [1]  8.206171 10.369698 13.175691  7.739249  9.839496

Com o código acima, geramos 5 valores de uma distribuição normal com média 10 e desvio-padrão 2. Entretanto, você também poderia ter gerado os mesmos valores a partir da normal-padrão: x2 nada mais é do que x1*2 + 10:

all.equal(x1*2 + 10, x2)
## [1] TRUE

Saber como tranformar uma distribuição em outra é algo bastante útil e pode poupar bastante tempo na hora de fazer simulações. Veremos exemplos práticos disso nos exercícios.

Às vezes, ao invés de gerar números aleatórios, nós temos valores que, presume-se, foram gerados por uma distribuição normal, e queremos saber a densidade ou a probabilidade associada àquele valor.

Por exemplo, supondo uma distribuição normal-padrão, qual a probabilidade de x ser menor do que 1.65? Isto é, queremos saber o valor da área hachurada da curva de densidade:

plot of chunk grafico area densidade
Para responder essa pergunta, você vai usar a função pnorm:

# probabilidade de X < 1.65
pnorm(1.65)
## [1] 0.9505285

Note que aproximadamente 95% dos valores da normal-padrão estão abaixo de 1.65. E se quisermos fazer a pergunta contrária: qual o valor de x tal que 95% dos valores da curva estejam abaixo deste valor? Para isso usamos a função qnorm:

qnorm(0.95)
## [1] 1.644854

Para calcularmos os valores da função densidade utilizamos a função dnorm. Vejamos como fazer isso reproduzindo os gráficos da função densidade exibidos anteriormente:

# Sequencia de -3 a 3 igualmente espaçada e
# com valores redondos
x <- pretty(c(-3, 3), 1000)

# Função densidade de -3 a 3
y <- dnorm(x)

# Gráfico Função Densidade
plot.new()
plot.window(xlim=range(x), ylim=range(y))
axis(1); axis(2)
polygon(x, y, col = "lightblue")
title(main = "Distribuição Normal \nFunção Densidade")

# Gráfico Função Densidade Hachurado
plot.new()
plot.window(xlim=range(x), ylim=range(y))
axis(1);axis(2)
polygon(x, y, col = "lightblue")
title(main = "Distribuição Normal \nFunção Densidade")
z <- 1.65
lines(c(z, z), y = c(dnorm(-3), dnorm(z)))
polygon(c(x[x<=z], z), c(y[x<=z], min(y)), density = 10, angle = 45)
text(x = z + 0.3, y = dnorm(z) + 0.01,"1.65")

Exemplo 1: Teorema Central do Limite

O teorema cental do limite nos diz que, sob certas condições de regularidade (como variância finita), quanto mais observações tivermos, a distribuição amostral da média de uma variável aleatória será aproximadamente normal, independentemente do formato original da distribuição.

Vejamos um exemplo com a distribuição exponencial. A função densidade da exponencial pode ser escrita como f(x) = \lambda e^{-\lambda x}, com média E(X) = \frac{1}{\lambda} e desvio padrão DP(X) = \frac{1}{\lambda}.

Para nosso exemplo, tomaremos \lambda = 1. Assim, temos que E(X) = DP(X) = 1 e, segundo o teorema central do limite, a variável (\bar{x}-1)\sqrt{n} tende a uma normal-padrão (\bar{x} é a média amostral de x)

Note que o formato do histograma da exponencial (\lambda = 1) não se parece com o formato de sino da distribuição normal, que vimos na seção anterior:

# semente para reproducibilidade
set.seed(10)

# geramos 1000 variávels aleatórias de uma distribuição exponencial
x <- rexp(n = 1000, rate = 1)

# histograma
hist(x, col = "lightblue", main = "Distribuição Exponencial", freq = F)

plot of chunk hist exp

Entretanto, o que ocorre com a distribuição de (\bar{x}-1)\sqrt{n}  quando aumentamos o valor de n? Façamos uma simulação para seis valores de tamanho amostral diferentes: 1, 5, 10, 100, 500 e 1000.

# Simulacõees TCL - exponencial

# semente para reproducibilidade
set.seed(100)

# diferentes tamanhos amostrais que iremos simular
n <- c(1, 5, 10, 100, 500, 1000)

# número de replicações para cada n
n.rep <- 1000

## simulações
sims <- lapply(n, function(n) replicate(n.rep, (mean(rexp(n)) - 1)*sqrt(n)))

Na prática, a simulação toda foi feita com apenas uma linha, combinando o lapply com replicate. Explicando melhor o código acima, com o comando lapply(n, ...) estamos dizendo para o R que iremos aplicar uma função para cada valor de n. Mas que função estamos aplicando? Neste caso, a função anômima function(n) replicate(n.rep, (mean(rexp(n)) - 1)*sqrt(n)). Mais detalhadamente, com o comando replicate(n.rep, (mean(rexp(n)) - 1)*sqrt(n))) repetimos n.rep vezes a expressão (mean(rexp(n)) - 1)*sqrt(n)), que nada mais é do que a média padronizada de uma exponencial (\lambda = 1) de tamanho amostral n multiplicada por \sqrt{n}

O resultado de nossas simulações está na lista sims que tem a seguinte estrutura:

## nomes para as listas
names(sims) <- as.character(n)

## estrutura do resultado
str(sims)
## List of 6
##  $ 1   : num [1:1000] -0.0758 -0.2762 -0.8954 2.0974 -0.3752 ...
##  $ 5   : num [1:1000] -0.25122 0.00986 -1.19437 -1.29553 1.14441 ...
##  $ 10  : num [1:1000] -0.237 1.042 0.523 1.228 0.929 ...
##  $ 100 : num [1:1000] -0.303 0.43 -0.25 -0.339 -0.659 ...
##  $ 500 : num [1:1000] 2.072 0.239 -0.91 0.421 -0.353 ...
##  $ 1000: num [1:1000] -1.0039 0.0282 -0.0259 -0.0925 -0.9421 ...

Perceba que temos uma lista com 6 elementos, um para cada n diferente. Você pode acessar os resultados da lista ou pelo índice ou pelo nome do elemento:

# pega os resultados de n = 1000
sims[[6]]
sims[["1000"]]

Vejamos todos os resultados da simulação ao mesmo tempo em um gráfico. O histograma dos valores simulados estão em azul claro e a função densidade da normal-padrão em vermelho.

plot of chunk tcl ggplot2

Quando n = 1, a distribuição segue o mesmo formato da exponencial. Todavia, note que a convergência para a distribuição normal ocorre bem rapidamente neste exemplo. Com n = 100 as diferenças entre a normal e os dados simulados já se tornam bastante pequenas.

Fizemos o gráfico acima com o ggplot2:

library(ggplot2)
library(reshape2)

# Prepara base de dados para gráfico
## Transforma em data.frame
sims.df <- as.data.frame(do.call("cbind", sims))

## Empilha para o ggplot2
sims.df <- melt(sims.df,
                variable.name = "n",
                value.name = "Valor")
sims.df$n <- paste("n =", sims.df$n)
sims.df$n <- factor(sims.df$n, levels = unique(sims.df$n))

# Histogramas vs Densidade Normal (ggplot2)
ggplot(sims.df, aes(x = Valor)) +
  # Histograma
  geom_histogram(aes(y = ..density..),
                 fill = "lightblue",
                 col = "black",
                 binwidth = 0.5) + xlim(c(-6, 6)) +
  # Uma faceta para cada n
  facet_wrap(~n) +
  ## Densidade da normal(0,1) para comparação
  stat_function(fun = dnorm,
                col = "red", size = 0.8) +
  # Titulo principal e do eixo Y
  ggtitle("Teorema Central do Limite\nDistribuição Expoencial") +
  ylab("Densidade") +
  ## Tema em preto e branco
  theme_bw()

Sua vez!

Nós simulamos o teorema central do limite usando funções da família apply: lapply e replicate. Isso permite nos expressarmos de maneira bastante concisa, em apenas uma linha.

Como você faria a mesma simulação usando loops? Compare os resultados e veja se eles são idênticos.

# Resposta sugerida

# Com FOR
## para reproducibilidade
set.seed(100)
## tamanho amostral
n <- c(1, 5, 10, 100, 500, 1000)
## numero de replicacoes
n.rep <- 1000

# lista para armazenar os resultados para cada n
sims.for <- vector("list", length(n))

## começo do for
## faremos n.rep replicacoes para cada n
## para cada i de n
for (i in seq_along(n)) {

  # crie um vetor temporario para realizar n.rep repetições
  temp <- numeric(n.rep)

  # realiza n.rep repetições de (mean(rexp(n[i])) - 1)*sqrt(n[i])
  for (j in 1:n.rep) {
    temp[j] <- (mean(rexp(n[i])) - 1)*sqrt(n[i])
  }

  # guarda resultado na lista
  sims.for[[i]] <- temp
}

# nomes para os resultados da lista
names(sims.for) <- n

# compara com simulação anterior
all.equal(sims, sims.for)
## [1] TRUE

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

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

Livros de R – sugestões


Algumas pessoas têm pedido recomendações de livros de R, então seguem, abaixo, algumas dicas. Evidentemente essa lista não é exaustiva, na parte de estatística e machine learning, por exemplo, há uma infinidade de livros, muitos com temas bastante específicos (como métodos estatísticos voltados para genética etc).

Livros mais gerais

Se você está iniciando no R, comece estudando um dos livros abaixo (ou se sentir necessidade, dois). Eu recomendaria iniciar com o R for dummies.  Apenas depois de ler um desses, vá para os livros mais específicos.

·         R for dummies;
·         The Art of R programming;
·         R for everyone;
·         R in action.

Livros sobre gráficos

Esses são os três principais livros sobre gráficos no R e trabalham com gráficos base, ggplot2 e lattice. Vale lembrar que esses não são livros que vão te ensinar a fazer bons gráficos, mas sim livros que vão te ensinar a como fazer gráficos no R.

·         ggplot2: Elegant Graphics for Data Analysis;
·         Lattice: Multivariate Data Visualization with R;
·         R Graphics.

Livros de Estatística

Como disse no começo, essa lista é enorme. Existem livros específicos de séries temporais, de dados espaciais etc. Seguem quatro sugestões que abordam estatística geral, regressões, séries temporais e estatística bayesiana.

·         Discovering Statistics Using R (Ainda não li, mas falaram muito bem do Andy Field, parece ser um livro bastante didático de estatística usando o R);
·         An R Companion to Applied Regression ;
·         Introductory Time Series with R ;
·         Doing Bayesian Data Analysis: A tutorial with R, JAGS and Stan (Este livro é bastante didático, uma ótima itrodução para estatística Bayesiana);

Machine Learning

Também existem vários livros de machine learning usando o R. Eu recomendo fortemente usar o pacote caret e o livro do pacote é o Applied Predictive Modeling. Existem dois livros de teoria do Hastie e Tibshirani que estão disponíveis gratuitamente na internet e que podem ser bastante úteis: um livro mais didático de introdução ao assunto é  (An Introduction to Statistical Learning) e uma versão mais detalhada (The Elements o Statistical Learning).

·         Applied Predictive Modeling  (sobre o pacote caret – recomendo fortemente);
·         An introduction to Statistical Learning: with applications in R (um pouco menos de aplicações em R, mas mais didático, versão gratuita disponível na internet – também tem curso online);
·         The elements of statistical learning (não trata de R em si, complementa os livros acima).

Temas mais avançados

Algumas sugestões de temas interessantes.

·         Advanced R (para saber um pouco mais como o R funciona);
·         R packages (fazer pacotes no R);
·         Seamless R and C++ integration with Rcpp (integrar R com C++, não adiante ler sem saber um pouco de C++)
·         XML and Web Technologies for Data Sciences with R (integração do R com tecnologias web)
·         Dynamic Documents with R and knitr (criar documentos dinâmicos – como um paper ou um livro- diretamente do R)
·          Orientação a objetos S4 em R (não é um livro, mas é um material bacana para orientação a objetos S4).

Dividir, Aplicar e Combinar (Split, Apply and Combine)


***

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.

As partes do livro não estão sendo publicadas na ordem – por exemplo, a seção abaixo não é uma explicação da família apply, mas sim da estratégia split-apply-combine e de algumas funções que podem ser utilizadas nesta estratégia (como o trio split, lapply, do.call,  a função tapply, ou a função aggregate).  A explicação sobre diferentes funções apply e derivados (applysapplymapplylapplyvapplyrapplyreplicate, rowSums, colSums, rowMeans, colMeans) é feita em seção anterior a essa seção (ainda não disponível no blog).

Volte para ver atualizações!

***

Um padrão recorrente

Nossa base de dados (clique aqui para download) contém preços tanto de aluguel quanto de venda de apartamentos. Suponha que queiramos tirar a médias dos preços. Não faz muito sentido tirar a média dos dois tipos (aluguel e venda) juntos, certo? Como poderíamos fazer isso então? Uma possível solução seria a seguinte:

Primeiramente, dividimos a base de dados, criando duas outras, uma para cada grupo: aluguel e venda.

# 1) separar a base em duas bases diferentes:
#    - aluguel; e,
#    - venda
aluguel <- dados[dados$tipo == "aluguel", ]
venda <- dados[dados$tipo == "venda", ]

Em seguida nós calculamos a média para cada uma das novas bases que criamos.

# 2) calcular a média para cada uma das bases
media_aluguel <- mean(aluguel$preco)
media_venda   <- mean(venda$preco)

Por fim, nós combinamos os resultados em um único vetor:

# 3) combinar os resultados em um único vetor
medias = c(aluguel = media_aluguel, venda = media_venda)
medias
##     aluguel       venda
##    4594.757 1232719.524

Pronto! Calculamos as duas médias que queríamos. Todavia, note que gastamos cerca de 5 linhas para chegar ao resultado – e que foram 5 linhas porque temos apenas 2 categorias (alguel e venda) neste exemplo. Imagine se tívessemos que analisar separadamente 200 ou 2000 categorias? Teríamos que criar uma base separada para cada uma delas?

Não necessariamente. Na verdade podemos fazer isso (ou coisas mais complexas) com apenas uma ou duas linhas. Para você saber aonde queremos chegar, seguem alguns exemplos mais sucintos que realizam o mesmo cálculo feito anteriormente:

# com tapply
tapply(X = dados$preco, INDEX = dados$tipo, FUN = mean)
##     aluguel       venda
##    4594.757 1232719.524
# com aggregate
aggregate(x = list(media = dados$preco), by = list(tipo = dados$tipo),
          FUN = mean)
##      tipo       media
## 1 aluguel    4594.757
## 2   venda 1232719.524
# com dplyr
library(dplyr)
dados %>% group_by(tipo) %>% summarise(media = mean(preco))
## Source: local data frame [2 x 2]
##
##      tipo       media
## 1 aluguel    4594.757
## 2   venda 1232719.524

Mas, antes de entrarmos nas formas mais concisas de escrever essas operações, vejamos, primeiramente, como fazê-las com algumas funções mais básicas do R, o que vai lhe permitir maior flexibilidade em alguns casos.

Dividir, Aplicar e Combinar (Split, Apply and Combine)

O padrão de análise descrito na seção anterior é bastante recorrente quando trabalhamos com dados. Dentro da comunidade do R, esse processo é conhecido como Dividir, Aplicar e Combinar (DAC) ou, em inglês, Split, Apply and Combine (SAC). Em nosso caso específico, nós pegamos um vetor (o vetor de preços), dividimos segundo algum critério (por tipo), aplicamos um função em cada um dos grupos separadamente (no nosso caso, a média) e depois combinamos os resultados novamente.

dac_1

Para quem conhece SQL, muitas dessas operações são similares ao group by, ou, para quem usa Excel, similar a uma tabela dinâmica – mas note que não são coisas exatamente equivalentes, pois o conceito aqui é mais flexível. Isso vai ficar mais claro na medida em que usarmos exemplos mais complicados.

Apesar de só estarmos introduzindo o conceito de DAC agora, nós, na verdade, já tínhamos visto este padrão diversas vezes quando estudamos as funções do tipo apply. Por exemplo, ao aplicar uma função por linhas, você está dividindo a matriz por uma das dimensões (a dimensão das linhas), aplicando funções a cada uma das partes (a cada uma das linhas) e combinando os resultados em um único vetor:

dac_2

Vejamos algumas peças para construir essa estratégia de análise de dados usando as funções base do R.

Dividir: a função split

A primeira função que você deve conehcer é a função split() (dividir, em inglês) que, literalmente, divide um objeto segundo um conjunto de características. A função tem a seguinte estrutura:

str(split)
## function (x, f, drop = FALSE, ...)

\pause

Em que os principais parâmetros são:

  • x: vetor ou data.frame que será divido;
  • f: fatores que irão definir os grupos de divisão.

O resultado da função é uma lista para cada fator, contendo os vetores ou data.frames do respectivo grupo.

Voltando ao nosso exemplo, vamos dividir nosso data.frame segundo a lista de fatores tipo (aluguel ou venda). Note que o resultado é uma lista contendo dois vetores: (i) um para aluguel; (ii) e outro para venda.

alug_venda <- split(dados$preco, dados$tipo)
str(alug_venda, max.level = 1)
## List of 2
##  $ aluguel: num [1:76085] 650 750 800 800 800 820 850 850 850 860 ...
##  $ venda  : num [1:216517] 159000 170000 175000 180000 180000 180000 185000 185000 190000 195000 ...

Aplicar e combinar – voltando à família apply

Como visto, o resultado do split é uma lista para cada categoria. Queremos aplicar uma função a cada um dos elementos dessa lista. Ora, já vimos uma função que faz isso: o lapply(). Dessa forma, com o comando lapply(alug_venda, mean) podemos calcular a média de cada um dos elementos da lista, separadamente:

medias <- lapply(alug_venda, mean)
medias
## $aluguel
## [1] 4594.757
##
## $venda
## [1] 1232720

Combinando os resultados com unlist()

Ok, estamos quase lá, já temos o resultado final, mas ele está no formato de uma lista, que não é um dos formatos mais convenientes. Geralmente, em casos como esse, queremos um vetor. Vamos, assim, tentar simplificar este objeto. Uma das formas de fazer isso seria utilizar uma função que nós tambem já vimos quando estudamos os objetos básicos do R: a função unlist()

unlist(medias)
##     aluguel       venda
##    4594.757 1232719.524

Combinando com do.call()

Existe outra função de conveniência que, em conjunto com rbind() e cbind() pode ser bastante útil para simplificar nossos resultados: a função do.call().

Como ela funciona?

A função do.call() tem a seguinte sintaxe: na primeira posição passamos, em formato texto, uma função que queremos utilizar (como cbind()); já na segunda posição passamos a lista de argumentos que que serão utilizadas pela função que está na primeira posição.

Em outras palavras – e mais diretamente – o comando:

do.call("alguma_funcao", lista_de_argumentos)

É equivalente a:

alguma_funcao(lista_de_argumentos[1],  lista_de_argumentos[2], ..., lista_de_argumentos[n])

No nosso caso, temos apenas duas médias, então não seria complicado elencar um a um os elementos no rbind() ou no cbind(). Todavia, imagine um caso em que temos centenas de médias. Nesta situação, a função do.call() é bastante conveniente.

do.call("rbind", medias)
##                [,1]
## aluguel    4594.757
## venda   1232719.524
do.call("cbind", medias)
##       aluguel   venda
## [1,] 4594.757 1232720

Aplicando e simplificando ao mesmo tempo: sapply()

Agora podemos encaixar conceitualmente outra função que já tínhamos aprendido, o sapply(). Essa função tenta fazer os dois passos do DAC ao mesmo tempo: Aplicar e Combinar (que neste caso é sinônimo de simplificar):

sapply(alug_venda, mean)
##     aluguel       venda
##    4594.757 1232719.524

Aplicando e simplificando ao mesmo tempo: vapply()

Existe, ainda, uma versão mais restrita do sapply(): o vapply(). A principal diferença entre eles é que o vapply() exige que você especifique o formato do resultado esperado da operação.

Enquanto o o primeiro é mais prático para uso interativo, o segundo é mais seguro para programar suas próprias funções, pois, se o resultado não vier conforme esperado, ele irá acusar o erro.

Por exemplo, no comando abaixo estamos dizendo explicitamente ao R que queremos um resultado do tipo numérico. Assim, se por acaso vier algo diferente, o R acusará um erro.

vapply(alug_venda, mean, numeric(1))
##     aluguel       venda
##    4594.757 1232719.524

Para ilustrar essa situação, suponha que esperássemos que o resultado do nosso cálculo fosse um vetor do tipo character com um elemento. Note agora R fornece uma mensagem de erro bastante explicativa.

vapply(alug_venda, mean, character(1))
## Error in vapply(alug_venda, mean, character(1)): valores devem ser do tipo 'character',
##  mas o resultado de FUN(X[[1]]) é de tipo 'double'

Dividir, Aplicar e Combinar: fazendo tudo ao mesmo tempo

Conhecer as funções mais fundamentais da estrutura Split, Apply, Combine é importante para você ter a flexibilidade de fazer análises mais personalizadas quando precisar. O exemplo anterior é bastante simples para podermos nos concentrar nos conceitos, entretanto, não se engane: combinando apenas as funções do tipo apply() com as funções split() e do.call() é possível fazer diversas operações relativamente complexas com poucas linhas (prepare-se para os exercícios!).

Contudo, existem funções que realizam grande parte do processo de manipulação de dados de uma forma mais simples e compacta (e muitas vezes mais intuitiva, como o dplyr!) e essas funções serão suficientes para a maior parte do seu trabalho!

Nessa seção veremos duas funções base do R: tapply() e aggregate().

DAC com tapply

A função tapply() tem a seguinte estrutura:

str(tapply)
## function (X, INDEX, FUN = NULL, ..., simplify = TRUE)
  • X: o objeto que será agregado. Ex: preços;
  • INDEX: uma lista de vetores que servirão de índice para agregar o objeto. Ex: bairros;
  • FUN: a função que será aplicada a X para cada INDEX. Ex: mediana.
  • simplify: tentará simplificar o resultado para uma estrutura mais simples?

Voltemos, assim, ao nosso exemplo inicial: calcular a mediana do metro quadrado para aluguel e para venda. Como ficaria com o tapply? Como tínhamos visto, basta uma única linha:

tapply(dados$pm2, dados$tipo, median)
##    aluguel      venda
##   35.71429 9000.00000

Podemos passar mais de um fator para a função? Sim! Vamos, por exemplo, calcular a mediana dos preços separadas por aluguel, venda e bairro. Note que o resultado, agora, não é um vetor, mas uma matriz:

tapply(dados$pm2, list(dados$bairro, dados$tipo), median)
##             aluguel    venda
## Asa Norte  35.55556 9000.000
## Asa Sul    38.68472 9210.526
## Lago Norte 34.61538 6753.247
## Lago Sul   32.83582 5516.129
## Noroeste   37.83784 9666.561
## Sudoeste   33.84307 9473.684

É possível colocar mais fatores ainda? Claro! Que tal a mediana por aluguel e venda, separada por tipo de imóvel e por bairro? Ao invés de uma matriz, como passamos três fatores, teremos como resultado um array com três dimensões:

tabelas <- tapply(dados$pm2, list(dados$imovel, dados$tipo, dados$bairro), median)
str(tabelas)
##  num [1:5, 1:2, 1:6] 31.4 29.2 32.7 48 45.7 ...
##  - attr(*, "dimnames")=List of 3
##   ..$ : chr [1:5] "apartamento" "casa" "kitinete" "loja" ...
##   ..$ : chr [1:2] "aluguel" "venda"
##   ..$ : chr [1:6] "Asa Norte" "Asa Sul" "Lago Norte" "Lago Sul" ...

Note que a primeira dimensão separa os resultados por tipo do imóvel; a segunda dimensão, por aluguel e venda; e, por fim, a terceira dimensão separa os resultados por bairros. Deste modo você pode filtrar o array em qualquer uma das três dimensões para selecionar um subconjunto dos valores. Por exemplo, ao selecionar apenas Asa Norte, obtemos uma matriz com as medianas de aluguel e venda, separadas por tipo de imóvel, mas somente deste bairro:

tabelas[,,"Asa Norte"]
##                 aluguel     venda
## apartamento    31.42857  9000.000
## casa           29.16667  5823.529
## kitinete       32.66667  8035.714
## loja           48.00000  5868.545
## sala-comercial 45.71429 10733.453

Já se selecionarmos vendas, obtemos uma matriz com as medianas de venda por tipo de imóvel, separadas por bairro. E assim por diante.

tabelas[,"venda",] # venda por tipo de imóvel e bairro
##                Asa Norte   Asa Sul Lago Norte Lago Sul  Noroeste Sudoeste
## apartamento     9000.000  9066.667   8329.250 5199.120  9654.000 9689.922
## casa            5823.529  5611.728   4375.000 5510.204        NA       NA
## kitinete        8035.714 12142.857   7621.467 8648.649  9641.136 8166.667
## loja            5868.545 11666.667   7818.182 8648.649 18729.515 8800.603
## sala-comercial 10733.453 10346.487  10600.000 6111.111 17872.222 8931.034

DAC com aggregate

O aggregate() é similar ao tapply() mas, ao invés de retornar um array, retorna um data.frame com dados empilhados (veremos mais detalhes sobre dados empilhados ao final deste capítulo) colocando uma coluna diferente para cada índice e apenas uma coluna com os valores.

A função aggregate() tem duas sintaxes principais.

A primeira, similar ao tapply() é:

aggregate(dados$valor, by=list(dados$indice1,
                               dados$indice2), funcao)

Já a segunda sintaxe utiliza a formula interface do R e é do tipo:

aggregate(valor ~ indice1 + indice2, dados, funcao)

Trataremos mais fundo como funcionam fórmulas no R em outro capítulo. Por equanto você pode ler a formula valor~indice1+indice2 da seguinte forma: queremos a variável valor separada (~) pelo indice1 e (+) pelo indice2.

Vejamos um exemplo do aggregate(). Vamos calcular a mediana do preço por metro quadrado, separada por bairro, venda ou aluguel, e tipo de imóvel. Note a diferença do formato deste resultado para o formato do tapply. Ao invés de termos três dimensões, temos um data.frame com uma coluna para cada fator (bairro, tipo e imovel) e apenas uma coluna de valores (pm2):

pm2_bairro_tipo_imovel <- aggregate(pm2 ~ bairro + tipo + imovel, data=dados, median)
str(pm2_bairro_tipo_imovel)
## 'data.frame':    55 obs. of  4 variables:
##  $ bairro: chr  "Asa Norte" "Asa Sul" "Lago Norte" "Lago Sul" ...
##  $ tipo  : chr  "aluguel" "aluguel" "aluguel" "aluguel" ...
##  $ imovel: chr  "apartamento" "apartamento" "apartamento" "apartamento" ...
##  $ pm2   : num  31.4 30.8 34 29.3 37.5 ...
head(pm2_bairro_tipo_imovel)
##       bairro    tipo      imovel      pm2
## 1  Asa Norte aluguel apartamento 31.42857
## 2    Asa Sul aluguel apartamento 30.83333
## 3 Lago Norte aluguel apartamento 34.04255
## 4   Lago Sul aluguel apartamento 29.28870
## 5   Noroeste aluguel apartamento 37.50000
## 6   Sudoeste aluguel apartamento 32.60870

O aggregate() também é mais flexível que o tapply() em outros aspectos. É possível passar mais de uma variável a ser agregada utilizando cbind() e, além disso, passar argumentos para fazer a análise de apenas um subconjunto (subset) dos dados.

Vejamos outro exemplo: vamos calcular a mediana do preço, do metro quadrado e do preço por metro quadrado dos valores de aluguel de apartamento separados por bairro.

mediana_aluguel <- aggregate(cbind(preco, m2, pm2) ~ bairro,
                             data = dados,
                             subset = (dados$tipo=="aluguel" &
                                       dados$imovel=="apartamento"),
                             FUN = median)
mediana_aluguel <- mediana_aluguel[order(mediana_aluguel$pm2, decreasing=TRUE), ]
mediana_aluguel
##       bairro preco   m2      pm2
## 5   Noroeste  2600 74.6 37.50000
## 3 Lago Norte  1800 55.0 34.04255
## 6   Sudoeste  2700 82.0 32.60870
## 1  Asa Norte  2300 70.0 31.42857
## 2    Asa Sul  2800 80.0 30.83333
## 4   Lago Sul  1500 51.0 29.28870

Note que fizemos várias coisas – filtramos o data.frame para selecionar apenas os dados de aluguel de apartamento, separamos por bairro, e calculamos a mediana para três variáveis – diretamente com o aggregate()

Excel, csv e C++ no R. Livro do Alvin Roth, Nova biografia de Steve Jobs. PCO e liberdade de expressão.


Alguns links interessantes:

R

O pessoal do RStudio não para de trabalhar:

Novo pacote (readr) para ler arquivos de texto (csv e similares) no R;

Novo pacote (readxl) para ler arquivos do Excel no R;

Novo pacote (dygraphs) para fazer gráficos interativos de séries temporais no R usando JavaScript; e

O novo RStudio está ficando cada vez mais poderoso: agora tem uma série de recursos novos para C++ como code completion, diagnóstico de sintaxe e source interativo.

Livros

– O Nobel Alvin Roth irá lançar um novo livro para o público geral: Who Gets What — and Why: The New Economics of Matchmaking and Market Design. O livro está em pré-venda, previsto para sair em junho.

– Nova biografia de Steve Jobs está tendo uma boa repercussão no público e na crítica: Becoming Steve Jobs: The Evolution of a Reckless Upstart into a Visionary Leader.

Para finalizar

– Ainda estou na dúvida se é sério, mas o PCO tem um texto  – aparentemente de verdade – defendendo a liberdade de expressão:

Levy Fidelix é um político de direita e inimigo da luta dos homossexuais, e seu discurso foi um ataque direito aos direitos democráticos, mas a condenação dele não é uma vitória da luta pelas liberdades democráticas (…) A multa de um milhão de reais como penalidade para um candidato expressar a sua opinião política em um debate de campanha eleitoral é uma gravíssimo precedente contra a já limitada possibilidade de livre expressão. A crença de que a justiça está do lado do progresso social e da democracia e, por este motivo, podemos dar a ela poderes discricionários, é não só equivocada, como é uma completa cegueira política (…) Nessas condições, é uma política suicida, já não digamos deixar de denunciar estes abusos, mas principalmente aplaudi-los e confundir a consciência das massas, chamando-as apoiar medidas antidemocráticas apenas porque atingem este ou aquele elemento reacionário. (…) A liberdade de expressão, completa e irrestrita, é uma condição sine qua non para a existência das outras liberdades democráticas, ela é uma liberdade que engloba toda a sociedade e que precede todas as liberdades individuais. (…) Existe uma crescente campanha para solidificar o “crime de opinião” o crime onde você pensa algo que alguns não gostaram e naturalmente você é culpado, onde a sua liberdade está reboque da opinião pública, e se sua opinião tem de ter “selo de aprovação” da opinião pública formada pelos monopólios de comunicação e pela burguesia, a opinião própria já está proibida.