Join com dplyr

Fazer um join ou um merge nada mais é do que juntar dois conjuntos de dados por meio de um ou mais campos em comum (chaves/key). Existem algumas vantagens de se utilizar o join com dplyr em relação ao merge do base R e ao SQL:

● Sempre preserva a ordem das linhas originais

● Sintaxe mais intuitiva

● Pode ser aplicado em diferentes tipos de dados, inclusive com spark.

instalação e inicialização do pacote:

library(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

Tipos de Join

Podemos dividir as funções do tipo join em 2 tipos:

●Mutating joins:

left_join(), right_join(),inner_join(), full_join()

●Filtering joins:

semi_join(), anti_join()

Temos ainda, no pacote dplyr, as funções union(), intersect(), setdiff(), setequal() que também serão úteis para unir e comparar conjuntos de dados.

Conjunto de dados exemplo

O conjunto de dados para análise será nycflights13. Ele é adequado para o estudo que iremos realizar pois se trata de um banco de dados relacional.

inicializar o pacote:

library(nycflights13)

analizar:

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>

Note que por se tratar de uma tibble, o dataframe é impresso de uma forma um pouco diferente da usual. Temos aqui os dados de 336,776 vôos que partiram de Nova Iorque em 2013. O pacote inclui, além da tabela flights, outras 4 tibbles (airlines, airports, planes, weather) relacionadas à esta:

●airlines: permite que você procure o nome completo do operador através do código abreviado

●airports: fornece informações sobre cada aeroporto, identificadas pelo código do aeroporto faa

●planes: fornece informações sobre cada avião, identificados pelo seu tailnum

●weather: dados climáticos para cada aeroporto de Nova York por hora

O diagrama acima mostra como a tabela flights se relaciona com as outras:

●flights se conecta à planes através de uma variável única, tailnum.

●flights se conecta à airlines através de uma variável única, carrier.

●flights se conecta à airports de duas formas: através das variáveis origin e dest.

●flights se conecta à weather através de origin (location), year, month, day e hour (time).

Inner Join

inner_join(x, y): Retorna todas as linhas de x onde existem valores correspondentes em y e todas as colunas de x e y. Se houver múltiplas combinações entre x e y, todas as combinações das correspondências são retornadas.

É o tipo mais simples de join, que combina pares de observações sempre que suas chaves são iguais.

O resultado do inner join é um novo data frame que contém a chave, os valores de “x” e os valores de “y”. Usamos “by” para informar ao dplyr qual variável é a chave.

Tanto o merge() do R base quanto o inner_join() do dplyr tentam descobrir as chaves em comum buscando campos com o mesmo nome. Mas vamos supor que os campos chave tivessem nomes diferentes (o que é bem comum). No R base usamos by.x e by.y para especificar o nome do campo chave do primeiro e segundo data.frame. No inner_join() do dplyr usamos um vetor no formato c(“chave.x” = “chave.y”).

Veremos como isso se aplica à nossa base de dados: flights e planes estão conectadas através da chave “tailnum”.

inner_join(x, y, by = NULL, copy = FALSE, suffix = c(“.x”, “.y”), …)

inner_join<-inner_join(flights,planes,by="tailnum")
dim(flights) 
## [1] 336776     19
dim(planes)
## [1] 3322    9
dim(inner_join) 
## [1] 284170     27

Resumindo, o inner join retorna todos os campos de ambos os data.frames, mas somente as linhas em que as chaves são iguais.

Podemos estranhar o fato da tabela “inner_join” conter um menor número de linhas do que a tabela “flights”. Isso significa que existem aviões que não estão identificados na tabela “plane”. Uma boa forma de checar isso é usando o anti_join:

Anti Join

anti_join(x, y): Returna todas as linhas de x para as quais não existem valores correspondente em y, mantendo apenas as colunas de x. É considerado um “filtering join”.

anti_join<-anti_join(flights,planes,by="tailnum")
dim(anti_join)[1]+dim(inner_join)[1]
## [1] 336776
dim(flights)[1]
## [1] 336776

Vemos que a quantidade de linhas cujas chaves estão em flights mas não estão em planes (representadas no anti_join) somadas às linhas que estão em ambas (representadas em inner_join) é exatamente a mesma que na base original flights, conforme esperado.

Semi Join

semi_join(x, y): Returna todas as linhas de x para as quais existem valores correspondente em y, mantendo apenas as x. O semi join difere do inner join porque o inner join irá retornar uma linha de x para cada valor correspondente de y, enquanto um semi join nunca irá duplicar valores x.

Pode-se dizer que é um “filtering join” complementar ao anti join. Na prática, ele vai retornar as mesmas linhas que o inner_join equivalente (sem duplicar), e apenas vai exercer a função filtrar a tabela x. Isto é, diferentemente do inner join, ele não retorna as colunas de y, apenas identifica se existe correpondência.

semi_join<-semi_join(flights,planes,by="tailnum")
dim(semi_join)
## [1] 284170     19
dim(inner_join)
## [1] 284170     27

Neste exemplo, o número de linhas resultante do semi join e do inner join foi o mesmo pois não havia nenhum valor duplicado da variável chave tailnum na tabela planes.

Full Join

full_join(x, y): Returna todas as linhas e todas as colunas tanto de x quanto de y. Returna NA no caso de não haver valor correspondente.

full_join<-full_join(flights,planes,by="tailnum")
dim(full_join)
## [1] 336776     27
tail(full_join)
## # A tibble: 6 x 27
##   year.x month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1   2013     9    30       NA           1842        NA       NA
## 2   2013     9    30       NA           1455        NA       NA
## 3   2013     9    30       NA           2200        NA       NA
## 4   2013     9    30       NA           1210        NA       NA
## 5   2013     9    30       NA           1159        NA       NA
## 6   2013     9    30       NA            840        NA       NA
## # ... with 20 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>, year.y <int>, type <chr>, manufacturer <chr>,
## #   model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>

É interessante notar que como as bases flights e planes possuiam ambas, originalmente, uma coluna “year”, sendo que ela não foi usada como chave para o join (consideramos apenas o “tailnum”), o dplyr renomeou estas colunas como “year.x” e “year.y” para poder distinguir entre aquela que veio da base flights (base x) e aquela que veio da base planes(base y).

colnames(full_join)
##  [1] "year.x"         "month"          "day"            "dep_time"      
##  [5] "sched_dep_time" "dep_delay"      "arr_time"       "sched_arr_time"
##  [9] "arr_delay"      "carrier"        "flight"         "tailnum"       
## [13] "origin"         "dest"           "air_time"       "distance"      
## [17] "hour"           "minute"         "time_hour"      "year.y"        
## [21] "type"           "manufacturer"   "model"          "engines"       
## [25] "seats"          "speed"          "engine"

Note ainda que, caso não especifique a chave, o R automaticamente considera todas as colunas com nome igual como keys e isso não necessariamente produz o resultado desejado:

full_join2<-full_join(flights,planes)
## Joining, by = c("year", "tailnum")
dim(full_join2)
## [1] 340006     26
tail(full_join2)
## # A tibble: 6 x 26
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  1991    NA    NA       NA             NA        NA       NA
## 2  2002    NA    NA       NA             NA        NA       NA
## 3  1992    NA    NA       NA             NA        NA       NA
## 4  2002    NA    NA       NA             NA        NA       NA
## 5  1992    NA    NA       NA             NA        NA       NA
## 6  1992    NA    NA       NA             NA        NA       NA
## # ... with 19 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>, type <chr>, manufacturer <chr>, model <chr>,
## #   engines <int>, seats <int>, speed <int>, engine <chr>

No nosso exemplo, year da base flights é referente à data de partida do avião, enquanto year da base planes é referente à data de fabricação do avião. Dessa forma, se não especificarmos a chave corretamente através do argumento by, teremos um join sem sentido.

Left Join

left_join(x, y): Returna todas as linhas de x, e todas as colunas tanto de x quanto de y. Se houver múltiplas correspondências entre x e y, todas as combinações de correspondências serão retornadas.

left_join<-left_join(flights,planes,by="tailnum")
dim(left_join)
## [1] 336776     27

Right Join

right_join(x, y): Returna todas as linhas de y, e todas as colunas tanto de x quanto de y. Se houver múltiplas correspondências entre x e y, todas as combinações de correspondências serão retornadas.

O princípio é EXATAMENTE o mesmo do left join. A única diferença é a permanência dos registros do conjunto da direita em vez dos da esquerda, como podemos ver na figura acima.

right_join<-right_join(planes,flights,by="tailnum")
dim(left_join)
## [1] 336776     27

Veremos que o conjunto gerado em right_join tem exatamente as mesmas dimensões que o de left_join, pois usamos as mesmas bases de dados, em ordem trocada, para fazer as junções.