#6 - Combinando DataFrames: merge, join e concat

Como combinar DataFrames diferentes no Pandas usando merge() para joins ao estilo SQL, join() por índice e concat() para empilhar tabelas.

#6 - Combinando DataFrames: merge, join e concat

Dificilmente todos os dados de uma análise real vêm de uma única tabela. É bem mais comum ter um DataFrame de clientes, outro de pedidos, outro de produtos — cada um numa fonte diferente — e precisar juntá-los antes de conseguir responder qualquer pergunta que cruze essas informações. O Pandas tem três ferramentas principais pra isso: merge(), join() e concat().

merge(): joins ao estilo SQL

pd.merge() (ou o método equivalente df.merge()) combina dois DataFrames com base nos valores de uma ou mais colunas em comum — o mesmo conceito de JOIN do SQL, pra quem já usou banco de dados relacional.

Vamos usar dois DataFrames de exemplo: um de clientes e outro de pedidos.

import pandas as pd

clientes = pd.DataFrame({
    "id_cliente": [1, 2, 3, 4],
    "nome": ["Ana", "Bruno", "Carla", "Diego"]
})

pedidos = pd.DataFrame({
    "id_pedido": [101, 102, 103, 104],
    "id_cliente": [1, 2, 2, 5],
    "valor": [250.0, 89.9, 430.0, 120.0]
})

Repare que o cliente 4 (Diego) não tem nenhum pedido, e o pedido 104 pertence ao cliente 5, que não existe na tabela de clientes — isso vai ser importante pra entender a diferença entre os tipos de join.

Exemplo 1 - inner join (o padrão)

resultado = pd.merge(clientes, pedidos, on="id_cliente")
print(resultado)

A saída é:

   id_cliente   nome  id_pedido  valor
0           1    Ana        101  250.0
1           2  Bruno        102   89.9
2           2  Bruno        103  430.0

Por padrão (how="inner"), só sobrevivem as linhas cujo id_cliente existe nas duas tabelas. Carla e Diego somem (não tinham pedido), e o pedido 104 também some (não tinha cliente correspondente).

Exemplo 2 - left join

Pra manter todas as linhas da tabela da esquerda mesmo sem correspondência, use how="left":

resultado = pd.merge(clientes, pedidos, on="id_cliente", how="left")
print(resultado)
   id_cliente   nome  id_pedido  valor
0           1    Ana      101.0  250.0
1           2  Bruno      102.0   89.9
2           2  Bruno      103.0  430.0
3           3  Carla        NaN    NaN
4           4  Diego        NaN    NaN

Agora Carla e Diego aparecem, com NaN nas colunas que vieram da tabela de pedidos — já que nenhum deles tinha um pedido correspondente.

Exemplo 3 - outer join

how="outer" mantém tudo dos dois lados, preenchendo com NaN onde não há correspondência de nenhum dos lados:

resultado = pd.merge(clientes, pedidos, on="id_cliente", how="outer")
print(resultado)
   id_cliente   nome  id_pedido  valor
0           1    Ana      101.0  250.0
1           2  Bruno      102.0   89.9
2           2  Bruno      103.0  430.0
3           3  Carla        NaN    NaN
4           4  Diego        NaN    NaN
5           5    NaN      104.0  120.0

Agora o pedido 104 também aparece, mesmo sem um cliente correspondente — a coluna nome fica NaN nessa linha.

Nota: existe ainda how="right", que faz o espelho do left (mantém tudo da tabela da direita), e how="cross", que combina cada linha de uma tabela com cada linha da outra — útil pra gerar todas as combinações possíveis entre dois conjuntos.

Exemplo 4 - juntando por colunas com nomes diferentes

Se a coluna-chave tiver nomes diferentes em cada tabela, use left_on e right_on no lugar de on:

pedidos2 = pedidos.rename(columns={"id_cliente": "cliente_id"})

resultado = pd.merge(clientes, pedidos2, left_on="id_cliente", right_on="cliente_id")
print(resultado)

O resultado é o mesmo do inner join do Exemplo 1, só que agora com as duas colunas-chave (id_cliente e cliente_id) lado a lado no resultado, em vez de uma só.

join(): combinando pelo índice

Enquanto merge() combina por colunas, .join() combina pelo índice de cada DataFrame. É útil quando as tabelas já compartilham o mesmo rótulo de índice, sem precisar de uma coluna-chave explícita.

Exemplo 5 - join básico

esquerda = pd.DataFrame(
    {"nome": ["Ana", "Bruno", "Carla"]},
    index=["c1", "c2", "c3"]
)

direita = pd.DataFrame(
    {"cidade": ["Recife", "Curitiba"]},
    index=["c1", "c2"]
)

print(esquerda.join(direita))
   nome    cidade
c1  Ana    Recife
c2 Bruno  Curitiba
c3 Carla       NaN

Por padrão, .join() se comporta como um left join (how="left") — todas as linhas do esquerda são mantidas, com NaN onde não há correspondência no índice de direita. O parâmetro how aceita os mesmos valores de merge() ("inner", "outer", "right").

concat(): empilhando DataFrames

Diferente de merge() e join(), que combinam colunas com base numa chave, pd.concat() serve pra empilhar DataFrames que já têm a mesma estrutura — por exemplo, vendas de janeiro e vendas de fevereiro, cada mês num arquivo separado.

Exemplo 6 - empilhando linhas

vendas_jan = pd.DataFrame({"produto": ["Mouse", "Teclado"], "unidades": [30, 15]})
vendas_fev = pd.DataFrame({"produto": ["Mouse", "Monitor"], "unidades": [22, 8]})

resultado = pd.concat([vendas_jan, vendas_fev])
print(resultado)
   produto  unidades
0    Mouse        30
1  Teclado        15
0    Mouse        22
1  Monitor         8

Repare que o índice se repete (0, 1, 0, 1) — cada DataFrame manteve o índice original. Pra gerar um índice novo e sequencial, use ignore_index=True:

resultado = pd.concat([vendas_jan, vendas_fev], ignore_index=True)
print(resultado)
   produto  unidades
0    Mouse        30
1  Teclado        15
2    Mouse        22
3  Monitor         8

Exemplo 7 - empilhando lado a lado

Com axis=1, em vez de empilhar linhas, o concat() junta colunas lado a lado, alinhando pelo índice:

resultado = pd.concat([esquerda, direita], axis=1)
print(resultado)
    nome    cidade
c1   Ana    Recife
c2 Bruno  Curitiba
c3 Carla       NaN

Nesse caso o resultado fica parecido com o .join() do Exemplo 5, já que os dois DataFrames compartilham índice — a diferença é que concat() não tem o conceito de “esquerda” e “direita” prioritário, ele só alinha pelo índice e junta as colunas de cada um.

Com merge(), join() e concat(), você já consegue reunir dados espalhados em várias fontes numa única tabela pronta pra análise. No próximo post da série, o foco muda pra dentro das colunas: como tratar texto e dados categóricos com o Pandas.

Fonte adaptada: Pandas - Merging/Joining, Pandas - Concatenation