4.6 Join

No início deste capítulo, mostramos várias tabelas e levantamos questões também relacionadas às várias tabelas. No entanto, não falamos sobre como combinar de tabelas ainda, o que faremos nesta seção. Em DataFrames.jl, a combinação de várias tabelas é feita via joins. Os joins são extremamente poderosos, mas pode demorar um pouco para você entendê-los. Não é necessário saber os joins abaixo de cor, porque a documentação DataFrames.jl, juntamente com este livro, listará-los para você. Mas, é essencial saber que os joins existem. Se você alguma vez se pegar dando voltas sobre as linhas de um DataFrame e comparando-o com outros dados, então você provavelmente precisará de um dos joins abaixo.

No Section 4, introduzimos as notas escolares para o ano de 2020 com grades_2020:

grades_2020()
name grade_2020
Sally 1.0
Bob 5.0
Alice 8.5
Hank 4.0

Agora, vamos combinar grades_2020 com as notas de 2021:

grades_2021()
name grade_2021
Bob 2 9.5
Sally 9.5
Hank 6.0

Para fazer isso, vamos utilizar os joins. DataFrames.jl lista não menos que sete tipos de join. Isso pode parecer assustador no início, mas espere porque todos eles são úteis e vamos mostrá-los.

4.6.1 innerjoin

O primeiro é innerjoin. Suponha que temos dois datasets A e B com as respectivas colunas A_1, A_2, ..., A_n e B_1, B_2, ..., B_m e uma das colunas tem o mesmo nome, digamos A_1 e B_1 são ambas chamadas :id. Então, o inner join em :id irá percorrer todos os elementos em A_1 e compará-lo aos elementos em B_1. Se os elementos são os mesmos, então ele irá adicionar todas as informações de A_2, ..., A_n e B_2, ..., B_m depois da coluna :id.

Ok, não se preocupe se você ainda não compreendeu esta descrição. O resultado do join nos datasets de notas será assim:

innerjoin(grades_2020(), grades_2021(); on=:name)
name grade_2020 grade_2021
Sally 1.0 9.5
Hank 4.0 6.0

Observe que apenas “Sally” e “Hank” estão em ambos datasets. O nome inner join faz sentido, uma vez que, em matemática, o set intersection (ou a intersecção de conjuntos) é definido por “todos os elementos em \(A\), que também estão em \(B\), ou todos os elementos em \(B\) que também estão em \(A\).”

4.6.2 outerjoin

Talvez você esteja pensando agora “se temos um inner (interno), provavelmente também temos um outer (externo).” Sim, você adivinhou certo!

O outerjoin é muito menos rigoroso do que o innerjoin e pega qualquer linha que encontrar que contenha um nome em pelo menos um dos datasets:

outerjoin(grades_2020(), grades_2021(); on=:name)
name grade_2020 grade_2021
Sally 1.0 9.5
Hank 4.0 6.0
Bob 5.0 missing
Alice 8.5 missing
Bob 2 missing 9.5

Portanto, este método pode criar dados faltantes mesmo que nenhum dos datasets originais tivesse valores ausentes.

4.6.3 crossjoin

Podemos obter ainda mais dados faltantes se usarmos o crossjoin. Esse tipo de join retorna o produto cartesiano das linhas, que é basicamente a multiplicação das linhas, ou seja, para cada linha crie uma combinação com qualquer outra linha:

crossjoin(grades_2020(), grades_2021(); on=:id)
MethodError: no method matching crossjoin(::DataFrame, ::DataFrame; on=:id)
Closest candidates are:
  crossjoin(::DataFrames.AbstractDataFrame, ::DataFrames.AbstractDataFrame; makeunique) at ~/.julia/packages/DataFrames/MA4YO/src/join/composer.jl:1319 got unsupported keyword argument "on"
  crossjoin(::DataFrames.AbstractDataFrame, ::DataFrames.AbstractDataFrame, !Matched::DataFrames.AbstractDataFrame...; makeunique) at ~/.julia/packages/DataFrames/MA4YO/src/join/composer.jl:1330 got unsupported keyword argument "on"
  ...

Ops! Já que crossjoin não leva os elementos na linha em consideração, não precisamos especificar o argumento on para o que queremos juntar:

crossjoin(grades_2020(), grades_2021())
ArgumentError: Duplicate variable names: :name. Pass makeunique=true to make them unique using a suffix automatically.
Stacktrace:
  [1] add_names(ind::DataFrames.Index, add_ind::DataFrames.Index; makeunique::Bool)
    @ DataFrames ~/.julia/packages/DataFrames/MA4YO/src/other/index.jl:427
  [2] merge!(x::DataFrames.Index, y::DataFrames.Index; makeunique::Bool)
  ...

Ops de novo! Esse é um erro bastante comum com DataFrames e joins. As tabelas para as notas de 2020 e 2021 têm um nome de coluna duplicado, a saber :name. Como antes, o erro do output de DataFrames.jl mostra uma sugestão simples que pode corrigir o problema. Podemos apenas passar makeunique=true para resolver isso:

crossjoin(grades_2020(), grades_2021(); makeunique=true)
name grade_2020 name_1 grade_2021
Sally 1.0 Bob 2 9.5
Sally 1.0 Sally 9.5
Sally 1.0 Hank 6.0
Bob 5.0 Bob 2 9.5
Bob 5.0 Sally 9.5
Bob 5.0 Hank 6.0
Alice 8.5 Bob 2 9.5
Alice 8.5 Sally 9.5
Alice 8.5 Hank 6.0
Hank 4.0 Bob 2 9.5
Hank 4.0 Sally 9.5
Hank 4.0 Hank 6.0

Então, agora, temos uma linha para cada nota de todos nos datasets de notas de 2020 e 2021. Para consultas diretas, como “quem tem a nota mais alta?” o produto cartesiano geralmente não é tão útil, mas para consultas “estatísticas,” pode ser.

4.6.4 leftjoin e rightjoin

Mais úteis para projetos de dados científicos são os leftjoin e rightjoin. O left join (ou join à esquerda) fornece todos os elementos do DataFrame à esquerda:

leftjoin(grades_2020(), grades_2021(); on=:name)
name grade_2020 grade_2021
Sally 1.0 9.5
Hank 4.0 6.0
Bob 5.0 missing
Alice 8.5 missing

Aqui, as notas para “Bob” e “Alice” estavam faltando na tabela de notas de 2021, então é por isso que também existem elementos faltantes. A join à direita faz quase que o oposto:

rightjoin(grades_2020(), grades_2021(); on=:name)
name grade_2020 grade_2021
Sally 1.0 9.5
Hank 4.0 6.0
Bob 2 missing 9.5

Agora, as notas de 2020 estão faltando.

Perceba que leftjoin(A, B) != rightjoin(B, A), porque a ordem das colunas será diferente. Por exemplo, compare o output abaixo com o output anterior:

leftjoin(grades_2021(), grades_2020(); on=:name)
name grade_2021 grade_2020
Sally 9.5 1.0
Hank 6.0 4.0
Bob 2 9.5 missing

4.6.5 semijoin e antijoin

Por último, temos semijoin e antijoin.

O semi join é ainda mais restritivo que o inner join. Retorna apenas elementos do DataFrame da esquerda que estão em ambos DataFrames. Ele é como se fosse uma combinação do join da esquerda com o inner join.

semijoin(grades_2020(), grades_2021(); on=:name)
name grade_2020
Sally 1.0
Hank 4.0

O oposto do semi join é o anti join. Ele retorna apenas os elementos do DataFrame da esquerda que não estão no DataFrame da direita:

antijoin(grades_2020(), grades_2021(); on=:name)
name grade_2020
Bob 5.0
Alice 8.5


CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso