# Concatenação e junções

Juntar linhas com concat e relacionar tabelas com merge e join, verificando cardinalidade.

Página: https://resumos.rgo.pt/cadeiras/ct-iadp/concatenacao-juncoes/

Há dois pedidos diferentes. Podemos juntar vendas de janeiro às vendas de fevereiro, conservando as mesmas colunas. Ou podemos acrescentar o preço a cada venda através do código do produto. O primeiro usa concatenação; o segundo usa uma junção.

## Concatenar linhas ou colunas

```
vendas = pd.concat([janeiro, fevereiro], ignore_index=True)
```

Com `axis=0`, pandas acrescenta linhas e alinha as colunas pelo nome. `ignore_index=True` cria um índice consecutivo. Se uma coluna só existe num dos lados, as linhas do outro ficam com valores em falta nessa coluna. `join="inner"` conserva apenas as colunas comuns.

Com `axis=1`, acrescenta colunas e alinha as linhas pelo índice. Não associa automaticamente a primeira linha de uma tabela à primeira da outra. Para uma relação por código de produto, usa `merge`.

Slides antigos podem usar `DataFrame.append`. Esse método foi removido no pandas 2.0. O equivalente para juntar tabelas é `pd.concat`. Para muitas linhas, recolhe-as primeiro e concatena uma vez, em vez de reconstruir a tabela a cada iteração.

## Conservar a origem e verificar o índice

`keys` acrescenta um nível ao índice, útil para conservar a origem de cada bloco. `names` dá nomes a esses níveis. Continua a existir um índice por linha dentro de cada origem.

```
janeiro = pd.DataFrame({"unidades": [2, 3]})
fevereiro = pd.DataFrame({"unidades": [4]})
por_mes = pd.concat([janeiro, fevereiro], keys=["jan", "fev"], names=["mes", "linha"])
print(por_mes.loc["jan", "unidades"].to_list())  # [2, 3]
```

`verify_integrity=True` rejeita rótulos repetidos no eixo concatenado. Sem `keys`, os índices 0 dos dois blocos colidem. Com `keys`, os pares `("jan", 0)` e `("fev", 0)` são distintos. Esta verificação é sobre o índice, não sobre valores duplicados numa coluna de identificadores. Com `ignore_index=True`, o novo índice consecutivo não conserva a colisão original.

## Relacionar por uma chave

```python
import pandas as pd
vendas = pd.DataFrame({"codigo": ["A", "B", "A", "C"], "quantidade": [2, 3, 5, 4]})
produtos = pd.DataFrame({"codigo": ["A", "B"], "preco": [1.5, 1.2]})
resultado = vendas.merge(produtos, on="codigo", how="left", validate="many_to_one", indicator=True)
print(resultado.to_string(index=False))
print("linhas sem preço:", resultado["preco"].isna().sum())
```

As duas vendas de A recebem 1,50, a venda de B recebe 1,20 e C fica sem preço. A junção à esquerda conserva as quatro vendas. `indicator=True` identifica as linhas com correspondência como `both` e C como `left_only`.

`validate="many_to_one"` exige que o código seja único em `produtos`; pode repetir-se em `vendas`. Esta verificação protege o significado de uma linha por venda.

## O tipo de junção

| `how` | Chaves conservadas |
| --- | --- |
| `inner` | Presentes nos dois lados |
| `left` | Todas as do lado esquerdo |
| `right` | Todas as do lado direito |
| `outer` | Presentes em pelo menos um lado |
| `cross` | Todos os pares, sem chave |

Uma junção interna excluiria a venda de C. Isso pode diminuir a faturação ou a contagem sem qualquer erro de execução. Uma junção externa incluiria também produtos sem vendas.

Se os nomes das chaves diferem, usa `left_on="produto"` e `right_on="codigo"`. Para colunas com o mesmo nome que não sejam chaves, `suffixes=("_venda", "_catalogo")` distingue as origens.

## Repetições multiplicam linhas

Se A aparece duas vezes à esquerda e três vezes à direita, a junção cria seis pares para A. Não escolhe um dos preços. Uma relação muitos-para-muitos pode ser válida, mas exige que esse produto de correspondências seja o resultado pretendido.

![Duas vendas do produto A relacionam-se com uma única linha de preço e mantêm duas linhas no resultado.](https://resumos.rgo.pt/cadeiras/ct-iadp/concatenacao-juncoes/figura-1.svg)

`validate` aceita `one_to_one`, `one_to_many` e `many_to_one`. Confere também o número de linhas, chaves sem correspondência e totais antes e depois. pandas pode relacionar chaves nulas entre si, o que difere do comportamento habitual de `NULL` em SQL.

## join usa o índice

`vendas.join(produtos.set_index("codigo"), on="codigo")` procura o código da venda no índice de produtos. `join` é útil quando o índice representa a chave. Define essa relação explicitamente; um índice de posições não é um código de produto.

## Exercícios

Contar correspondências

Uma chave A aparece duas vezes à esquerda e três à direita. Quantas linhas de A cria uma junção pela chave?

Primeira pista

Cada linha da esquerda combina com todas as correspondências da direita.

Mais uma pista

São dois grupos de três pares.

Ver solução

Há 2 × 3 = 6 linhas. Uma relação muitos-para-muitos pode alterar contagens e somas.

#### Erros frequentes

Somar 2 + 3 ou escolher apenas uma das linhas da direita.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/concatenacao-juncoes/#repeti%C3%A7%C3%B5es-multiplicam-linhas)

Conservar vendas sem preço

Queres conservar todas as vendas e detetar produtos sem preço num catálogo. Que junção escolhes?

Primeira pista

A tabela que não pode perder linhas fica à esquerda.

Mais uma pista

As linhas sem correspondência precisam de ficar visíveis.

Ver solução

Usa left e valida a relação many\_to\_one se o catálogo deve ter uma linha por produto. Confere nulos e contagem depois.

*   **left, com vendas à esquerda.** Conserva todas as vendas e mostra preço ausente quando não há correspondência.
*   **inner.** Elimina vendas sem produto correspondente no catálogo.
*   **cross.** Cria todos os pares de venda e produto, ignorando a chave.

#### Erros frequentes

Eliminar as vendas desconhecidas e apresentar o total restante como total completo.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/concatenacao-juncoes/#o-tipo-de-jun%C3%A7%C3%A3o)
