Curso
Muita gente usa o .apply() pra “evitar loops” e espera que seja rápido e direto. Na prática, o " .apply(axis=1) " por linha ainda é um loop no nível do Python. Pode ser lento com grandes volumes de dados e, às vezes, mostra resultados que você não esperava. A solução é simples: use operações vetorizadas pandas/NumPy para tarefas comuns e reserve .apply() para a lógica que realmente precisa de várias colunas.
Este guia mostra como .apply() funciona hoje, destaca as armadilhas comuns e fornece padrões prontos para uso que são mais rápidos e claros.
O que é DataFrame.apply() e Series.apply()?
DataFrame.apply(func, axis=0) chama func em cada coluna por padrão. Com axis=1, ele chama func em cada linha. Series.apply(func) chama func em cada elemento (ou em todo o Series, dependendo das assinaturas nas versões recentes).
- Por linha vs. por coluna:
axis=1significa “por linha”.axis=0(padrão) significa “por coluna”.
- Regras de retorno de formato (pandas ≥0.23): Se a sua função devolver um escalar por linha/coluna, você vai ter um objeto de matriz (
Series). Se ele retornar umSeriesoudict, o pandas expande essas chaves em umDataFrame. Se ele retornar uma lista/matriz, você vai receber uma série de listas, a menos que defina umresult_type='expand'.
- Erros mais rigorosos (pandas ≥2.0): Agora, quando os comprimentos das listas não batem, dá erro, em vez de simplesmente aparecer um resultado meio estranho.
Primeira escolha recomendada: Operações vetorizadas
A maioria das tarefas que envolvem combinar ou transformar colunas são mais rápidas e claras com expressões vetorizadas ou métodos integrados. O exemplo a seguir calcula a diferença de pontos de um time de beisebol sem usar .apply().
import pandas as pd
team_stats = pd.DataFrame({
"Team": ["ARI", "ATL", "BAL"],
"League": ["NL", "NL", "AL"],
"Year": [2012, 2012, 2012],
"RunsScored": [734, 700, 712],
"RunsAllowed": [688, 600, 705],
"Wins": [81, 94, 93],
"Games": [162, 162, 162],
"Playoffs": [0, 1, 1],
})
# Vectorized: fast and idiomatic
team_stats["RunDiff"] = team_stats["RunsScored"] - team_stats["RunsAllowed"]
print(team_stats[["Team", "RunsScored", "RunsAllowed", "RunDiff"]])
Row-Wise .apply() quando você realmente precisa
Use isso .apply(axis=1) quando sua lógica realmente abrange várias colunas e não é facilmente vetorizada (por exemplo, regras condicionais que dependem de vários campos).
calcular um valor derivado de várias colunas
Esse padrão calcula um valor por linha usando várias entradas. A abordagem vetorizada acima ainda é a preferida, mas isso mostra o uso correto por linha e as opções que afetam a velocidade e a saída.
def compute_run_diff(row):
# Treat the row as read-only; return a scalar
return row["RunsScored"] - row["RunsAllowed"]
# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"] = team_stats.apply(compute_run_diff, axis=1)
Quando a função é numérica e só precisa de matrizes brutas, passar raw=True pula algumas sobrecargas do pandas, fornecendo uma matriz NumPy para sua função.
import numpy as np
def compute_run_diff_raw(values):
# values is a NumPy array when raw=True
# Order matches the column order we select
rs, ra = values
return rs - ra
team_stats["RunDiff_raw"] = team_stats[["RunsScored", "RunsAllowed"]].apply(
compute_run_diff_raw, axis=1, raw=True
)
confira a entrada da função uma vez
Quando eu conecto uma nova função de linha, imprimo uma linha uma vez para confirmar o que está sendo passado.
def debug_row(row):
# Print the first row only
if row.name == team_stats.index[0]:
print("Example row:", row.to_dict())
return 0
_ = team_stats.apply(debug_row, axis=1)
Estudo de caso: Somas, totais da temporada e sinalizadores de texto
Dadas as estatísticas Rays por ano, prefira métodos integrados e ferramentas elementares sempre que possível. .apply() onde possível.
rays_by_year = pd.DataFrame(
{
"Year": [2012, 2011, 2010, 2009, 2008],
"RunsScored": [697, 707, 802, 803, 774],
"RunsAllowed": [577, 614, 649, 754, 671],
"Wins": [90, 91, 96, 84, 97],
"Playoffs": [0, 1, 1, 0, 1],
}
).set_index("Year")
obter somas de colunas de forma eficiente
Use DataFrame.sum() em vez de .apply(sum). É mais rápido e mais claro.
# Preferred
totals = rays_by_year.sum(axis=0)
print(totals)
# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)
calcular o total de corridas marcadas numa temporada
Use aritmética vetorizada nas colunas; não é .apply() necessidade.
rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())
converter um sinalizador 0/1 em texto
Prefira Series.map() ou replace() para transformações elemento a elemento em uma coluna.
# Using map with a dict
rays_by_year["PlayoffsText"] = rays_by_year["Playoffs"].map({0: "No", 1: "Yes"})
# Equivalent with replace
rays_by_year["PlayoffsText2"] = rays_by_year["Playoffs"].replace({0: "No", 1: "Yes"})
Controle a forma de saída a partir de .apply()
Quando sua função devolver mais de um valor por linha, deixe a forma bem clara. Isso evita surpresas e problemas com versões futuras.
retornar uma lista e expandir para colunas
Use result_type='expand' para dividir uma lista/matriz em várias colunas.
def wins_losses(row):
# Derive wins and losses as two outputs
wins = row["Wins"]
losses = row["Games"] - row["Wins"] if "Games" in row else np.nan
return [wins, losses]
# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns = ["WinsOut", "LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)
retornar uma série ou dicionário para nomear colunas automaticamente
Devolvendo um Series ou dict de cada linha gera um DataFrame com rótulos de coluna correspondentes.
def summary_row(row):
return pd.Series(
{
"IsWinningSeason": row["Wins"] >= 90,
"RunRatio": (row["RunsScored"] / row["RunsAllowed"]),
}
)
summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)
Dicas de desempenho que fazem a diferença
Por linha .apply(axis=1) é prático, mas lento em quadros grandes, porque chama sua função Python uma vez por linha. Esses padrões evitam esse gargalo.
- Prefira operações vetorizadas pandas/NumPy e métodos integrados como
sum,mean,clip,where,astypee métodos de string/acessador.
- Para operações numéricas em linhas/colunas que aceitam matrizes, use
raw=Truepara obter uma matriz NumPy e reduzir a sobrecarga do pandas.
- Para trabalhar elemento a elemento em uma única coluna, use
Series.map()ou métodos vetorizados em vez deDataFrame.apply(axis=1).
- Evite alterar a linha/coluna fornecida dentro da sua função; em vez disso, retorne um novo valor.
Notas sobre a versão que você precisa saber (pandas 2.x)
As versões recentes do pandas tornaram mais rígido o comportamento em torno de .apply() para que os resultados sejam mais previsíveis.
- Resultados em forma de lista: Na versão 2.0+, se os comprimentos das listas não combinarem, vai aparecer um erro. Use comprimentos consistentes e defina
result_type='expand'quando quiser várias colunas.
- Expansão da produção: Retornar um objeto `
Series` ou ` `dictexpande-se em um DataFrame com essas chaves como colunas (estável desde 0.23).
Series.apply()alterações: O argumento `convert_dtype` está obsoleto. Se você precisar de tipos mistos, primeiro converta para umobject(por exemplo,s.astype("object").apply(fn)). As assinaturas mais recentes permitem controlar sefnrecebe escalares ou uma série em mais contextos (por exemplo,by_row).
.apply() vs. .map() vs. .applymap() vs. .agg()
Escolha a API que combina com o formato da sua transformação.
Series.map(func_or_dict): Transformação elemento a elemento em uma coluna. Ótimo pra pesquisas ou funções simples.
DataFrame.apply(func, axis=1): Lógica por linha que precisa de várias colunas.
DataFrame.apply(func, axis=0): Lógica por coluna (cada entrada é uma série que representa uma coluna).
DataFrame.applymap(func): Elemento por elemento em cada célula. Use com moderação; métodos vetorizados são mais rápidos.
.agg()/.transform(): Agregações e transformações por grupo; prefira essas em pipelines groupby.
Erros comuns e soluções rápidas
Esses são os problemas que mais causam resultados errados ou lentidão, com maneiras de corrigi-los.
- Esquecendo axis=1 para a lógica por linha: Se a sua função de repente receber colunas em vez de linhas, adicione
axis=1.
- Série inesperada de listas: Ao retornar uma lista/matriz de cada linha, defina
result_type='expand'para dividir em colunas.
- Código lento por linha: Troque por expressões vetorizadas ou internas; se não der, pense em usar o `
raw=True` pra diminuir a sobrecarga.
- Mudando a linha de entrada: Trate as entradas como somente leitura e retorne novos valores.
- Desvio do tipo de dados: Se a sua função às vezes retorna valores não inteiros, as colunas inteiras podem ser convertidas para float ou objeto. Depois, manda ver com
astypese necessário.
Conclusão
.apply() é uma ferramenta flexível, mas não é um atalho para o desempenho. Use operações vetorizadas para aritmética, agregação e transformações elementares em uma única coluna. Use um DataFrame.apply(axis=1) e apenas quando sua lógica realmente precisar de várias colunas por linha. Quando você usar, controle a forma da saída com result_type, considere raw=True para funções numéricas e fique de olho em dtypes. Esses padrões dão resultados previsíveis nos pandas modernos e se adaptam melhor conforme seus dados aumentam.
