O objetivo deste rápido tutorial é introduzir algumas funções dos pacotes dplyr e tidyr úteis para manipulação de conjuntos de dados em R. Esta é uma tarefa essencial, uma vez que os dados nem sempre chegam ao pesquisador num formato que permita a aplicação direta dos recursos do computacionais. Portanto, colocar os dados em formato adequado muitas vezes pode ser tão importante quanto (e até mais trabalhoso) do que a análise em si.
O pacotes dplyr e tidyr possuem uma gama de funções que permitem remoldar os dados com apenas alguns comandos. Utilizaremos o conjuntos de dados flags (ver documentação) para fornecer uma abordagem prática para os principais comandos de dplyr e tidyr.
Primeiramente vamos baixar os dados diretamente da web. O banco de dados flags constitui-se numa lista de 194 países com 30 atributos sobre sua geografia (área, população) e suas bandeiras (presença de cores, faixas, estrelas, etc.)
OBS: como nos dados originais não há um cabeçalho com os nomes das variáveis, foi necessário adicionar manualmente.
# data wrangling tutorial:
library(plyr)
suppressPackageStartupMessages(library(dplyr))
library(tidyr)
library(microbenchmark)
# packages plyr, dplyr, tidyr
flags <- read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/flags/flag.data", sep=",")
atributos <- c("name", "landmass", "zone", "area", "population", "language", "religion", "bars", "stripes", "colours",
"red", "green", "blue", "gold", "white", "black", "orange", "mainhue", "circles", "crosses", "saltires",
"quarters", "sunstars", "crescent", "triangle", "icon", "animate", "text", "topleft", "botright")
colnames(flags) <- atributos
fullData = flags
# temos o data frame, agora vamos seguir os passos do cheatsheet
Por enquanto, a variável flags é um dataframe. Para o uso do pacote dplyr é conveniente converter os dados para outra estrutura chamada tbl. Em seguida, a função distinct() permite remover linhas repetidas.
# transformar os dados para a classe tbl:
flags <- tbl_df(flags)
distinct(flags)
## # A tibble: 194 × 30
## name landmass zone area population language religion bars
## <fctr> <int> <int> <int> <int> <int> <int> <int>
## 1 Afghanistan 5 1 648 16 10 2 0
## 2 Albania 3 1 29 3 6 6 0
## 3 Algeria 4 1 2388 20 8 2 2
## 4 American-Samoa 6 3 0 0 1 1 0
## 5 Andorra 3 1 0 0 6 0 3
## 6 Angola 4 2 1247 7 10 5 0
## 7 Anguilla 1 4 0 0 1 1 0
## 8 Antigua-Barbuda 1 4 0 0 1 1 0
## 9 Argentina 2 3 2777 28 2 0 0
## 10 Argentine 2 3 2777 28 2 0 0
## # ... with 184 more rows, and 22 more variables: stripes <int>,
## # colours <int>, red <int>, green <int>, blue <int>, gold <int>,
## # white <int>, black <int>, orange <int>, mainhue <fctr>, circles <int>,
## # crosses <int>, saltires <int>, quarters <int>, sunstars <int>,
## # crescent <int>, triangle <int>, icon <int>, animate <int>, text <int>,
## # topleft <fctr>, botright <fctr>
Note que a observação relativa à Argentina é repetida, mas a função distinct() não removeu a linha, já que na linha repetida consta “Argentine”, portanto, as duas linhas não seriam iguais. O comando abaixo resolve este problema:
# removendo a linha repetida "Argentine"
for(i in 1:nrow(flags)){
if(flags[i,1] == "Argentine")
flags <- flags[-i, ]
}
Como o objetivo aqui é mostrar como trabalhar com o pacote, selecionaremos uma parte dos dados selecionar aleatoriamente uma parte dos dados. Isto pode ser feito de duas maneiras, a saber, selecionando uma porcentagem dos dados, utilizando a função sample_frac() ou um número determinado, utilizando a função sample_n().
Por exemplo, para selecionar aleatoriamente 80% dos objetos com sample_frac(), temos:
sample_frac(flags, 0.8, replace=FALSE)
## # A tibble: 155 × 30
## name landmass zone area population language religion
## <fctr> <int> <int> <int> <int> <int> <int>
## 1 Peru 2 3 1285 14 2 0
## 2 Zimbabwe 4 2 391 8 10 5
## 3 Haiti 1 4 28 6 3 0
## 4 Tanzania 4 2 945 18 10 5
## 5 Cape-Verde-Islands 4 4 4 0 6 0
## 6 Soloman-Islands 6 2 30 0 1 1
## 7 Botswana 4 2 600 1 10 5
## 8 Faeroes 3 4 1 0 6 1
## 9 North-Yemen 5 1 195 9 8 2
## 10 Japan 5 1 372 118 9 7
## # ... with 145 more rows, and 23 more variables: bars <int>,
## # stripes <int>, colours <int>, red <int>, green <int>, blue <int>,
## # gold <int>, white <int>, black <int>, orange <int>, mainhue <fctr>,
## # circles <int>, crosses <int>, saltires <int>, quarters <int>,
## # sunstars <int>, crescent <int>, triangle <int>, icon <int>,
## # animate <int>, text <int>, topleft <fctr>, botright <fctr>
Vamos escolher aleatoriamente 5 países para que seja possível visualizar as saídas das funções. Em seguida, usamos o comando glimpse(), que permite visualizar os dados com os atributos como linhas e os objetos como colunas. A tabela apresenta 3 variáveis para facilitar a visualização:
# selecionar aleatoriamente um número de linhas dos dados
flags = sample_n(flags, 5, replace=FALSE)
glimpse(flags)
## Observations: 5
## Variables: 30
## $ name <fctr> Indonesia, South-Korea, Luxembourg, Peru, Kenya
## $ landmass <int> 6, 5, 3, 2, 4
## $ zone <int> 2, 1, 1, 3, 1
## $ area <int> 1904, 99, 3, 1285, 583
## $ population <int> 157, 39, 0, 14, 17
## $ language <int> 10, 10, 4, 2, 10
## $ religion <int> 2, 7, 0, 0, 5
## $ bars <int> 0, 0, 0, 3, 0
## $ stripes <int> 2, 0, 3, 0, 5
## $ colours <int> 2, 4, 3, 2, 4
## $ red <int> 1, 1, 1, 1, 1
## $ green <int> 0, 0, 0, 0, 1
## $ blue <int> 0, 1, 1, 0, 0
## $ gold <int> 0, 0, 0, 0, 0
## $ white <int> 1, 1, 1, 1, 1
## $ black <int> 0, 1, 0, 0, 1
## $ orange <int> 0, 0, 0, 0, 0
## $ mainhue <fctr> red, white, red, red, red
## $ circles <int> 0, 1, 0, 0, 1
## $ crosses <int> 0, 0, 0, 0, 0
## $ saltires <int> 0, 0, 0, 0, 0
## $ quarters <int> 0, 0, 0, 0, 0
## $ sunstars <int> 0, 0, 0, 0, 0
## $ crescent <int> 0, 0, 0, 0, 0
## $ triangle <int> 0, 0, 0, 0, 0
## $ icon <int> 0, 1, 0, 0, 1
## $ animate <int> 0, 0, 0, 0, 0
## $ text <int> 0, 0, 0, 0, 0
## $ topleft <fctr> red, white, red, red, black
## $ botright <fctr> white, white, blue, red, green
library(knitr)
flags %>% select(.,name,area,population) %>% kable(.,caption="Seleção aleatória de 5 observações")
| name | area | population |
|---|---|---|
| Indonesia | 1904 | 157 |
| South-Korea | 99 | 39 |
| Luxembourg | 3 | 0 |
| Peru | 1285 | 14 |
| Kenya | 583 | 17 |
A função gather() pertence ao pacote tidyr converte as colunas de dados para o formato chave/valor. Os atributos desejados são colocados na coluna “key” e seus respectivos valores na coluna “value”. Note que fazemos uso, pela primeira vez, do operador %>%, que torna o código em R mais legível. Por exemplo, a notação usual equivalente ao primeiro comando abaixo seria
gather(flags,key="key", value = "value", -name)
Por meio deste comando, convertemos os dados para a notação chave/valor, onde as chaves são todas as variáveis, exceto o nome dos países.
gathered_data = flags %>% gather(., key="key", value = "value", -name)
## Warning: attributes are not identical across measure variables; they will
## be dropped
gathered_data
## # A tibble: 145 × 3
## name key value
## <fctr> <chr> <chr>
## 1 Indonesia landmass 6
## 2 South-Korea landmass 5
## 3 Luxembourg landmass 3
## 4 Peru landmass 2
## 5 Kenya landmass 4
## 6 Indonesia zone 2
## 7 South-Korea zone 1
## 8 Luxembourg zone 1
## 9 Peru zone 3
## 10 Kenya zone 1
## # ... with 135 more rows
A função spread, do pacote tidyr, pega as chaves e as transforma em atributos - como tínhamos anteriormente. Então, as funções spread e gather são complementares. Uma desfaz o que a outra faz.
gathered_data %>% spread(., key, value)
## # A tibble: 5 × 30
## name animate area bars black blue botright circles colours
## * <fctr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 Indonesia 0 1904 0 0 0 white 0 2
## 2 Kenya 0 583 0 1 0 green 1 4
## 3 Luxembourg 0 3 0 0 1 blue 0 3
## 4 Peru 0 1285 3 0 0 red 0 2
## 5 South-Korea 0 99 0 1 1 white 1 4
## # ... with 21 more variables: crescent <chr>, crosses <chr>, gold <chr>,
## # green <chr>, icon <chr>, landmass <chr>, language <chr>,
## # mainhue <chr>, orange <chr>, population <chr>, quarters <chr>,
## # red <chr>, religion <chr>, saltires <chr>, stripes <chr>,
## # sunstars <chr>, text <chr>, topleft <chr>, triangle <chr>,
## # white <chr>, zone <chr>
Para ordenar as observações de acordo com os valores de um atributo, use a função arrange(). Por exemplo, vamos ordenar os países de acordo com a área, do maior para o menor…
flags %>% arrange(., desc(area))
## # A tibble: 5 × 30
## name landmass zone area population language religion bars
## <fctr> <int> <int> <int> <int> <int> <int> <int>
## 1 Indonesia 6 2 1904 157 10 2 0
## 2 Peru 2 3 1285 14 2 0 3
## 3 Kenya 4 1 583 17 10 5 0
## 4 South-Korea 5 1 99 39 10 7 0
## 5 Luxembourg 3 1 3 0 4 0 0
## # ... with 22 more variables: stripes <int>, colours <int>, red <int>,
## # green <int>, blue <int>, gold <int>, white <int>, black <int>,
## # orange <int>, mainhue <fctr>, circles <int>, crosses <int>,
## # saltires <int>, quarters <int>, sunstars <int>, crescent <int>,
## # triangle <int>, icon <int>, animate <int>, text <int>, topleft <fctr>,
## # botright <fctr>
…e do menor para o maior:
#(do menor para o maior)
flags %>% arrange(., area)
## # A tibble: 5 × 30
## name landmass zone area population language religion bars
## <fctr> <int> <int> <int> <int> <int> <int> <int>
## 1 Luxembourg 3 1 3 0 4 0 0
## 2 South-Korea 5 1 99 39 10 7 0
## 3 Kenya 4 1 583 17 10 5 0
## 4 Peru 2 3 1285 14 2 0 3
## 5 Indonesia 6 2 1904 157 10 2 0
## # ... with 22 more variables: stripes <int>, colours <int>, red <int>,
## # green <int>, blue <int>, gold <int>, white <int>, black <int>,
## # orange <int>, mainhue <fctr>, circles <int>, crosses <int>,
## # saltires <int>, quarters <int>, sunstars <int>, crescent <int>,
## # triangle <int>, icon <int>, animate <int>, text <int>, topleft <fctr>,
## # botright <fctr>
Podemos selecionar observações de acordo com um critério lógico usando a função filter(). No exemplo, selecionamos apenas os países com população maior que 40 milhões e/ou área maior que 200.
# função filter
flags %>% filter(., population > 20, area>50)
## # A tibble: 2 × 30
## name landmass zone area population language religion bars
## <fctr> <int> <int> <int> <int> <int> <int> <int>
## 1 Indonesia 6 2 1904 157 10 2 0
## 2 South-Korea 5 1 99 39 10 7 0
## # ... with 22 more variables: stripes <int>, colours <int>, red <int>,
## # green <int>, blue <int>, gold <int>, white <int>, black <int>,
## # orange <int>, mainhue <fctr>, circles <int>, crosses <int>,
## # saltires <int>, quarters <int>, sunstars <int>, crescent <int>,
## # triangle <int>, icon <int>, animate <int>, text <int>, topleft <fctr>,
## # botright <fctr>
flags %>% filter(., population > 20 | area>50)
## # A tibble: 4 × 30
## name landmass zone area population language religion bars
## <fctr> <int> <int> <int> <int> <int> <int> <int>
## 1 Indonesia 6 2 1904 157 10 2 0
## 2 South-Korea 5 1 99 39 10 7 0
## 3 Peru 2 3 1285 14 2 0 3
## 4 Kenya 4 1 583 17 10 5 0
## # ... with 22 more variables: stripes <int>, colours <int>, red <int>,
## # green <int>, blue <int>, gold <int>, white <int>, black <int>,
## # orange <int>, mainhue <fctr>, circles <int>, crosses <int>,
## # saltires <int>, quarters <int>, sunstars <int>, crescent <int>,
## # triangle <int>, icon <int>, animate <int>, text <int>, topleft <fctr>,
## # botright <fctr>
No entanto, há uma contraindicação ao uso da função filter(): a função subset() cumpre o mesmo papel de modo muito mais eficiente, como pode ser visto nos testes de desempenho abaixo:
# note que a função subset é bem mais rápida:
microbenchmark(filter(flags, population > 20 | area>50),
subset(flags, population>20 | area>50))
## Unit: microseconds
## expr min lq mean
## filter(flags, population > 20 | area > 50) 692.540 730.326 855.3812
## subset(flags, population > 20 | area > 50) 134.434 150.644 186.4928
## median uq max neval cld
## 786.6245 887.0295 2366.651 100 b
## 172.3845 198.4125 466.470 100 a
microbenchmark(filter(flags, population >20,area>50),
subset(flags, population>20,area>50))
## Unit: microseconds
## expr min lq mean
## filter(flags, population > 20, area > 50) 748.192 803.660 941.43112
## subset(flags, population > 20, area > 50) 63.289 75.728 98.91214
## median uq max neval cld
## 867.1455 979.4240 3093.432 100 b
## 95.0750 111.0395 183.388 100 a
Para obter medidas-resumo das variáveis (mínimo, máximo, média, mediana, variância e desvio padrão), utilizam-se as funções summarise_each() (para todas as variáveis) e summarise() (para variáveis selecionadas).
Note que, quando aplicada para todas as colunas, o comando summarise_each(…, funs(mean)) retorna algumas advertências: elas referem-se ao fato de que não podemos calcular a média para fatores. No caso das variáveis booleanas, a média expressará a proporção de casos positivos, por exemplo, a proporção de países que têm azul em sua bandeira.
## Summarise data
flags %>% summarise_each(., funs(mean))
## Warning in mean.default(structure(c(83L, 155L, 103L, 137L, 94L), .Label =
## c("Afghanistan", : argument is not numeric or logical: returning NA
## Warning in mean.default(structure(c(7L, 8L, 7L, 7L, 7L), .Label =
## c("black", : argument is not numeric or logical: returning NA
## Warning in mean.default(structure(c(6L, 7L, 6L, 6L, 1L), .Label =
## c("black", : argument is not numeric or logical: returning NA
## Warning in mean.default(structure(c(8L, 8L, 2L, 7L, 5L), .Label =
## c("black", : argument is not numeric or logical: returning NA
## # A tibble: 1 × 30
## name landmass zone area population language religion bars stripes
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NA 4 1.6 774.8 45.4 7.2 2.8 0.6 2
## # ... with 21 more variables: colours <dbl>, red <dbl>, green <dbl>,
## # blue <dbl>, gold <dbl>, white <dbl>, black <dbl>, orange <dbl>,
## # mainhue <dbl>, circles <dbl>, crosses <dbl>, saltires <dbl>,
## # quarters <dbl>, sunstars <dbl>, crescent <dbl>, triangle <dbl>,
## # icon <dbl>, animate <dbl>, text <dbl>, topleft <dbl>, botright <dbl>
flags %>% summarise(., meanArea = mean(area), meanPop = max(population),meanBlack = mean(black),
meanBlue = mean(blue), meanGold = mean(gold)) %>% kable(.,align='l')
| meanArea | meanPop | meanBlack | meanBlue | meanGold |
|---|---|---|---|---|
| 774.8 | 157 | 0.4 | 0.4 | 0 |
Ás vezes pode ser interessante agrupar observações por fatores de uma variável. Por exemplo, podemos agrupar os países de acordo com a divisão geográfica a qual pertencem. A função ddply() cumpre esta função. No exemplo abaixo, agrupamos os países de acordo com o continente e com a religião dominante.
OBS: nos dados utilizados aqui, América Central e do Norte foram agrupadas em um só continente.
# ddply function (agupar segundo alguma variável)
flags %>% ddply(., .(landmass))
## name landmass zone area population language religion bars stripes
## 1 Peru 2 3 1285 14 2 0 3 0
## 2 Luxembourg 3 1 3 0 4 0 0 3
## 3 Kenya 4 1 583 17 10 5 0 5
## 4 South-Korea 5 1 99 39 10 7 0 0
## 5 Indonesia 6 2 1904 157 10 2 0 2
## colours red green blue gold white black orange mainhue circles crosses
## 1 2 1 0 0 0 1 0 0 red 0 0
## 2 3 1 0 1 0 1 0 0 red 0 0
## 3 4 1 1 0 0 1 1 0 red 1 0
## 4 4 1 0 1 0 1 1 0 white 1 0
## 5 2 1 0 0 0 1 0 0 red 0 0
## saltires quarters sunstars crescent triangle icon animate text topleft
## 1 0 0 0 0 0 0 0 0 red
## 2 0 0 0 0 0 0 0 0 red
## 3 0 0 0 0 0 1 0 0 black
## 4 0 0 0 0 0 1 0 0 white
## 5 0 0 0 0 0 0 0 0 red
## botright
## 1 red
## 2 blue
## 3 green
## 4 white
## 5 white
flags %>% ddply(., .(religion))
## name landmass zone area population language religion bars stripes
## 1 Luxembourg 3 1 3 0 4 0 0 3
## 2 Peru 2 3 1285 14 2 0 3 0
## 3 Indonesia 6 2 1904 157 10 2 0 2
## 4 Kenya 4 1 583 17 10 5 0 5
## 5 South-Korea 5 1 99 39 10 7 0 0
## colours red green blue gold white black orange mainhue circles crosses
## 1 3 1 0 1 0 1 0 0 red 0 0
## 2 2 1 0 0 0 1 0 0 red 0 0
## 3 2 1 0 0 0 1 0 0 red 0 0
## 4 4 1 1 0 0 1 1 0 red 1 0
## 5 4 1 0 1 0 1 1 0 white 1 0
## saltires quarters sunstars crescent triangle icon animate text topleft
## 1 0 0 0 0 0 0 0 0 red
## 2 0 0 0 0 0 0 0 0 red
## 3 0 0 0 0 0 0 0 0 red
## 4 0 0 0 0 0 1 0 0 black
## 5 0 0 0 0 0 1 0 0 white
## botright
## 1 blue
## 2 red
## 3 white
## 4 green
## 5 white
Vamos supor que tenhamos interesse em saber quais os países que possuem verde+amarelo ou vermelho+branco sua bandeira. Podemos utilizar a função mutate(), que cria uma nova variável de acordo com operações sobre as variáveis existentes.
Criamos a função pairsVec() indicadora da presença de duas cores: a função recebe dois vetores booleanos (1 - TRUE e 0 - FALSE) e retorna outro vetor booleano em que 1 indica (TRUE, TRUE) e 0 indica os outros casos. Ou seja, quando temos as duas cores presentes na bandeira do i-ésimo país, o vetor resultante terá 1 na posição i.
Utilizamos pairsVec() como um dos argumentos da função mutate() para gerar duas novas variáveis: goldGrenn (indica se há amarelo e verde na bandeira) e redWhite (se há vermelho e branco na bandeira). Em seguida, utilizamos a função select() para imprimir apenas os nomes dos países e as variáveis recém criadas.
pairsVec = function(x,y){
res = NULL;
for(i in 1:length(x)){
if(x[i] == 1 && y[i] == 1)
res[i] <- 1
else
res[i] <- 0
}
res
}
mutate(flags, goldGrenn = pairsVec(gold, green), redWhite = pairsVec(red, white)) %>% select(.,name,goldGrenn:redWhite) %>% kable(.,align='l')
| name | goldGrenn | redWhite |
|---|---|---|
| Indonesia | 0 | 1 |
| South-Korea | 0 | 1 |
| Luxembourg | 0 | 1 |
| Peru | 0 | 1 |
| Kenya | 0 | 1 |
Caso haja interesse em aplicar uma função a todas as colunas, utiliza-se a função mutate_each(). No exemplo, aplicamos a média acumulada a cada uma das variáveis.
# aplicar uma função "janela" para cada uma das variáveis (atributos)
mutate_each(flags, funs(cummean))
## # A tibble: 5 × 30
## name landmass zone area population language religion bars
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 83.0000 6.000000 2.000000 1904.0000 157.00000 10.0 2.00 0.00
## 2 119.0000 5.500000 1.500000 1001.5000 98.00000 10.0 4.50 0.00
## 3 113.6667 4.666667 1.333333 668.6667 65.33333 8.0 3.00 0.00
## 4 119.5000 4.000000 1.750000 822.7500 52.50000 6.5 2.25 0.75
## 5 114.4000 4.000000 1.600000 774.8000 45.40000 7.2 2.80 0.60
## # ... with 22 more variables: stripes <dbl>, colours <dbl>, red <dbl>,
## # green <dbl>, blue <dbl>, gold <dbl>, white <dbl>, black <dbl>,
## # orange <dbl>, mainhue <dbl>, circles <dbl>, crosses <dbl>,
## # saltires <dbl>, quarters <dbl>, sunstars <dbl>, crescent <dbl>,
## # triangle <dbl>, icon <dbl>, animate <dbl>, text <dbl>, topleft <dbl>,
## # botright <dbl>
Combinando as funções ddply() e mutate(), podemos criar variáveis por grupos definidos anteriormente. Podemos, por exemplo, saber a porcentagem de países que possuem verde e amarelo na bandeira fazendo a média por grupos de modo bastante sintético. O próximo comando foi feito para todo com conjunto de dados (variável fullData()):
fullData %>% mutate(., goldGrenn = pairsVec(gold, green)) %>% ddply(., ~landmass, summarise, rate = mean(goldGrenn)) %>% kable(.,align='l')
| landmass | rate |
|---|---|
| 1 | 0.3870968 |
| 2 | 0.4117647 |
| 3 | 0.1142857 |
| 4 | 0.4230769 |
| 5 | 0.0769231 |
| 6 | 0.2000000 |
O pacote dplyr também oferece recursos para particionar/agregar dados. Isto é feito por meio das funções select() e slice(). A primeira seleciona variáveis, enquanto a segunda seleciona observações.
Exemplo: selecionar as variáveis entre “name” e “language” (inclusive) e as 3 primeiras observações. Note que a função bind_rows() une as linhas de f1 e f2, formando o mesmo conjunto de dados original, como pode ser confirmado pela função setequal()
# selecionar as variáveis entre "name" e "language" (inclusive) e as 10 primeiras observações
f1 = flags %>% slice(., 1:3)
f2 = flags %>% slice(., 4:5)
Note que a função bind_rows() une as linhas de f1 e f2, formando o mesmo conjunto de dados original, como pode ser confirmado pela função setequal()
# agora vamos juntar os dois:
setequal(bind_rows(f1,f2), flags)
## TRUE
Estes são alguns dos comandos do pacote dplyr. Este tutorial não é exaustivo, apenas pretende motivar o uso do pacote como uma boa solução para manipulação de conjuntos de dados. Há muita documentação na internet sobre o pacote, incluindo resumos rápidos.