Neste breve tutorial vamos ver alguns exemplos de como criar funções e utilizar processos iterativos (loops), e sua forma vetorializada (famílias de funções apply e map) facilitam a organização do código, reduzem bastante o esforço de programação e tornam o programa mais eficiente.
Estes exemplos servirão de norte para que você leia, em sala, os capítulos 19 - Functions, 20 - Iteration, e o trecho do capítulo 18 - Vectors que trata de listas.
Imagine que você precisa organizar dados dos candidatos à presidência de diversos anos (2006 a 2014) e vai utilizar a API do CEPESP Data. Utilizando a função get_candidates do pacote cepespR podemos obter os dados:
<<<<<<< HEADlibrary(cepespR)
=======
if (!require("devtools")) install.packages("devtools")
devtools::install_github("Cepesp-Fgv/cepesp-r")
library(cepespR)
>>>>>>> f1fa47223e3e989580fbb77334ef74b00b876a86
candidatos06 <- get_candidates(year = 2006, position = "President")
candidatos10 <- get_candidates(year = 2010, position = "President")
candidatos14 <- get_candidates(year = 2014, position = "President")
e, a seguir, podemos juntar todos em um único data_frame:
library(tidyverse)
candidatos <- bind_rows(candidatos06, candidatos10, candidatos14)
Simples, não? Imagine agora que você quer repetir a mesma tarefa não 4, mas centenas de vezes. O código deixaria de ser simples e passaria a ter centenas de linhas.
Essa centena de linhas seria pura repetição de código. No nosso exemplo, a obtenção dos dados para candidatos à presidência em 2006 e em 2010 são idênticas, exceto por 2 números. Como podemos evitar a repetição de códigos?
Neste tutorial vamos rever dois tópicos pelos quais passamos brevemente no curso e que são fundamentais para tornar o seu código mais enxuto e eficiente: funções e iterações (loops e similares).
No exemplo acima, um simples for loop resolveria nosso problema de repetição de código. Veja abaixo e tente entender sozinha(o) o que está acontencendo:
vetor_anos <- c(2006, 2010, 2014)
candidatos <- data.frame()
for (ano in vetor_anos){
candidatos <- bind_rows(candidatos,
get_candidates(year = ano, position = "President"))
}
Simples, não? Se quiséssemos utilizar mais anos em nossa análise, bastaria mudar o vetor “percorrido” pelo loop que contém o número de anos.
Vamos supor agora que não queremos juntar todos os anos, apenas contar o número de linhas (que é o número de candidatos) em cada um dos anos e armazenar o resultado em um vetor. Veja como fazemos isso com um for loop:
n_candidatos <- c()
for (ano in vetor_anos){
n_candidatos <- c(n_candidatos,
get_candidates(year = ano, position = "President") %>%
nrow())
}
Igualmente simples. Há outras maneiras de produzir o mesmo resultado em R sem utilizar for loops. Note que podemos pensar um for loop como uma função que recebe como argumento um vetor e realiza a mesma tarefa para cada elemento do vetor.
Há duas famílias de funções em R que contêm soluções alternativas – e mais eficientes – do que o for loop para executar a mesma tarefa para cada item de um vetor: a família “apply”, do pacote base, e a família “map”, do pacote purrr (do tidyverse).
Vamos ver como ficaria o código acima utilizando funções de ambas as famílias. Em ambas situações escrevemos uma função dentro da própria função e a aplicamos a cada elemento do vetor. Por enquanto, apenas examine o código. Aprenderemos mais sobre tais funções no futuro.
Em primeiro lugar, vamos usar a função sapply (pacote base):
n_candidatos <- sapply(vetor_anos, function(x){
get_candidates(year = x, position = "President") %>%
nrow()
}
)
Agora com a função map_dbl (pacote purrr – “dbl” é abreviação de “double”):
library(purrr)
n_candidatos <- map_dbl(vetor_anos, function(x){
get_candidates(year = x, position = "President") %>%
nrow()
}
)
Note que nos dois casos acima precisamos construir uma função (que, no caso, é a combinação das funções nrow e candidates).
Uma das bases mais estudadas em política comparada e estudos empíricos é a Polity IV, que contém varáveis sobre diversas características de um conjunto grande de países e em vários anos. Quem quiser conhecer mais sobre os dados pode acessar aqui ou ler a documentação aqui.
A principal variável da base de dados é um indicador de grau de democracia que resulta da combinação de um conjunto variáveis componentes codificadas diretamente a partir da observação dos casos. Vamos ignorar seus significados e apenas observar que essas variáveis componentes recebem valores de 0 a 7, se o caso for uma democracia, ou os códigos -66, -77 e -88 em períodos autoritários ou de transição.
Comece abrindo os dados que estão no repositório do curso e criando uma cópia, ‘p4’, que será modificada.
p4_raw <- read.csv2("https://raw.githubusercontent.com/leobarone/FLS6397_2018/master/data/p4v2016.csv")
p4 <- p4_raw
Como as variáveis contêm alguns códigos numéricos cujas distâncias matemáticas não fazem nenhum sentido (-66, -77 e -88), precisamos transformá-los em NA para podermos calcular qualquer estatística descritiva com a variável. Vamos realizar a transformação nas variáveis ‘xconst’, ‘xrreg’, ‘xropen’, ‘xconst’ e aprender um novo operador, %in%. Todas as variáveis se referem sobre características da competição pelo poder Executivo em um país em um ano:
p4$xrcomp[p4$xrcomp %in% c(-66, -77, -88)] <- NA
p4$xrreg[p4$xrreg %in% c(-66, -77, -88)] <- NA
p4$xropen[p4$xropen %in% c(-66, -77, -88)] <- NA
p4$xconst[p4$xconst %in% c(-66, -77, -88)] <- NA
Como vamos repetir a mesma transformação de variáveis diversas vezes, convém escrever uma função para tal transformação. Observe com cuidado o código abaixo:
limpa_var <- function(x) {
x[x %in% c(-66, -77, -88)] <- NA
return(x)
}
Vamos refazer o código acima utilizando a função que acabamos de criar (lembre-se criar um novo objeto ‘p4’, pois as variáveis foram transformadas no código anterior):
p4 <- p4_raw
p4$xrcomp <- limpa_var(p4$xrcomp)
p4$xrreg <- limpa_var(p4$xrreg)
p4$xropen <- limpa_var(p4$xropen)
p4$xconst <- limpa_var(p4$xconst)
Melhor. Temos linhas mais enxutas. Se estívessemos aplicando transformações mais complexas às variáveis, encurtaríamos bastante o código.
Ainda assim, temos muitas repetições de linha. O que muda de uma linha à outra é apenas o nome da variável. Como vimos no caso anterior, podemos realizar tarefas repetidas em loop. Vamos, dessa forma, aplicar a função que criamos em loop:
p4 <- p4_raw
vetor_var <- c('xrcomp', 'xrreg', 'xropen', 'xconst')
for (var in vetor_var){
p4[, var] <- limpa_var(p4[, var])
}
Se estívessemos utilizando todas as variáveis do banco de dados codificadas da mesma maneira (são várias) teríamos uma economia bastante importante de código.
Obs: uma forma alternativa de selecionar variáveis de um data frame utilizando colchetes é aplicando colchetes duplo (em vez de separar linhas e colunas dentro do colchetes por vírgula). O estilo de código abaixo, encontrado com frequência no livro “R for Data Science”, é equivalente ao que acabamos de ver.
for (var in vetor_var){
p4[[var]] <- limpa_var(p4[[var]])
}
Vamos aproveitar o exemplo acima e comparar as médias das variáveis. Poderíamos simplesmente aplicar as funções de média e guardar o resultado em um vetor:
xrcomp_mean <- mean(p4$xrcomp, na.rm = T)
xrreg_mean <- mean(p4$xrreg, na.rm = T)
xropen_mean <- mean(p4$xropen, na.rm = T)
xconst_mean <- mean(p4$xconst, na.rm = T)
medias <- c(xrcomp_mean, xrreg_mean, xropen_mean, xconst_mean)
Deve estar claro para você agora que este código é desnecessariamente longo, mesmo para apenas 4 variáveis. Vamos utilizar um loop para em vez de repetir o código diversas vezes:
medias <- c()
for (var in vetor_var){
medias <- c(medias, mean(p4[[var]], na.rm = T))
}
medias
Mais simples do que um loop, podemos utilizar a função sapply
sapply(p4[vetor_var], mean, na.rm = T)
ou a função map_dbl:
map_dbl(p4[vetor_var], mean, na.rm = T)
Quando estamos tratando de data frames, há soluções implementadas para realizar exatamente o que fizemos acima de forma bastante eficiente. Note que, ainda assim, precisaremos de funções e loops para outros tipos de problemas (como webscraping, por exemplo).
Seguindo com o nosso exemplo, podemos calcular a média de um conjunto de variáveis utilizando o verbo summarise_all, cujo comportamento se assemelha ao de summarise, mas permite a aplicação a um data frame e não apenas a uma variável:
summarise_all(p4[vetor_var], mean, na.rm = T)
ou, utilizando o pipe:
p4[vetor_var] %>%
summarise_all(mean, na.rm = T)
Caso todas as variáveis tenham nome semelhante (por exemplo, comecem com o mesmo prefixo), não precisamos restringir o data frame, como fizemos acima, se utilizarmos a função summarise_at, que seleciona variáveis
p4 %>%
summarise_at(vars(starts_with("x")), mean, na.rm = T)
Existem outros “helpers” para selecionar variáveis com base em regularidade dos nomes e você pode encontrar mais sobre isso aqui.
summarise_if tem comportamento análogo a summarise_at.
Tal como summarise, podemos aplicar uma transformação a todas as variáveis de um data frame com mutate_all. Lembre-se de copiar “p4” novamente.
p4 <- p4_raw
vetor_var <- c('xrcomp', 'xrreg', 'xropen', 'xconst')
p4[vetor_var] <- p4[vetor_var] %>% mutate_all(limpa_var)
mutate_at também pode ser usada para transformar variáveis a partir de regularidade nos nomes das variáveis:
p4 %>%
mutate_at(vars(starts_with("x")), limpa_var)
Finalmente, essas variações de mutate e summarise pode ser combinadas com group_by para gerar transformações e sumários, respectivamente, por uma variável de agrupamento.
Por exemplo, podemos calcular as médias por ano (vamos filtrar para anos acima de 2010) das 4 variáveis com as quais temos trabalhando:
p4 %>%
filter(year > 2010) %>%
group_by(year) %>%
summarise_at(vars(starts_with("x")), mean, na.rm = T)
Vamos voltar ao nosso primeiro exemplo, no qual abrimos dados do CEPESPData em loop. Se você observar com cuidado, verá que nosso primeiro exemplo não teve nenhuma alternativa ao uso do for loop. Por que?
Para obter uma resposta adequada, precisamos reponder a outras perguntas: como abrir um conjunto de data frames ao mesmo tempo sem combiná-los em um único data frame?
Existe alguma classe de objeto que nos permite armazenar em um único objeto vários data frames (eventualmente tão diferentes entre si a ponto de não serem “combináveis”)? Sim, existe. Essa classe de objeto se chama “list” (listas).
Vamos voltar ao exemplo. Nossa solução com loop foi:
vetor_anos <- c(2006, 2010, 2014)
candidatos <- data.frame()
for (ano in vetor_anos){
candidatos <- bind_rows(candidatos,
get_candidates(year = ano, position = "President"))
}
Agora, vamos substituir o for loop pela função do tipo map, que é a função mais básica da família. Seu comportamento é o seguinte: a partir de um vetor, a função retorna uma lista que tem, em cada posição, o resultado da transformação de cada elemento do vetor.
candidatos_lista <- vetor_anos %>% map(function(x) {get_candidates(year = x,
position = "President")})
Note que, no nosso caso, um elemento do vetor (ano) gera um data frame (de candidatos). Ao retornar uma lista, que é um objeto bastante flexível, não precisamos lidar com o fato de que os elementos da lista não necessariamente se combinam (no nosso caso, felizmente, combinam).
Também existe uma forma mais simples de realizar uma operação do map, substituindo a definição de função com ‘~’ e ‘x’ com ‘.’. O resultado é o mesmo.
candidatos_lista <- vetor_anos %>% map(~get_candidates(year = .,
position = "President"))
Veremos, na leitura indicada para esta aula, o que são listas. Quando passarmos a outros tópicos, como webscraping, listas serão fundamentais.
Há diversas maneiras de tirar objetos de dentro de uma lista. Com dois colchetes, podemos extrair um elemento de uma posição específica (por exemplo, o data frame com os candidatos de 2006, que está na segunda posição):
head(candidatos_lista[[2]])
Há também funções que nos ajudam a combinar elementos de uma lista (se forem combináveis). Para o nosso caso, a função bind_rows resolverá o problema. Veja a aplicação de map e bind_rows em conjunto (com pipe):
candidatos <- vetor_anos %>%
map(~get_candidates(year = ., position = "President")) %>%
bind_rows()
Incrível, não? Em poucas linhas de código fazemos muitas coisas.
Refere-se os dados de ‘Polity’ baixados anteriormente (p4_raw).
1. Use um for loop para identificar o valor máximo na tabela das três variáveis: parcomp (a competitividade da participação), polcomp (a competitividade da política) e polity2 (o indicador geral da democracia).
2. Use sapply ou map para realizar a mesma coisa.
3. Use summarise_at para realizar a mesma coisa.
4. Use summarise_at com group_by para calcular a valor máximo de cada variável em cada país.
5. (Desafiador) Use summarise_at com group_by para calcular a valor máximo e mínimo de cada variável em cada país, e depois identificar o país que teve a variação mais extrema em democria usando o indicador polity2. Hint: Podemos aplicar duas funções ao mesmo tempo usando funs(fun1,fun2) dentro de summarise_at. Lembre-se também de substituir os valores -66, -77 e -88 por NA.
Vamos, agora, às leituras.