Category

Dicas de R

Como fazer web scrapping de tabelas de dados usando o R?

By | Dicas de R

Nem sempre os dados que precisamos estão disponíveis facilmente para download, principalmente quando se trata de fontes públicas de informação. Nestes casos, quando não há uma API ou um arquivo CSV, por exemplo, uma alternativa pode ser implementar a técnica de "raspagem de dados" ou web scrapping, que nada mais é do que coletar dados de páginas na web. Felizmente, no R este procedimento costuma ser relativamente fácil e neste post faremos um exercício simples de demonstração.

Como exemplo, pegaremos os dados de mandatos dos Ministros da Saúde do Brasil disponíveis neste link. A página contém três tabelas com informações sobre os nomes dos ministros, datas de início e fim no mandato e o respectivo governo. Vamos pegar os dados dessas três tabelas com o auxílio de alguns pacotes no R e, ao final, faremos uma simples análise gráfica.

Importante: para este tipo de procedimento recomendamos que utilize o navegador Google Chrome.

1º passo: identificar

Antes de tudo, até mesmo antes de abrir o RStudio, é preciso identificar na página quais informações se deseja coletar. Neste exemplo, os dados estão organizados em três tabelas: República Nova, Ditadura militar, Nova República.

2º passo: navegar

Agora que sabemos o que desejamos obter, o próximo passo é buscar o chamado XPath que nos fornece um caminho para as tabelas, dentro da estrutura da página. Esse caminho servirá para apontar o que exatamente iremos coletar da página, facilitando o trabalho. Para obtê-lo é bem simples: clique com o botão direito em cima da tabela e depois clique em Inspecionar.

Isso fará com que se abra uma seção de ferramentas de desenvolvedor no navegador. Na aba Elements, aparecerá em destaque os códigos HTML do local onde você clicou com o botão direito. Um olhar ligeiro nos mostrará que as três tabelas que estamos buscando possuem essa identificação logo no início: <table width="100%" class="wikitable"> e devem constar próximo à região de destaque aberta. Agora clique com o botão direito sobre essa identificação em qualquer uma das três tabelas, selecione Copy e depois Copy XPath.

Esse procedimento pode variar conforme a página e/ou elemento que se deseja coletar, portanto é importante o instinto da curiosidade.

Feito estes procedimentos você deve ter obtido um código XPath similar a esse:


//*[@id="mw-content-text"]/div[1]/table[1]

Iremos remover a parte final entre colchetes que indica o número da tabela que queremos, assim obteremos todas as três tabelas de uma única vez:


//*[@id="mw-content-text"]/div[1]/table

3º passo: coletar

Agora vamos para o R fazer a coleta dos dados efetivamente. Aqui utilizaremos estes pacotes:

# Instalar/carregar pacotes
if (!require("pacman")) install.packages("pacman")
pacman::p_load(
"tidyverse",
"rvest",
"janitor",
"lubridate"
)

Para organização do código, primeiro definimos os parâmetros XPath e a URL da página, salvando-os em objetos, e depois fazemos a leitura das informações da página usando a função read_html do pacote rvest.

# URL base
www <- "https://pt.wikipedia.org/wiki/Lista_de_ministros_da_Sa%C3%BAde_do_Brasil"

# Xpath da tabela
xpath <- "//*[@id='mw-content-text']/div[1]/table"

# Salvar informações da página
pg <- rvest::read_html(www)

Agora usamos a função html_nodes para extrair exatamente o XPath desejado e transformamos o output em tabela. Em sequência fazemos uma pequena tratativa e unimos as três tabelas. O resultado é uma tibble com 59 linhas.

# Buscar tabelas pelo Xpath e transformar formato
dados <- pg %>%
rvest::html_nodes(xpath = xpath) %>%
rvest::html_table() %>%
purrr::map(~dplyr::mutate(.x, `Nº` = as.character(`Nº`))) %>%
dplyr::bind_rows() %>%
janitor::clean_names()

dplyr::glimpse(dados)
# Rows: 59
# Columns: 5
# $ no <chr> "1", "2", "3", "3", "4", "4", "4", "5", "5", "6", "7", "8",~
# $ nome <chr> "Antônio Balbino", "Miguel Couto Filho", "Mário Pinotti", "~
# $ inicio <chr> "6 de agosto de 1953", "23 de dezembro de 1953", "3 de junh~
# $ fim <chr> "22 de dezembro de 1953", "2 de junho de 1954", "24 de agos~
# $ presidente <chr> "Getúlio Vargas", "Getúlio Vargas", "Getúlio Vargas", "Café~

Simples e rápido, né? E com esses dados em mãos, basta mais algumas tratativas para poder gerar um gráfico como esse:


Com o gráfico é possível observar uma similaridade de mudanças de ministros entre o governo atual e os conturbados governos Collor e Itamar.

 

Dicas de R: fazendo uma classificação KNN

By | Dicas de R

No Dicas de R de hoje, vamos falar como funciona a classificação KNN (K nearest neighbours), aplicar o método em um exemplo, e visualizar o resultado a partir da amostra. O escopo de métodos como o KNN é de problemas de classificação. Ao invés de utilizar dados para prever resultados contínuos como em regressões lineares, são criadas regras de decisão para a classe esperada de uma observação. Ademais, tais classes podem ser utilizadas para a previsão de variáveis supostas contínuas, sendo um dos métodos mais simples de estimação não-paramétrica.

O desenvolvimento de métodos de classificação é baseado em distribuições condicionais. Considerando como melhor classificador aquele que minimiza o valor esperado da taxa de erros de teste (o número de classificações feitas erradas sobre o total, a partir da estimação de treino), podemos provar que a melhor regra de decisão é aquela que assinala a classe de maior probabilidade, dado os valores das variáveis de previsão. Essa regra é chama de classificador de Bayes, e depende do conhecimento completo das distribuições condicionais da variável a ser classificada, logo muitas vezes não é possível utilizá-lo.

Com isso, vamos explicar como funciona o classificador KNN. Após separarmos os dados entre treino e teste, definimos a seguinte regra: para uma observação das variáveis regressoras de teste, são identificados os K pontos de treino mais próximos à posição da amostra, e a probabilidade condicional de cada classe é a frequência relativa de sua ocorrência entre os pontos escolhidos. Desse modo, a classificação estimada para o ponto de teste é igual à classe que ocorre mais vezes dentre os K pontos de treino. A escolha do número K é importante: quanto menor for K, menor será a taxa de erro de treino (para K=1, a amostra será perfeitamente classificada), porém a classificação gerará padrões complicados e pode ter alta taxa de erro de teste, dependendo da amostra. Isso condiz com o tradeoff de viés-variância, de modo que, quanto menor o K, menor é o viés da classificação (pois está mais próxima dos dados reais), porém maior a variância da classificação entre amostras distintas, enquanto que, quanto maior o K, menor é o impacto de amostras diferentes de treino sobre o classificador final, abrindo mão de parte da acurácia do modelo.

Vamos então partir para um exemplo. Os dados utilizados serão do clássico dataset iris, contendo 4 dados de medição de flores, e suas respectivas espécies. Primeiramente, vamos utilizar a classificação KNN para tentar prever a espécie de cada flor a partir das medições de suas pétalas. Gerando os dados:

dados <- iris
class <- dados[,3:5]

train <- c(1:30, 51:80, 101:130)
class_train <- dados[train,]
class_test <- dados[-train,]

Então, podemos realizar a classificação utilizando a função knn(), do pacote class. Seus inputs são os dados de treino, teste, o número K e a classificação correta do treino. Abaixo, o modelo e sua previsão, comparada com os valores reais:


library(class)

pred = knn(train = class_train[,1:2], test = class_test[1:2], cl = class_train[,3], k = 4)

table(pred, class_test[,3])

pred setosa versicolor virginica
setosa 20 0 0
versicolor 0 20 1
virginica 0 0 19

Como podemos ver, o modelo acertou quase todas as classificações, com apenas um erro. Ele funcionou tão bem pois as variáveis se separam em grupos bem distintos, e, como o comprimento das pétalas se diferencia bastante entre as espécies - e possui escala maior, logo tem muito mais impacto na regra de decisão -, ele facilita a regra de decisão. Abaixo, vamos plotar o classificador para K=4, em conjunto com os pontos de teste. O código gera uma malha de pontos que cobrem o gráfico, e então faz a classificação KNN sobre cada um dos pontos da malha. Após isso, geramos um dataframe que indica os pontos onde o contorno deve ser de uma espécie, e onde não deve ser, para cada uma das 3 espécies, de modo a gerar 3 contornos que, por hipótese, não se sobrepõem. Com esse dataframe, podemos utilizar a geom_contour() do ggplot2, criando assim o gráfico abaixo:


library(ggplot2)
library(tidyverse)
library(MASS)

cover <- expand.grid(x=seq(min(class_train[,1]-1), max(class_train[,1]+1),
by=0.1),
y=seq(min(class_train[,2]-1), max(class_train[,2]+1),
by=0.1))

cover_class <- knn(train = class_train[,1:2], cover, cl = class_train[,3], k = 4)

dataf <- bind_rows(mutate(cover,
cls="setosa",
prob_cls=ifelse(cover_class==cls,
T, F)),
mutate(cover,
cls="versicolor",
prob_cls=ifelse(cover_class==cls,
T, F)),
mutate(cover,
cls="virginica",
prob_cls=ifelse(cover_class==cls,
T, F)))

ggplot(dataf) +
geom_point(aes(x=x, y=y, color=cls),
data = mutate(cover, cls=cover_class),
size=1.2) +
geom_contour(aes(x=x, y=y, z=prob_cls, group=cls, color=cls),
bins=2,
data=dataf) +
geom_point(aes(x=Petal.Length, y=Petal.Width, col=Species),
size=3,
data=class_test)+
labs(x='Petal Length',y = 'Petal Width', colour = 'Species')+
theme_minimal()

 

Conteúdos como esse podem ser encontrados no nosso Curso de Machine Learning usando o R.


Dicas de R: usando Python dentro do R

By | Dicas de R

No Dicas de R de hoje, vamos mostrar como você pode rodar código em Python dentro do próprio R, facilitando a integração para pessoas que estão começando no R e já têm conhecimento de Python, ou que querem embutir em seu código o ferramental de Python, que muitas vezes pode agilizá-lo e resolver problemas. Essa integração pode ser feita através do pacote reticulate, como mostraremos a seguir. Vamos supor aqui que você já tem tanto o R como o Python instalados, e também já possui todos os pacotes de Python e de R que quer usar. Para carregar o Python no R, basta rodar:


library(reticulate)

O R irá carregar a sua versão padrão de Python. Se quiser utilizar outra (como ao começar um projeto), basta adicionar o código

use_python("/usr/local/bin/python")

Substituindo o argumento pelo caminho até a versão utilizada. Feito isso, vamos carregar o pandas como exemplo:

 
pd = import('pandas')

Após carregar a library, ela se torna um objeto no R como qualquer outro. Suas funções podem ser acessadas com o operador $, como se fossem colunas. Com isso, o código fica semelhante a um código de Python, usando pd$read_csv ao invés de pd.read_csv, por exemplo.

Para rodar códigos básicos de Python, existem as funções py_run_string(), que recebe strings de código como argumento, e py_run_file(), que recebe um script completo.

Como os objetos de uma linguagem não podem ser operados diretamente por outra, o reticulate transforma objetos de R em objetos de Python sempre que você chama uma função do Python sobre um objeto de seu environment base, e os transforma de volta após finalizar a função. Caso queira trabalhar com objetos de Python ao longo de várias funções, também é possível importar um módulo do Python e impedir que a transformação automática seja feita após suas funções:

 
pd = import('pandas', convert = FALSE)

Com isso, os objetos trabalhados serão de Python, e depois disso para transformá-los em objetos de R basta rodar py_to_r(nome_do_objeto).

O pacote apresentado aqui permite integração total entre o Python e o R, facilitando o uso de ferramentas estatísticas integradas no R para usuários de Python. Para mais informações sobre ele, recomendamos sua página principal, disponível aqui.


Dicas de R: alguns truques para melhorar seus gráficos

By | Dicas de R

No Dicas de R de hoje, vamos apresentar algumas funções que podem melhorar a qualidade dos seus gráficos em ggplot, utilizando para nossas visualizações usando o clássico mtcars. Um gráfico comumente utilizado para a exploração de uma base é o seu histograma, como a seguir:


library(ggplot2)
library(tidyverse)

dados <- mtcars

dados %>% ggplot(aes(x=factor(cyl), y = mpg))+
geom_boxplot()+
labs(y='Milhas por Galão', x = 'Número de cilindros')+
theme_bw()

Porém, podemos utilizar uma alternativa que facilita a visualização da distribuição estimada dos valores, chamada de Kernel Density Estimation. Ela é parente do histograma, e permite uma visualização mais suavizada dos dados. Ademais, com o pacote ggridges, podemos fazer uma visualização semelhante à acima, exibindo os quartis estimados:


library(ggridges)

dados %>% ggplot(aes(x=mpg, y = factor(cyl),
fill = factor(stat(quantile))))+
labs(x='Milhas por Galão', y = 'Número de cilindros')+
scale_x_continuous(breaks = seq(10, 35, by = 5))+
stat_density_ridges(geom = "density_ridges_gradient",
quantile_lines = TRUE, alpha = 1,
rel_min_height = 0.01,
scale = 0.9,
size = 1)+
scale_fill_viridis_d(name = "Quartis")+
theme_minimal()

Agora, vamos adicionar à análise o peso dos carros. Para isso, o modo mais simples de visualizarmos os dados é o gráfico de dispersão:


dados %>% ggplot(aes(x=mpg, y=wt, color=factor(cyl), shape = factor(cyl)))+
geom_point(size = 3) +
labs(x='Milhas por Galão', y = 'Peso', color='Cilindros', shape='Cilindros')

A visualização é facilitada pela inclusão de tanto cores como formatos para cada tipo de variável, permitindo distinguir os pontos que estabelecem a relação descrita. Como podemos ver, a correlação negativa entre as variáveis é fortemente ditada pelos grupos que se definem pelo número de cilindros. Podemos facilitar a visualização da posição dos grupos adicionando elipses em torno de cada um:


dados %>% ggplot(aes(x=mpg, y=wt, color=factor(cyl), shape = factor(cyl)))+
geom_point(size = 3) +
labs(x='Milhas por Galão', y = 'Peso', color='Cilindros', shape='Cilindros')+
stat_ellipse(size=1.05)

 


Visualizando e analisando dados de espécies

By | Dicas de R

No Dicas de R dessa semana, iremos analisar alguns dados sobre espécies, utilizando a base de dados AnAge, disponibilizada no pacote (não disponível no CRAN) hagr. Para instalar ele, devemos rodar o código:


devtools::install_github("datawookie/hagr")

Após isso, basta carregar o pacote e teremos acesso aos dados.

library(hagr)
library(tidyverse)
library(ggplot2)
library(wordcloud2)

dados <- age

levels(factor(dados$data_quality))

dados_limpos <- dados %>% filter((data_quality == "high" | data_quality == "acceptable") &
kingdom == "Animalia" &
(sample_size == "large" | sample_size == "medium"),
phylum == "Chordata")

Devemos notar que o pessoal aqui da Análise Macro não é especializado em biologia, e as ferramentas utilizadas aqui no Dicas têm intuito de serem apresentadas apenas como modos de utilizar a programação em R, logo se houverem erros específicos ao tema, comentários são bem-vindos. Os dados contêm os nomes de cada espécie, e, como no post de semana passada, pode ser interessante verificar os animais com maior quantidade de espécies. Iremos fazer isso através de um wordcloud:



nomes <- dados_limpos %>% select(common_name) %>%
mutate(principal = word(common_name, -1))

wordcloud2(table(unlist(nomes$principal)))

Como podemos ver, alguns dos animais com mais espécies são sapos, lagartos, morcegos e ratos. Outra relação que podemos verificar é entre a massa dos animais e sua taxa de metabolismo:


met <- dados_limpos %>% filter(metabolic_rate_watt>0)

ggplot(data = met, aes(x=body_mass_g, y = metabolic_rate_watt, color = class)) +
geom_point()+scale_x_continuous(trans = 'log10') +
scale_y_continuous(trans = 'log10')+
labs(title = "Comparação entre massa corporal e taxa de metabolismo",
x="Massa", y="Taxa de metabolismo")+
theme_minimal()+
theme(legend.title = element_blank())

Como podemos ver, a relação é crescente para todos os grupos que possuem dados. É interessante notar que animais de sangue quente, como mamíferos e aves, parecem ter - na média- taxas de metabolismo maiores do que animais de sangue frio, como répteis e anfíbios. Vamos então testar estatisticamente essa relação, analisando as populações de mamíferos e répteis - supomos aqui que espécies são amostras independentes, o que pode não ser verdade dependendo de quão próximos são os animais identificados, ferindo o TCL e inviabilizando o teste t de Student. Ademais, os dados para mamíferos não aparentam seguir distribuição normal, como podemos ver a seguir:


par(mfrow=c(1,2))
qqnorm(log10(filter(met, class=='Mammalia')$metabolic_rate_watt)); qqline(log10(filter(met, class=='Mammalia')$metabolic_rate_watt))
qqnorm(log10(filter(met, class=='Reptilia')$metabolic_rate_watt)); qqline(log10(filter(met, class=='Reptilia')$metabolic_rate_watt))

shapiro.test(log10(filter(met, class=='Mammalia')$metabolic_rate_watt))
shapiro.test(log10(filter(met, class=='Reptilia')$metabolic_rate_watt))

A distribuição de taxa de metabolismo de mamíferos (à esquerda) apresenta caudas pesadas em comparação à normal.

Além dos gráficos Q-Q, a estatística de Wilk-Shapiro para a amostra tem p-valor 0.04, indicando rejeição da hipótese nula de normalidade. Tanto o gráfico Q-Q como a estatística para répteis (p-valor de 0.99) indicam normalidade dessa amostra, logo o problema só ocorre com mamíferos. Com isso, como a amostra para mamíferos é grande (n=167), iremos aplicar o teste t, dado que a média das observações será razoavelmente normal. Ademais, para contornar a possibilidade do tamanho da amostra não ser grande o bastante - e também o problema de independência dentro de cada amostra já observado -, também iremos aplicar o teste de Wilcoxon-Mann-Whitney, que é não-paramétrico e não faz hipóteses sobre as distribuições das amostras. Os códigos para os testes são:


t.test(log10(filter(met, class=='Mammalia')$metabolic_rate_watt), log10(filter(met, class=='Reptilia')$metabolic_rate_watt),
alternative = 'two.sided')

wilcox.test(log10(filter(met, class=='Mammalia')$metabolic_rate_watt), log10(filter(met, class=='Reptilia')$metabolic_rate_watt))

O resultado dos testes é de p-valor menor que 0.01 para ambos, evidenciando assim que a média entre as duas populações é estatisticamente diferente.


Seja avisado da nossa próxima aula ao vivo de R para Análise de Dados, toda terça-feira, às 21h!

Quero ser avisado
{"cart_token":"","hash":"","cart_data":""}