Unidade 10 Manipulando Dados

Neste capítulo vou adotar uma abordagem diferente do que fizemos quando introduzimos Gráficos em R. Na apresentação dos gráficos iniciamos pela biblioteca base e depois conhecemos o pacote ggplot2, que considero um muito mais que um aprimoramento para aqueles que desejam gráficos mais refinados, mas sim um pacote com todo um suporte conceitual de como criar visualizações dos seus dados de uma maneira sistemática e mais fácil.

Aqui vou iniciar utilizando pacotes que tornam a manipulação de dados mais fácil de ser compreentendida e implementada, pacotes que fazem parte de um grupo chamado de tidyverse. Tidyverse (Wickham 2017) é uma família de pacotes que compartilham uma mesma filosofia de uso e design para trabalharem juntos de forma harmônica.

David Robinson no seu Blog Variance Explained descreve por que prefere iniciar seus estudantes com tidyverse, o que acaba também corroborando com o fato de que a maioria do material de R disponibilizado na internet já usa essa filosofia.

Many teachers suggest I’m overestimating their students: “No, see, my students are beginners…”. If I push the point, they might insist I’m not understanding just how much of a beginner these students are, and emphasize they’re looking to keep it simple and teach the basics, and that that students can get to the advanced methods later….

My claim is that this is precisely backwards. ggplot2 is easier to teach beginners, not harder, and makes constructing plots simpler, not more complicated.

— David Robinson - Data Scientist.

E ele está citando ggplot2 porque esse pacote também é parte da família tidyverse. Um exemplo de como alguns dos pacotes de tidyverse podem ser combinados no workflow de análise de dados de pode ser visualizado na figura 10.1.

Data Analysis Workflow recriado com alguns pacotes de **tidyverse** (Fonte:[@Gergana2017]).

Figura 10.1: Data Analysis Workflow recriado com alguns pacotes de tidyverse (Fonte:(Gergana et al. 2017)).

Nosso foco neste capítulo será abordar a manipulação de dados usando o pacote Dplyr(Wickham et al. 2017).

10.1 Tibbles

Ao invés de trabalharmos com data frames usaremos aqui uma estrutura chamada tibble. Tibble nada mais é do que um data frames melhorado, mais flexível. Mas melhorado em termos de que? Bom, a definição formal da linguagem R aconteceu já faz um bom tempo, e a concepção de data frame elaborada naquela época, apesar de contínuar a atender os requisitos básicos para manipulação de tabelas, não é totalmente compatível com as inovações descobertas ao longo desses anos. Mudar um tipo básico como data frame poderia causar um caos na compatibilidade com o códigi já existente. Assim, algumas inovações foram inseridas em um tipo especial chamado tiblle, permitindo que os dados possam ser manipulados com mais flexibilidade sem quebrar a compatibilidade com o tipo data frames.

Tibbles são usados principalmente nos pacotes da família tydeverse, mas normalmente citados como se fossem data frames. Se vc deseja conhecer mais um pouco sobre tibbles, consulte livro R for Data Science de Garrett Grolemund e Hadley Wickham (Grolemund and Wickham 2016). Ou acesse vignette(“tibble”) no site CRAN-R

Uma das muitas vantagens de se usar tibbles ao se trabalhar com grandes bancos de dados, é que ao imprimir seus dados, apenas as serão imprssas as 10 primeiras linhas e as colunas que couberem no console.

Normalmente o tipo tibble é carregado junto com os pacotes família tydeverse, por exemplo o Dplyr.

## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

10.1.1 Usando tibbles

Qualquer data frame pode ser convertido em tibble usando a função as_tibble().

as_tibble(iris)
## # A tibble: 150 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
##  1          5.1         3.5          1.4         0.2  setosa
##  2          4.9         3.0          1.4         0.2  setosa
##  3          4.7         3.2          1.3         0.2  setosa
##  4          4.6         3.1          1.5         0.2  setosa
##  5          5.0         3.6          1.4         0.2  setosa
##  6          5.4         3.9          1.7         0.4  setosa
##  7          4.6         3.4          1.4         0.3  setosa
##  8          5.0         3.4          1.5         0.2  setosa
##  9          4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa
## # ... with 140 more rows

10.2 Dados

Os exemplos a seguir foram baseados nas vignettes de dplyr, que utiliza os dados nycflights13::flights que contém todos os vôos que partiram da cidade de Nova York(EUA) em 2013, disponibilizados pelo US Bureau of Transportation Statistics, e armazenados no pacote nycflights13.

Na verdade o pacote nycflights13 contém um extenso banco relacional que também pode ser usados para entender como manipular dados distribuídos em tabelas distintas em um banco relacional. (Consulte “Relacional Data” em Grolemund and Wickham (2016) para maiores informações).

10.2.1 Carregando os dados

Instale o pacote antes de carregar e observe que os dados flights já são armazenados como o tipo tibble e por isso são impressos de forma reduzida.

library(nycflights13)
dim(flights)
## [1] 336776     19
flights
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Para checar a estrutura dos dados use glimpse(), ou help(flights) para uma descrição das variáveis.

glimpse(flights)
## Observations: 336,776
## Variables: 19
## $ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013,...
## $ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,...
## $ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,...
## $ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 55...
## $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 60...
## $ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2...
## $ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 7...
## $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 7...
## $ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -...
## $ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV",...
## $ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79...
## $ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN...
## $ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR"...
## $ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL"...
## $ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138...
## $ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 94...
## $ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5,...
## $ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013...
#help(flights)

Vale frisar que os dados consideram que os três aeroportos de New York (JFK, LGA e EWR) funcionam como um hub, sendo os voos planejados para retornar de onde partiram.

Voos chegam no hub e retornam para o ponto de partida

Figura 10.2: Voos chegam no hub e retornam para o ponto de partida

10.3 Funções básicas

Funcões básicas disponibilizadas pelo pacote Dplyr para manipulação de dados:

  • filter() para selecionar registros específicos .
  • arrange() para ordenar os registros segundo uma regra definida.
  • select() e rename() para selecionar variáveis baseado nos seus nomes.
  • mutate() e transmute() para adicionar novas variáveis em função das já existentes.
  • summarise() para condensar multiplos valores.
  • sample_n() e sample_frac() para a abtenção de amostras aleatórias.

10.4 Filtrando registros (linhas)

O comando filter() permite que registros (linhas da sua tabela) possam ser selecionados de forma mais simples e prática. Como padrão em tidyverse, o argumento define o conjunto de dados.

Exemplos

filter(starwars, species == "Human")
filter(starwars, mass > 1000)

# Múltiplos critérios
filter(starwars, hair_color == "none" & eye_color == "black")
filter(starwars, hair_color == "none" | eye_color == "black")

# Múltiplos argumentos são equivalentes a "and/e"
filter(starwars, hair_color == "none", eye_color == "black")

O filtro é usado de acordo com os operadores booleanos abaixo:

Operadores booleanos usados em `filter()`. **x** representa o círculo esquerdo e **y** o círculo direito, e a área sombreada a selação. (Fonte:[@Grolemund2016]).

Figura 10.3: Operadores booleanos usados em filter(). x representa o círculo esquerdo e y o círculo direito, e a área sombreada a selação. (Fonte:(Grolemund and Wickham 2016)).

Por exemplo, para selecionar todos o voos do dia 01 de Janeiro:

filter(flights, month == 1, day == 1)
## # A tibble: 842 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 832 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Exercícios:

  • Crie uma variável contendo todos os voos que partiram nos meses de novembro e dezembro:


  • Encontre os voos que não atrasaram (na partida ou chegada) por mais de duas horas:


10.4.1 Exercícios

  1. Encontre os voos que:
    1. possuem um atraso de chegada de duas ou mais horas
    2. Voaram para a cidade de Houston (IAH ou HOU)
    3. Foram operados pelas companhias United, American ou Delta Ailines
    4. Partiram no verão dos EUA (julho, agosto e setembro)
    5. Chegaram mais de duas horas atrasados, mas não decolaram atrasados.
    6. Partiram entre meia noite e as 06:00 (inclusive)
  2. Outra opção para usar com o comando de filtro é a função between(). Você poderia usá-lo para simplificar o código necessário para responder às queries anteriores?

  3. Quantos voos possuem valores ausentes em dep_time (NA)? Que outras variáveis estão ausentes? O que podem representar essas linhas com valores ausentes?

10.5 Organizando registros (linhas)

O comando arrange() é usado para organizar as linhas, que funciona de maneira similar à operação filtragem, exceto que aqui será alterada a ordem com que os registros (linhas) serão apresentados.

O default é usar a primeira coluna informada para a ordenação crescente, e as seguintes são usadas como uma chave adicional. .

Exemplo:

arrange(flights, year, month, day)

No caso da ordenação ser decrescente, basta usar a função desc():

arrange(flights, year, month, desc(day))

Vale a pena frisar que os valores ausentes sempre vão aparecer no final:

df <- tibble(x = c(5, 2, NA))
arrange(df, x)
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     2
## 2     5
## 3    NA
arrange(df, desc(x))
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     5
## 2     2
## 3    NA

10.5.1 Exercícios

  1. Como aplicar arrange() de modo que os valores ausentes apareçam no início? (Dica: use is.na()).

  2. Classifique os voos por ordem de atraso. E dentre esses os que partem mais cedo.

10.6 Selecionando variáveis (colunas)

O comando select() pode ser usado de diversas formas para selecionar apenas aquelas variáveis (colunas) interessantes para uma dada análise. Nesse caso, o novo conjunto de dados conterá as colunas selecionadas.

Exemplos:

select(flights, year, month, day)

select(flights, year:day)

select(flights, -(year:day))

10.6.1 Funções de apoio em select()

- “-” exclua da selação
- “:” Selecione a faixa (range)
- `contains()`    Seleciona colunas que contenham no nome os caracteres(strings) definidos
- `starts_with()` Seleciona colunas que cujo nome iniciem com os caracteres(strings) definidos
- `ends_with()`   Seleciona colunas que cujo nome sejam finalizados com os caracteres(strings) definidos
- `everything()`  Seleciona todas as colunas 
- `matches()`     Seleciona colunas nas quais o nome atende a uma expressão
- `num_range()`   Seleciona colunas nomeadas em uma sequência. [e.g. num_range("x", 1:5) para x1, x2, x3, x4, x5]
- `one_of()`      Seleciona colunas cujo nome pertença a um grupo

Use o help() para explorar essas funções!

Exemplos:

Seleciona todas as colunas exceto tailnum:

select(flights,-tailnum)

Seleciona todas as colunas cujo nome contenha “time”:

select(flights,contains("time"))
## # A tibble: 336,776 x 6
##    dep_time sched_dep_time arr_time sched_arr_time air_time
##       <int>          <int>    <int>          <int>    <dbl>
##  1      517            515      830            819      227
##  2      533            529      850            830      227
##  3      542            540      923            850      160
##  4      544            545     1004           1022      183
##  5      554            600      812            837      116
##  6      554            558      740            728      150
##  7      555            600      913            854      158
##  8      557            600      709            723       53
##  9      557            600      838            846      140
## 10      558            600      753            745      138
## # ... with 336,766 more rows, and 1 more variables: time_hour <dttm>

Comunas também podem ser renomeadas com o select()

select(flights,year,carrier,destination=dest)
## # A tibble: 336,776 x 3
##     year carrier destination
##    <int>   <chr>       <chr>
##  1  2013      UA         IAH
##  2  2013      UA         IAH
##  3  2013      AA         MIA
##  4  2013      B6         BQN
##  5  2013      DL         ATL
##  6  2013      UA         ORD
##  7  2013      B6         FLL
##  8  2013      EV         IAD
##  9  2013      B6         MCO
## 10  2013      AA         ORD
## # ... with 336,766 more rows

A função everything() pode ser usada em conjunção com select() para reodenar as colunas.

select(flights, time_hour, air_time, everything())
## # A tibble: 336,776 x 19
##              time_hour air_time  year month   day dep_time sched_dep_time
##                 <dttm>    <dbl> <int> <int> <int>    <int>          <int>
##  1 2013-01-01 05:00:00      227  2013     1     1      517            515
##  2 2013-01-01 05:00:00      227  2013     1     1      533            529
##  3 2013-01-01 05:00:00      160  2013     1     1      542            540
##  4 2013-01-01 05:00:00      183  2013     1     1      544            545
##  5 2013-01-01 06:00:00      116  2013     1     1      554            600
##  6 2013-01-01 05:00:00      150  2013     1     1      554            558
##  7 2013-01-01 06:00:00      158  2013     1     1      555            600
##  8 2013-01-01 06:00:00       53  2013     1     1      557            600
##  9 2013-01-01 06:00:00      140  2013     1     1      557            600
## 10 2013-01-01 06:00:00      138  2013     1     1      558            600
## # ... with 336,766 more rows, and 12 more variables: dep_delay <dbl>,
## #   arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, distance <dbl>,
## #   hour <dbl>, minute <dbl>

10.6.2 Exercício

  1. Como usar a função one_of(). Teste com:
vars <- c("year", "month", "day", "dep_delay", "arr_delay")

10.7 Criando novas variáveis

O comando mutate() é um dos comandos mais utéis quando é necessário criar novas variáveis em função das já existentes. As novas variáveis(colunas) serão adicionadas no final.

Exemplo:

flights_sml <- select(flights, 
                              year:day, 
                              ends_with("delay"), 
                              distance, 
                              air_time
)
mutate(flights_sml,
                    gain = arr_delay - dep_delay,
                    speed = distance / air_time * 60
)
## # A tibble: 336,776 x 9
##     year month   day dep_delay arr_delay distance air_time  gain    speed
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl> <dbl>    <dbl>
##  1  2013     1     1         2        11     1400      227     9 370.0441
##  2  2013     1     1         4        20     1416      227    16 374.2731
##  3  2013     1     1         2        33     1089      160    31 408.3750
##  4  2013     1     1        -1       -18     1576      183   -17 516.7213
##  5  2013     1     1        -6       -25      762      116   -19 394.1379
##  6  2013     1     1        -4        12      719      150    16 287.6000
##  7  2013     1     1        -5        19     1065      158    24 404.4304
##  8  2013     1     1        -3       -14      229       53   -11 259.2453
##  9  2013     1     1        -3        -8      944      140    -5 404.5714
## 10  2013     1     1        -2         8      733      138    10 318.6957
## # ... with 336,766 more rows

E as colunas recem criadas podem já ser usadas em novas expressões:

mutate(flights,
  gain = arr_delay - dep_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 22
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 15 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>, gain <dbl>, hours <dbl>,
## #   gain_per_hour <dbl>

E se o interesse é manter apenas as novas colunas, use transmute():

transmute(flights,
  gain = arr_delay - dep_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 3
##     gain     hours gain_per_hour
##    <dbl>     <dbl>         <dbl>
##  1     9 3.7833333      2.378855
##  2    16 3.7833333      4.229075
##  3    31 2.6666667     11.625000
##  4   -17 3.0500000     -5.573770
##  5   -19 1.9333333     -9.827586
##  6    16 2.5000000      6.400000
##  7    24 2.6333333      9.113924
##  8   -11 0.8833333    -12.452830
##  9    -5 2.3333333     -2.142857
## 10    10 2.3000000      4.347826
## # ... with 336,766 more rows

A função mutate() aceita uma serie de operações para a criação de novas variáveis, o que é extremamente útil na contrução de modelos estísticos de previsão, ou mais específivamente no contexto chamado de feature engineering.

Além dos operadores aritméticos " +, - , *, /, ^ " (e.g. air_time/60), expressões com operadores lógicos e modulares %/% (divisão inteira) e %% (resto) podem ser utilizados (e.g. x == y * (x %/% y) + (x %% y) ). Funções agregadoras, entre outras, também podem ser empregadas (e.g. x/sum(x), y - mean(y) ).

10.7.1 Exercícios:

  1. Compare air_time com (arr_time - dep_time). O resultado indica algum padrão? O que aconteceu com o resultado? Como corrigir?

  2. Compare dep_time, sched_dep_time, and dep_delay. Qual é a relação esperada entre essas variáveis?

10.8 Sumarizando dados grupados

Trabalhar sobre dados grupados é uma das grande facilidades proprorcionadas pelo pacote Dplyr. Essa funcionalidade é disponibilizada pela função sumarise(), usada em conjunto com a função group_by().

Por exemplo, para calcular o atraso médio por dia:

by_day <- group_by(flights, year, month, day)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE)) #removendo os dados ausentes
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day     delay
##    <int> <int> <int>     <dbl>
##  1  2013     1     1 11.548926
##  2  2013     1     2 13.858824
##  3  2013     1     3 10.987832
##  4  2013     1     4  8.951595
##  5  2013     1     5  5.732218
##  6  2013     1     6  7.148014
##  7  2013     1     7  5.417204
##  8  2013     1     8  2.553073
##  9  2013     1     9  2.276477
## 10  2013     1    10  2.844995
## # ... with 355 more rows

Antes de continuar, torna-se interessante introduzir um novo conceito, pipes. O uso de pipes, como veremos a seguir, pode tornar códigos complexos mais fácil de serem constrídos e lidos.

10.9 Pipes

Imagine que precisamos codificar o seguinte texto:

Batatinha quando nasce, esparrama pelo chão.

usando as seguintes funções:

  - nasce(objeto) #"para criação do objeto"
  
  - esparrama( objeto , lugar)

Ficaríamos com algo do tipo:

batatinha <- nasce("batata")
esparrama(batatinha, "chão")

Se quisermos montar uma cadeia de comandos poderíamos fazer:

esparrama(
          nasce("batata"), "chão"
          )

Note que os comandos foram aninhados de de dentro para fora, de forma que a função mais interna é executada e seu resultado torna-se argumento para a função externa. Apesar deste processo não ser muito intuitivo, essa é a forma comumente usado em liguagens de programação.

Não seria melhor se pudéssemos escrever o código na mesma sequência da frase. É exatamente isso que o uso de pipes (%>%) proporciona:

nasce("batata") %>% esparrama("chão")

Ao ler o código, Grolemund and Wickham (2016) sugere que %>% deve ser pronunciado como “então”. Eu particularmente prefiro “pipe” mesmo :)

Internamente, x %>% f(y) se transforma em f(x, y), e x %>% f(y) %>% g(z) gera g(f(x, y), z). Essa simples operação melhora sobremaneira a redigibilidade.

Usando os dados flights (exemplo em Grolemund and Wickham (2016)), poderíamos usar a função de summarise() para explorar a relação entre distância e atraso médio em função do destino, com dplyr, da seguinte forma:

by_dest <- group_by(flights, dest)
delay <- summarise(by_dest,
  count = n(),
  dist = mean(distance, na.rm = TRUE),
  delay = mean(arr_delay, na.rm = TRUE)
)
delay <- filter(delay, count > 20, dest != "HNL")

Usando um gráfico de pontos para investigar a relação, temos:

ggplot(data = delay, mapping = aes(x = dist, y = delay)) +
  geom_point(aes(size = count), alpha = 1/3) +
  geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'loess'

Podemos observar que os atrasos aumentam até a distância de ~750 milhas e depois decrescem. Provavelmente, voos mais longos permitem que os atrasos possam ser compensados no ar!

Os passos usados para preparar os dados foram:

1. `group_by()` para grupar os dados por destino.
2. `summarise()` para computar a distância, delay médio, e número de voos.
3. `filter()` para remover pontos expúrios(ruídos) e o aeroporto de Honolulu, cuja distância é quase o dobro da segunda maior.

Ao usarmos pipes o código passa a ser escrito como abaixo e não há necessidade de um repositório intermediário de dados:

delays <- flights %>% 
  group_by(dest) %>% 
  summarise(
    count = n(),
    dist = mean(distance, na.rm = TRUE),
    delay = mean(arr_delay, na.rm = TRUE)
  ) %>% 
  filter(count > 20, dest != "HNL")

Ou mesmo, linkando o código com ggplot2:

flights %>% 
  group_by(dest) %>% 
  summarise(
    count = n(),
    dist = mean(distance, na.rm = TRUE),
    delay = mean(arr_delay, na.rm = TRUE)
  ) %>% 
  filter(count > 20, dest != "HNL") %>%
  
  ggplot(mapping = aes(x = dist, y = delay)) +
  geom_point(aes(size = count), alpha = 1/3) +
  geom_smooth(se = FALSE)

10.10 Dados ausentes

O que aconteceria acima se os dados ausente não fossem removidos nas funções de agrupamento (na.rm=TRUE)?

Teste o comando:

flights %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay))


Podemos também usar os dados ausentes para identificar os voos cancelados. Assim, esses voos seriam excluídos da nossa análise. Considere então que os voos não cancelados são aqueles que possuem de atraso (delay) na chegada e na saída. E para os voos cancelados um dos dados está ausente.

not_cancelled <- flights %>% 
  filter(!is.na(dep_delay), !is.na(arr_delay))

10.10.1 Contando os NA

Uma boa prática é sempre investigar os número de observações(n()) ou o número de dados ausentes (sum(!is.na(x)))quando estamos usando funções agregadoras, pois as conclusões podem ser influenciadas por uma parcela pequena dos dados.

Considere novamente os dados referentes ao atraso dos voos não cancelados. Vamos considerar o atraso médio por aeronaves (identificadas por tailnum).

A função geom_freqpoly() plota a distribuição dos atrasos médios (delays$delay) usando uma linha.

delays <- not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay) 
  )

ggplot(data = delays, mapping = aes(x = delay)) + 
  geom_freqpoly(binwidth = 10)

(Teste geom_histogram() e veja a diferença)


Podemos notar que existem atrasos médios de até 5 horas em alguns voos! Mas investigando melhor podemos ver como é a relação entre os atrasos médios (delay) e o número de voos.

delays <- not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay),
    n = n()
  )

ggplot(data = delays, mapping = aes(x = n, y = delay)) + 
  geom_point(alpha = 1/10)

Note que há grandes variações quando o número de voos é menor. O que pode ser explicado pelo fato de que nesses casos a média pode estar sendo altamente influenciada por outliers, e a amostra ainda não possui tamanho suficiente para estabilizar essa variação. (A aplicaçào de estatística bayesiana pode amenizar esse efeito, mas é assunto para outro tópico).

Vamos repetir o gráfico acima considerando somente os atrasos onde a média considerou mais de 25 voos.


10.11 Outros exemplos de funções agregadoras

  • A média é uma medida (mean()) bastante usada, mas medidas como a mediada (median(x)) pode dimunuir os efeitos dos outliers.
  • Subsetting significa usar um filtro nos valores das colunas e pode ser aplicado com summarise().
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(
    avg_delay1 = mean(arr_delay),
    
    # Considere somente os atrasos (positivos)
    avg_delay2 = mean(arr_delay[arr_delay > 0]) 
  )
  • Medidas de dispersão como sd(), IRQ() e mad() (desvio absoluto em relação a média), sendo os dois últimos mais robustos em relação aos outliers.
# Algumas distância voadas variam mais dependendo do destino?
not_cancelled %>% 
  group_by(dest) %>% 
  summarise(distance_sd = sd(distance)) %>% 
  arrange(desc(distance_sd))
  • Medidas de posição: first(x), nth(x, 2), last(x). Indicadas para retornor um dado elemento de um grupamento. Se a posição indicada não existir devolve um valor default (i.e. Ao tentar recuperar o terceiro elemento de uma lista de dois elementos).
not_cancelled %>% 
  group_by(year, month, day) %>% 
  summarise(
    first_dep = first(dep_time), 
    last_dep = last(dep_time)
  )
  • Ou até mesmo combinar summarize() com filter()
popular_dests <- flights %>% 
  group_by(dest) %>% 
  filter(n() > 365) # mais de um voo por dia
popular_dests

O RStudio disponibiliza uma folha de resumo para pacotes Dplyr e Tidyr (Data Wrangling Cheatsheet - RStudio).

Para conhecer mais consulte o livro R for Data Science de Garrett Grolemund e Hadley Wickham (Grolemund and Wickham 2016).

References

Wickham, Hadley. 2017. Tidyverse: Easily Install and Load ’Tidyverse’ Packages. https://CRAN.R-project.org/package=tidyverse.

Gergana, John, Francesca, Sandra, and Isla. 2017. “Working efficiently with large datasets.” https://ourcodingclub.github.io/2017/03/20/seecc.html.

Wickham, Hadley, Romain Francois, Lionel Henry, and Kirill Müller. 2017. Dplyr: A Grammar of Data Manipulation. https://CRAN.R-project.org/package=dplyr.

Grolemund, Garrett, and Hadley Wickham. 2016. “R for Data Science.” http://r4ds.had.co.nz/.