Pandas: Otimização de Performance

Última atualização: 2026-08-26

O script apply do Bob levou 10 minutos; Alice mudou para vectorização e terminou em 30 segundos — uma aceleração de 20x. Este não é um caso isolado: 90% dos problemas de performance do Pandas vêm de "usar loops em vez de vectorização". Esta seção cobre o kit completo de ferramentas de performance, desde hábitos de programação até otimização em nível de motor, ajudando você a escrever código Pandas que é rápido e eficiente em memória.

⚠️ Nota: O código abaixo requer um ambiente Python local para ser executado.

1. O Que Você Vai Aprender


2. Os 10 Minutos de apply do Bob

(1) O Problema: Loops Linha por Linha São Lentos Demais

Bob usou iterrows para processar 100K linhas, e levou 10 minutos:

PYTHON
import pandas as pd
import numpy as np

# NÃO FAÇA ISSO — muito lento!
# for i, row in df.iterrows():
#     df.loc[i, 'new_col'] = row['a'] * row['b'] + row['c']
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

(2) A Solução: Vectorização em 30 Segundos

▶ Exemplo: Vectorização vs Loops (Dificuldade ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(100000),
    'b': np.random.randn(100000),
    'c': np.random.randn(100000)
})

# ❌ Lento: iterrows (~300s para 100K linhas)
# for i, row in df.iterrows():
#     df.loc[i, 'result'] = row['a'] * row['b'] + row['c']

# ✅ Rápido: vetorizado (~0.03s)
df['result'] = df['a'] * df['b'] + df['c']

print(f"Média do resultado: {df['result'].mean():.4f}")
# ~30 segundos vs ~0.03 segundos — 1000x mais rápido!
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

3. Ranking de Performance: Loops → apply → Vectorização

(1) Comparando 4 Métodos

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Comparação de velocidade de 4 métodos (Dificuldade ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(50000),
    'b': np.random.randn(50000)
})

# Método 1: iterrows (mais lento — NUNCA use para computação)
# ~60 segundos para 50K linhas

# Método 2: apply (lento)
result2 = df.apply(lambda row: row['a'] + row['b'], axis=1)

# Método 3: vetorizado (rápido)
result3 = df['a'] + df['b']

# Método 4: numpy (mais rápido)
result4 = (df['a'].values + df['b'].values)

# Verificar mesmo resultado
print(np.allclose(result2.values, result3.values))  # True
print(np.allclose(result3.values, result4))          # True
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

(2) Tabela de Ranking de Performance

Rank Método Velocidade Relativa Melhor Para
1 Operações NumPy Mais rápido (1x) Numérico puro
2 Vectorização Pandas Rápido (1-2x) Rótulos + numérico
3 map (dicionário) Médio (5-10x) Mapeamento um-para-um
4 apply Lento (50-100x) Lógica complexa
5 itertuples Muito lento (200x) Precisa iterar linhas
6 iterrows Mais lento (500x) Nunca use
🔥 Regra de Ouro: Use vectorização sempre que possível. Se não puder, use apply. Loops são o último recurso. iterrows deve ser sempre a última escolha (ou nenhuma escolha).


4. Motor de Expressões eval / query

(1) eval Acelera Operações Complexas

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Motor de expressões eval (Dificuldade ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'a': np.random.randn(100000),
    'b': np.random.randn(100000),
    'c': np.random.randn(100000),
    'd': np.random.randn(100000)
})

# Expressão complexa — eval evita DataFrames intermediários
result1 = df['a'] * df['b'] + df['c'] / df['d'] - df['a'] ** 2
result2 = df.eval('a * b + c / d - a ** 2')

print(np.allclose(result1, result2))  # True
# eval é mais rápido quando:
# 1. Muitas colunas na expressão (>4)
# 2. DataFrame é grande (>10K linhas)
# 3. Memória é limitada (evita intermediários)

# query para filtragem
filtered = df.query('a > 0 and b < 0')
# Equivalente: df[(df['a'] > 0) & (df['b'] < 0)]
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

(2) eval vs Operações Normais

Cenário Vantagem do eval Desvantagem do eval
Expressões complexas multi-coluna Menos variáveis intermediárias Sintaxe limitada
DataFrame grande Economiza memória Sem benefício para dados pequenos
Operações simples Sem vantagem Legibilidade ruim
Filtragem condicional (query) Melhor legibilidade Flexibilidade limitada

5. Otimização de Memória com categorias

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Compressão com tipo category (Dificuldade ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
# Simular: 100K linhas, 10 cidades únicas
df = pd.DataFrame({
    'city': np.random.choice(['NYC', 'LA', 'Chicago', 'Houston', 'Phoenix',
                               'Philly', 'San Antonio', 'San Diego', 'Dallas', 'Austin'], 100000),
    'status': np.random.choice(['Ativo', 'Inativo', 'Pendente', 'Cancelado'], 100000)
})

# Antes: tipo object
mem_before = df.memory_usage(deep=True).sum()
print(f"Antes (object): {mem_before / 1024:.1f} KB")

# Depois: tipo category
df['city'] = df['city'].astype('category')
df['status'] = df['status'].astype('category')
mem_after = df.memory_usage(deep=True).sum()
print(f"Depois (category): {mem_after / 1024:.1f} KB")
print(f"Economia: {(1 - mem_after / mem_before) * 100:.1f}%")
# Tipicamente 80-95% de redução de memória para strings de baixa cardinalidade!
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

(1) Quando Usar category

Condição Adequado Não Adequado
Valores únicos < 50% do total de linhas
Colunas de string
Ordenação é significativa Strings aleatórias não ordenadas
Precisa de métodos de string ❌ (.str limitado) ✅ (use objeto)
groupby frequente ✅ (mais rápido)

6. Compressão Numérica com downcast

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Tipos numéricos com downcast (Dificuldade ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'id': np.arange(100000),
    'age': np.random.randint(0, 120, 100000),
    'score': np.random.randint(0, 100, 100000),
    'price': np.round(np.random.uniform(0, 1000, 100000), 2),
    'flag': np.random.choice([0, 1], 100000)
})

# Verificar tipos atuais e memória
print("Antes:")
print(df.dtypes)
print(f"Memória: {df.memory_usage(deep=True).sum() / 1024:.1f} KB")

# Downcast de inteiros
df['id'] = pd.to_numeric(df['id'], downcast='unsigned')   # uint32 → uint32
df['age'] = pd.to_numeric(df['age'], downcast='unsigned')  # int64 → uint8 (0-255)
df['score'] = pd.to_numeric(df['score'], downcast='unsigned')
df['flag'] = pd.to_numeric(df['flag'], downcast='unsigned')  # int64 → uint8

# Downcast de floats
df['price'] = pd.to_numeric(df['price'], downcast='float')  # float64 → float32

print("\nDepois:")
print(df.dtypes)
print(f"Memória: {df.memory_usage(deep=True).sum() / 1024:.1f} KB")
# Economia típica: 50-75%
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

7. Copy-on-Write e Estratégias para Big Data

(1) Copy-on-Write (CoW)

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Comportamento do CoW (Dificuldade ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# Pandas 3.x: CoW é o padrão
pd.options.mode.copy_on_write = True

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# Com CoW: slice retorna uma view, modificação cria uma cópia
subset = df[['a']]  # nenhuma cópia até ser modificada
df.loc[0, 'a'] = 99  # modifica df, subset inalterado
print(subset)  # ainda [1, 2, 3] — CoW protege

# Padrões seguros sob CoW:
df['c'] = df['a'] + df['b']  # ✅ atribuir nova coluna
df = df.drop(columns='c')     # ✅ reatribuir

# Evite:
# subset['a'] = 10  # ❌ SettingWithCopyWarning (problema pré-CoW, corrigido pelo CoW)
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

(2) Estratégias para Big Data

Tamanho dos Dados Estratégia
< 1GB Pandas padrão
1-5GB downcast + category + eval
5-50GB processamento em chunks com chunksize
> 50GB Considere Dask / Polars / PySpark

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Chunking com chunksize (Dificuldade ⭐)

PYTHON
import pandas as pd
from io import StringIO

# Simular CSV grande
csv_data = "id,value\n" + "\n".join([f"{i},{i*10}" for i in range(1000)])

# Processar em chunks
total = 0
for chunk in pd.read_csv(StringIO(csv_data), chunksize=200):
    total += chunk['value'].sum()

print(f"Total: {total}")
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

8. Exemplo Completo: Otimização End-to-End para Um Milhão de Linhas

(6) ▶ Checklist de Otimização de Performance

100%
graph TB
    A[Otimização de Performance] --> B[1. Vectorização sobre loops]
    A --> C[2. Aceleração com eval / query]
    A --> D[3. Compressão de strings com category]
    A --> E[4. Compressão numérica com downcast]
    A --> F[5. Chunking com chunksize]
    A --> G[6. Copy-on-Write]
    B --> H[100-500x de aceleração]
    C --> I[Economizar memória, evitar intermediários]
    D --> J[80-95% de economia de memória]
    E --> K[50-75% de economia de memória]
    F --> L[Processar dados além da memória]
    G --> M[Fatiamento zero-copy]
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

: Do lento ao rápido, fluxo de trabalho completo (Dificuldade ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# Exemplo abrangente: otimização de 1M de linhas
# iterrows → apply → vetorizado → eval → category
# ============================================

np.random.seed(42)
df = pd.DataFrame({
    'city': np.random.choice([f'Cidade_{i}' for i in range(20)], 1000000),
    'category': np.random.choice(['Eletrônicos', 'Roupas', 'Casa', 'Esportes'], 1000000),
    'price': np.round(np.random.uniform(10, 500, 1000000), 2),
    'quantity': np.random.randint(1, 100, 1000000),
    'discount': np.random.choice([0, 0.1, 0.2, 0.3], 1000000)
})

# Passo 1: Memória baseline
mem1 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"Passo 1 — Memória bruta: {mem1:.1f} MB")

# Passo 2: Downcast de numéricos
df['quantity'] = pd.to_numeric(df['quantity'], downcast='unsigned')
df['price'] = pd.to_numeric(df['price'], downcast='float')
df['discount'] = pd.to_numeric(df['discount'], downcast='float')
mem2 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"Passo 2 — Após downcast: {mem2:.1f} MB ({(1-mem2/mem1)*100:.0f}% economizado)")

# Passo 3: Category para strings
df['city'] = df['city'].astype('category')
df['category'] = df['category'].astype('category')
mem3 = df.memory_usage(deep=True).sum() / (1024**2)
print(f"Passo 3 — Após category: {mem3:.1f} MB ({(1-mem3/mem1)*100:.0f}% economizado total)")

# Passo 4: Cálculo vetorizado (rápido)
df['revenue'] = df['price'] * df['quantity'] * (1 - df['discount'])
print(f"Passo 4 — Receita calculada (vetorizado, ~0.01s)")

# Passo 5: Alternativa com eval
df['revenue2'] = df.eval('price * quantity * (1 - discount)')
print(f"Passo 5 — Receita via eval")

# Verificar
print(f"Resultados coincidem: {np.allclose(df['revenue'], df['revenue2'])}")
TEXT 📖 Somente leitura
> **Saída:** Execute isso em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da versão do pandas.

❓ Perguntas Frequentes

P: Qual é a diferença entre iterrows e itertuples? R: iterrows retorna pares (index, Series), criando uma nova Series a cada vez — extremamente lento (~500x mais lento que a vectorização). itertuples retorna pares (index, namedtuple) sem criar uma Series — 5-10x mais rápido que iterrows, mas ainda 50x mais lento que a vectorização. Use itertuples quando precisar iterar linha por linha, mas primeiro considere se a vectorização é possível.

P: Quando eval é mais rápido? R: eval é mais rápido com dados grandes (>10K linhas) e expressões multi-coluna complexas — ele evita criar DataFrames intermediários e usa o motor numexpr para aceleração. Para dados pequenos ou expressões simples, eval é na verdade mais lento (sobrecarga de inicialização do motor). Regra prática: eval vale a pena apenas para operações compostas com 4+ colunas em grandes conjuntos de dados.

P: inplace realmente economiza memória? R: No Pandas 2.x, inplace geralmente não economiza memória — internamente ele pode ainda criar uma cópia temporária antes de atribuir. Sob o modo Copy-on-Write do Pandas 3.x, operações inplace e non-inplace têm basicamente o mesmo custo de memória. Recomendação: evite inplace; use o padrão de atribuição df = df.method() para uma semântica mais clara.

P: O que é Copy-on-Write? R: CoW é o comportamento padrão no Pandas 3.x — os dados compartilham a memória subjacente, e uma cópia é criada apenas quando ocorre modificação. Benefícios: o fatiamento não copia (economiza memória), a atribuição encadeada é segura (sem SettingWithCopyWarning). Impacto: algumas operações inplace se comportam de forma diferente e requerem reatribuição.

P: E quanto a arquivos extremamente grandes? R: Estratégia em três níveis: ① chunksize para leitura em chunks + agregação (adequado para estatísticas divisíveis); ② downcast/category para reduzir a memória por linha (caber mais linhas na memória); ③ trocar de ferramentas — Dask (API Pandas distribuída), Polars (DataFrame ultra-rápido escrito em Rust), PySpark (nível de cluster). O limite de memória de uma única máquina é aproximadamente 3-5x o tamanho dos dados.

P: apply é sempre mais lento que a vectorização? R: Na maioria dos casos, sim — apply chama uma função Python linha por linha, enquanto a vectorização é executada no nível C. Exceção rara: quando a vectorização requer criar múltiplos arrays intermediários enormes, apply pode economizar memória (mas não tempo). Estratégia: prefira a vectorização primeiro, tente eval se a performance for insuficiente, e use apply apenas como último recurso.

P: Como usar memory_usage? R: df.memory_usage(deep=True) retorna a contagem de bytes para cada coluna. deep=True calcula a memória real de strings para colunas objeto (caso contrário, conta apenas tamanhos de ponteiros). Total: df.memory_usage(deep=True).sum(). Use esta função para comparar antes e depois da otimização. Colunas category armazenam apenas códigos inteiros + um dicionário, usando muito menos memória que objeto.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Crie um DataFrame com 10K linhas, calcule uma nova coluna usando apply e vectorização, e compare o memory_usage.
  2. Intermediário (Dificuldade ⭐⭐): Crie um DataFrame com 100K linhas com colunas objeto, otimize a memória usando category + downcast, e calcule a porcentagem economizada.
  3. Desafio (Dificuldade ⭐⭐⭐): Crie um DataFrame com 1 milhão de linhas (com colunas category/float/int), e então complete: cálculo vetorizado → cálculo com eval → comparar resultados → acompanhar memory_usage ao longo do processo → produzir um relatório completo de otimização.

← Anterior: Visualização · Próximo: Saída Estilizada →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%