Criar, alterar e limpar tabelas
Colunas calculadas, rename, drop, map, apply e tratamento explícito de valores em falta.
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
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))Dados de entrada
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.
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))Dados de entrada
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
Mais uma pista
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.
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
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.