Back to Blog
pythondata-sciencepandasnews

Pandas vs Polars: Por Que a Evolução de 2025 Muda Tudo

Pare de adivinhar qual biblioteca de dados Python usar. Descubra como o Copy-on-Write do Pandas e o engine baseado em Rust do Polars estão redefinindo a engenharia de dados em 2025.

DataFormatHub Team
Dec 27, 20257 min
Share:
Pandas vs Polars: Por Que a Evolução de 2025 Muda Tudo

O ecossistema de dados Python, no final de 2025, continua sua rápida evolução, impulsionada por uma demanda insaciável por desempenho e escalabilidade. Por anos, o pandas tem sido o cavalo de batalha onipresente, fundamental para inúmeros pipelines analíticos. No entanto, os últimos dois anos testemunharam a ascensão meteórica e a significativa maturação do polars, uma biblioteca de DataFrame baseada em Rust que desafia fundamentalmente a abordagem tradicional do pandas. Como desenvolvedores, passamos do debate "qual é melhor" para focar em "quando usar o quê" e, crucialmente, "como essas bibliotecas estão convergindo e divergindo" em suas iterações mais recentes. Esta análise mergulha profundamente nos desenvolvimentos recentes, mudanças arquitetônicas e implicações práticas para desenvolvedores seniores que navegam pela pilha de dados Python.

A Ascensão Contínua do Polars: Uma Análise Profunda da Otimização de Consultas

O Polars consolidou sua reputação como uma potência de desempenho, em grande parte devido ao seu sofisticado otimizador de consultas e modelo de execução preguiçosa (lazy execution). Ao contrário da execução imediata (eager execution) do pandas, onde cada operação é executada imediatamente, o Polars constrói um plano lógico de toda a computação antes da execução, permitindo uma otimização significativa. Essa abordagem não se trata apenas de adiar a computação; trata-se de um reordenamento e simplificação inteligentes das operações.

Em seu núcleo, o Polars utiliza um Grafo Acíclico Direcionado (DAG) para representar a sequência de transformações. Quando um LazyFrame é criado (por exemplo, via pl.scan_csv(), que implicitamente usa avaliação preguiçosa, ou chamando .lazy() em um DataFrame eager), o Polars constrói este DAG. Operações como filter(), select() e with_columns() adicionam nós a este grafo. A mágica acontece quando .collect() é invocado, acionando o otimizador.

O otimizador emprega várias técnicas-chave:

  • Predicate Pushdown: Filtros são aplicados o mais cedo possível, idealmente na própria fonte de dados. Isso reduz drasticamente a quantidade de dados processados downstream, economizando memória e ciclos de CPU. Por exemplo, se você estiver lendo um arquivo Parquet grande e filtrando imediatamente por uma coluna, o Polars tentará enviar esse filtro para o leitor Parquet, carregando apenas as linhas relevantes.
  • Projection Pushdown: Apenas as colunas necessárias para o resultado final são carregadas e processadas. Se um pipeline envolve muitas colunas, mas o select() final precisa apenas de algumas, o Polars evita carregar ou computar as colunas desnecessárias.
  • Common Subplan Elimination: Sub-expressões idênticas dentro de um plano de consulta são identificadas e computadas apenas uma vez, reutilizando o resultado.
  • Expression Simplification: Operações redundantes são removidas ou simplificadas (por exemplo, pl.col("foo") * 1 se torna pl.col("foo")).

Esta arquitetura é particularmente evidente ao inspecionar um plano de execução de LazyFrame usando .explain(). Por exemplo, uma cadeia aparentemente complexa de filtros e agregações frequentemente revelará um plano otimizado onde os filtros são movidos para a leitura inicial, e as agregações intermediárias são combinadas.

import polars as pl
import os

# Assume 'large_data.csv' exists with columns 'id', 'category', 'value', 'timestamp'

# Example of a LazyFrame with predicate and projection pushdown potential
lf = (
    pl.scan_csv("large_data.csv")
    .filter(pl.col("timestamp").is_between(pl.datetime(2025, 1, 1), pl.datetime(2025, 12, 31)))
    .group_by("category")
    .agg(
        pl.col("value").mean().alias("avg_value"),
        pl.col("id").n_unique().alias("unique_ids")
    )
    .filter(pl.col("avg_value") > 100)
    .select(["category", "avg_value"])
)

print(lf.explain())

A saída de .explain() serve como uma ferramenta crucial de depuração e ajuste de desempenho, oferecendo uma representação textual do plano lógico otimizado. Para aprendizes visuais, o Polars também oferece show_graph() (se o graphviz estiver instalado), que renderiza o DAG, tornando as otimizações tangíveis.

A Evolução do Pandas: Copy-on-Write (CoW) e Semântica de Memória

Enquanto o Polars foi construído do zero com primitivas de desempenho, o pandas tem abordado sistematicamente suas próprias limitações arquitetônicas. Um desenvolvimento notável nas iterações recentes do pandas (começando com a versão 2.0 e refinado em lançamentos 2.x subsequentes) é o forte impulso para a semântica Copy-on-Write (CoW). Esta mudança é uma resposta direta ao "SettingWithCopyWarning" histórico do pandas e ao comportamento de memória frequentemente imprevisível, particularmente quando atribuições encadeadas levavam a mutações de dados não intencionais ou cópias eager dispendiosas.

Antes do CoW, o pandas frequentemente fazia cópias implícitas de DataFrames ou Series durante as operações, levando a um maior consumo de memória e desempenho não determinístico. Modificar uma "view" de um DataFrame poderia ou não modificar o original, dependendo de heurísticas internas, tornando o código difícil de entender. Com o CoW habilitado (geralmente via pd.set_option("mode.copy_on_write", True)), o pandas visa um comportamento mais previsível: os dados são copiados apenas quando são realmente modificados.

A implicação arquitetônica é uma mudança de views mutáveis para blocos de dados imutáveis que são compartilhados até que ocorra uma operação de gravação. Quando um usuário executa uma operação que modificaria um bloco compartilhado, uma cópia de apenas o bloco afetado é feita, deixando os dados originais compartilhados intocados. Isso reduz cópias desnecessárias, melhorando a eficiência da memória e, muitas vezes, o desempenho, especialmente em cenários envolvendo várias operações intermediárias que, em última análise, não modificam os dados originais.

import pandas as pd
import numpy as np

# Enable Copy-on-Write (recommended for recent pandas versions)
pd.set_option("mode.copy_on_write", True)

df = pd.DataFrame({'A': range(1_000_000), 'B': np.random.rand(1_000_000)})
df_view = df[df['A'] > 500_000] # This creates a logical view

# With CoW, 'df_view' shares memory with 'df' initially.
print(f"Memory usage of df_view before modification: {df_view.memory_usage(deep=True).sum() / (1024**2):.2f} MB")

# Now, modify a column in df_view
df_view['B'] = df_view['B'] * 10

# With CoW, only now is the 'B' column data for df_view copied.
print(f"Memory usage of df_view after modification: {df_view.memory_usage(deep=True).sum() / (1024**2):.2f} MB")

A Fronteira da Interoperabilidade: Integração Apache Arrow

Tanto o pandas quanto o polars estão profundamente investidos no Apache Arrow, e este compromisso só se fortaleceu no último ano. Arrow é um formato de memória columnar agnóstico de linguagem, projetado para troca e processamento eficientes de dados. Sua importância não pode ser exagerada em um ecossistema de dados heterogêneo. Assim como escolher o formato de serialização correto é crítico – veja nosso guia sobre JSON vs YAML vs JSON5: A Verdade Sobre os Formatos de Dados em 2025—a escolha do formato de memória define o teto do desempenho da sua aplicação.

Para o Polars, Arrow é fundamental. Sua arquitetura central é construída diretamente sobre a implementação Rust do Arrow, o que significa que os DataFrames do Polars inerentemente usam o formato de memória columnar Arrow. Isso permite operações de cópia zero e processamento vetorizado via instruções SIMD. O Pandas, por outro lado, tem integrado cada vez mais o PyArrow como um backend opcional e, agora, frequentemente padrão, para tipos de dados específicos. O Pandas 2.0 tornou o PyArrow uma dependência necessária para inferência padrão de strings e introduziu o conceito de DataFrames ArrowDtype.

import pandas as pd
import polars as pl
import pyarrow as pa

# Pandas with PyArrow backend
df_pd_arrow = pd.read_csv("some_text_data.csv", dtype_backend='pyarrow')

# Polars naturally uses Arrow
df_pl = pl.read_csv("some_text_data.csv")

# Interchange via __dataframe__ protocol
df_from_polars = pd.api.interchange.from_dataframe(df_pl.__dataframe__())

Benchmarking de Desempenho e Trade-offs: Quando Escolher o Que

Benchmarks recentes mostram consistentemente o Polars superando o pandas em muitas operações comuns de dados, particularmente com conjuntos de dados maiores.

Tipo de Operaçãopandas (Eager, NumPy/Arrow Backed)Polars (Lazy, Rust/Arrow Backed)
Ingestão de DadosBom, melhorando com PyArrowExcelente, especialmente com pushdowns
FiltragemEficiente, mas materialização eagerAltamente eficiente (predicate pushdown)
AgregaçõesPode ser lento devido a cópias de memóriaExcelente, paralelizado
JoinsO desempenho varia, intensivo em memóriaMuito eficiente, hash joins otimizados
Pegada de MemóriaMaior (5-10x o tamanho dos dados)Significativamente menor (2-4x o tamanho dos dados)

Gerenciamento de Memória e Escalabilidade: Uma Análise Mais Detalhada

O Polars opera em um modelo de dados columnar onde os dados para cada coluna são armazenados contíguos como arrays Apache Arrow. Para conjuntos de dados maiores que a RAM, o Polars oferece processamento híbrido out-of-core via sua API de streaming. Isso permite que ele processe dados em partes, despejando transparentemente os resultados intermediários em disco quando os limites de memória são atingidos.

Evolução da API e Experiência do Desenvolvedor

O sistema de expressões do Polars é um diferenciador fundamental, permitindo que transformações complexas sejam definidas como unidades únicas e otimizadas. As expressões são compostas, permitindo que os desenvolvedores escrevam código altamente legível e paralelizado.

# Polars expression example
result_pl = df_pl.lazy().with_columns(
    (pl.col("value") * 1.1).alias("value_x1.1"),
    (pl.col("value").rank(method="min")).alias("value_rank"),
    pl.when(pl.col("value") > 20).then(pl.lit("High")).otherwise(pl.lit("Low")).alias("value_tier")
).filter(pl.col("value_tier") == "High").collect()

O Pandas tem se concentrado na consistência, com PDEP-14 e PDEP-10 indicando uma mudança para um dtype nativo de string de alto desempenho e tornando o PyArrow uma dependência necessária. A implementação do Copy-on-Write (PDEP-7) continua sendo a evolução mais impactante para a estabilidade da biblioteca.

A Trajetória Futura: O Que Vem Por Aí no Horizonte

Olhando para o futuro a partir do final de 2025, o pandas está evoluindo para ser uma solução em memória mais performática e robusta, enquanto o polars está rapidamente se tornando a escolha padrão para processamento de dados de alto desempenho, escalável e potencialmente distribuído. O Polars está expandindo agressivamente para a aceleração de GPU via NVIDIA RAPIDS cuDF e execução distribuída através do "Polars Cloud". A base compartilhada do Apache Arrow garante que os dados possam fluir eficientemente entre essas bibliotecas poderosas, permitindo fluxos de trabalho híbridos que aproveitam os pontos fortes de cada um.


Fontes


🛠️ Ferramentas Relacionadas

Explore estas ferramentas DataFormatHub relacionadas a este tópico:


📚 Você Também Pode Gostar