# Criar, alterar e limpar tabelas

Colunas calculadas, rename, drop, map, apply e tratamento explícito de valores em falta.

Página: https://resumos.rgo.pt/cadeiras/ct-iadp/transformar-tabelas/

Uma transformação deve dizer que valores muda e que informação conserva. Criar a coluna de total acrescenta um cálculo a cada linha. Apagar uma linha retira uma observação, por isso precisa de uma razão.

## Criar uma coluna calculada

```python
import pandas as pd
vendas = pd.DataFrame({
    "produto": ["pão", "leite", "arroz"],
    "quantidade": [2, 3, 4],
    "preco": [1.5, 1.2, 2.0],
})
vendas["total"] = vendas["quantidade"] * vendas["preco"]
vendas = vendas.rename(columns={"preco": "preco_unitario"})
print(vendas.round(2).to_string(index=False))
```

Os totais são 3,00, 3,60 e 8,00. A multiplicação é vetorizada, alinhada pelo índice. A atribuição de um escalar a uma coluna preenche todas as linhas; a atribuição de uma Series alinha os seus rótulos com o índice do DataFrame.

`rename` muda os nomes e devolve outra tabela por defeito. `drop(columns=["preco_unitario"])` elimina uma coluna. `drop(index=[rotulo])` elimina uma linha por rótulo. Escolhe `columns` ou `index` para evitar confundir o eixo.

## Inserir, retirar e reordenar colunas

`df.insert(1, "total", valores)` insere a coluna na posição 1, antes da segunda coluna, e altera a tabela. O nome deve ser novo por defeito. Uma Series fornecida como `valores` alinha-se pelos rótulos do índice; uma lista deve ter o comprimento das linhas.

`retirada = df.pop("total")` remove a coluna e devolve-a como Series. `del df["total"]` remove-a sem devolver a coluna. `drop(columns=["total"])` devolve outra tabela por defeito. Estas operações têm efeitos diferentes; não atribuas `df = df.insert(...)`, porque `insert` devolve `None`.

Para reordenar, seleciona a lista de colunas pretendida. Para mudar todos os nomes, `df.columns = [...]` exige um nome por coluna, na ordem atual. `df.columns.str.lower()` transforma os nomes em minúsculas. Para alterar apenas alguns, `rename(columns={...})` deixa os restantes intactos.

```
vendas = pd.DataFrame({"produto": ["pão", "leite"], "unidades": [2, 3]})
vendas.insert(1, "preco", [1.5, 1.2])
precos = vendas.pop("preco")
vendas = vendas[["unidades", "produto"]]
print(precos.to_list())      # [1.5, 1.2]
print(vendas.columns.to_list())  # ['unidades', 'produto']
```

Uma atribuição com `df.loc[rotulo, ["produto", "unidades"]] = ["arroz", 4]` muda as células indicadas. Se o rótulo não existe, pode criar uma linha, com ausência nas colunas não fornecidas. Para acrescentar muitas linhas, constrói uma tabela e usa `concat`.

## map e apply

`serie.map(dicionario)` substitui valores através de uma correspondência. Valores sem correspondência ficam em falta, pelo que deves detetar chaves desconhecidas.

```
categorias = {"pão": "padaria", "leite": "laticínios"}
df["categoria"] = df["produto"].map(categorias)
```

`serie.apply(funcao)` aplica uma função a cada valor. `df.apply(funcao, axis=1)` chama a função para cada linha; com `axis=0`, para cada coluna. Prefere operações vetorizadas simples quando exprimem diretamente o cálculo. Usar `apply` para multiplicar duas colunas esconde uma operação que pandas já suporta.

## Representar ausência

Uma célula em falta não é automaticamente zero. `None`, `NaN`, `pd.NA` e `NaT` podem representar ausência, dependendo do tipo. Usa `isna()` ou `notna()` para a detetar. Não uses `valor == np.nan`, porque NaN não é igual a si mesmo.

```python
import pandas as pd
quantidades = pd.Series([2, None, 6], dtype="Float64")
print("em falta:", quantidades.isna().to_list())
print("conhecidos:", quantidades.count())
print("média conhecida:", quantidades.mean())
print("média se ausente = 0:", quantidades.fillna(0).mean())
print("soma toda ausente:", pd.Series([None, None], dtype="Float64").sum(min_count=1))
```

A média dos valores conhecidos é 4. Preencher com zero dá `8 / 3`, cerca de 2,6667. São respostas a hipóteses diferentes. Na última linha, `min_count=1` exige pelo menos um valor conhecido; sem isso, uma soma só de nulos pode dar zero.

## Escolher uma política

`dropna(subset=["quantidade"])` elimina linhas sem quantidade. `fillna(0)` substitui ausências por zero. Preencher pela média introduz valores estimados e reduz artificialmente a dispersão. Para um relatório, conserva a contagem de dados em falta e explica a hipótese usada.

`ffill()` propaga o valor anterior e `bfill()` o seguinte. Só fazem sentido quando a ordem está definida e a variável permite esse tipo de continuidade. Não propagues um preço entre produtos diferentes.

Um `pd.NA` isolado não pode ser tratado como verdadeiro ou falso. Algumas operações booleanas preservam a incerteza. Antes de filtrar, decide o destino das linhas cuja condição depende de dados ausentes.

## Duplicados e integridade

`duplicated(subset=["id"])` deteta repetições de um identificador. `drop_duplicates` conserva uma ocorrência conforme `keep`. Duas linhas iguais podem ser duas vendas reais; não as elimines apenas porque os valores coincidem. Usa a chave que identifica a observação e confere os totais antes e depois da limpeza.

## Exercícios

Conservar o denominador correto

Uma Series Float64 tem 2, um valor ausente e 6. Quanto vale mean() sem preencher ausências?

Primeira pista

mean ignora normalmente os valores ausentes.

Mais uma pista

Há dois valores conhecidos cuja soma é 8.

Ver solução

A média conhecida é 8 / 2 = 4. Se preenchesses com zero, a pergunta e o denominador seriam diferentes.

#### Erros frequentes

Dividir por três ou interpretar a ausência automaticamente como zero.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/transformar-tabelas/#representar-aus%C3%AAncia)

Atualizar a tabela certa

Queres alterar x para zero nas linhas em que x é negativo. Que expressão indica o destino da alteração numa única operação?

Primeira pista

A atribuição precisa de atingir o original.

Mais uma pista

loc pode selecionar linhas e colunas na mesma operação.

Ver solução

Usa df.loc\[mascara, “x”\] = 0, depois de justificar que zero é a correção pretendida.

*   **df.loc\[df\["x"\] < 0, "x"\] = 0.** Seleciona as linhas e a coluna no próprio destino da atribuição.
*   **df\[df\["x"\] < 0\]\["x"\] = 0.** É uma atribuição encadeada a uma seleção intermédia.
*   **df\["x"\] == 0.** Compara valores e não altera a tabela.

#### Erros frequentes

Corrigir um sintoma de execução sem definir o significado da alteração.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ct-iadp/transformar-tabelas/#criar-uma-coluna-calculada)
