Brains Up AnalyticsBRAINSUPAnalytics
NarwhalsPythonPolarspandasData Engineering

Narwhals: escreva código de DataFrame uma vez e rode em pandas, Polars e PyArrow

O que é o Narwhals — a camada leve de compatibilidade que deixa você escrever a lógica de DataFrame uma única vez (API estilo Polars) e rodar em pandas, Polars, PyArrow e mais, sem lock-in e sem dependências obrigatórias.

Por Dione Fraga · Databricks Certified Professional28 de agosto de 20264 min de leitura

Quem trabalha com dados em Python há algum tempo já viveu isto: um projeto padronizou em pandas, outro migrou para Polars pela performance, um terceiro consome dados via PyArrow. Aí você precisa de uma função de limpeza — trimar strings, filtrar linhas inválidas, agregar por chave — e descobre que a API de cada biblioteca é diferente o suficiente para você escrever a mesma transformação duas ou três vezes.

Pior ainda quando você mantém uma biblioteca interna reutilizada por vários times: se ela depende de pandas, você força pandas em todo mundo; se depende de Polars, força Polars. Não existe meio-termo elegante — até você conhecer o Narwhals.

O que é o Narwhals

O Narwhals é uma camada leve e extensível de compatibilidade entre bibliotecas de DataFrame. A ideia central é simples: você escreve a lógica uma única vez, usando uma API no estilo Polars (expressões, nw.col(...), métodos encadeados), e o Narwhals traduz isso para operações nativas do backend que você passar — pandas, Polars, PyArrow, Modin, Dask ou cuDF (GPU).

Dois princípios de projeto tornam a biblioteca especialmente útil para engenharia de dados:

  1. Backend-agnóstico com retorno fiel ao tipo. Se você entrou com um DataFrame do pandas, recebe pandas de volta. Se entrou com Polars, recebe Polars. A biblioteca não te empurra para um formato único.
  2. Zero dependências obrigatórias. O Narwhals importa preguiçosamente o backend que ele encontra no ponto de chamada. Um projeto que embarca Narwhals não puxa pandas, Polars ou PyArrow a menos que um deles já esteja instalado. Isso mantém sua árvore de dependências enxuta.

O padrão de uso em 3 passos

O fluxo canônico do Narwhals segue sempre a mesma forma:

import narwhals as nw

def limpa(df):
    # 1) Envolve qualquer DataFrame nativo em um objeto Narwhals
    df = nw.from_native(df)

    # 2) Escreve a lógica com a API Narwhals (estilo Polars)
    out = (
        df.filter(nw.col("valor") > 0)
          .with_columns(nw.col("nome").str.to_uppercase())
    )

    # 3) Devolve o tipo nativo original (pandas -> pandas, etc.)
    return out.to_native()

A mesma função limpa() agora funciona com qualquer um destes:

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

limpa(pd.read_parquet("vendas.parquet"))        # retorna pandas.DataFrame
limpa(pl.read_parquet("vendas.parquet"))        # retorna polars.DataFrame
limpa(pa.parquet.read_table("vendas.parquet"))  # retorna pyarrow.Table

Nenhuma linha de lógica muda entre os três casos. É esse o ganho.

Um exemplo um pouco mais real: agregação

Um group_by também é escrito uma vez só:

import narwhals as nw

@nw.narwhalify        # decorator: faz from_native/to_native automaticamente
def receita_por_uf(df):
    return (
        df.group_by("uf")
          .agg(nw.col("valor").sum().alias("receita"))
          .sort("receita", descending=True)
    )

O decorator @nw.narwhalify dispensa você de chamar from_native/to_native manualmente — ele envolve os argumentos de entrada e desembrulha a saída para você.

Por que isso importa na prática

  • Menos código para manter. Uma base de transformações em vez de uma por biblioteca. Para times que mantêm utilitários internos, é a diferença entre manter um código ou manter cinco.
  • Sem lock-in de biblioteca. Você pode começar em pandas e migrar partes críticas para Polars sem reescrever a lógica de negócio — só troca o DataFrame de entrada.
  • Overhead mínimo. O Narwhals é essencialmente uma tradução fina para a API nativa; ele não reimplementa o motor de execução. Você continua com a performance do backend escolhido (o Polars continua rápido, o cuDF continua na GPU).
  • Não é aposta arriscada. Ferramentas amplamente usadas já adotaram o Narwhals internamente para aceitar qualquer DataFrame sem forçar conversão para pandas: Plotly Express, Altair, scikit-learn, marimo, shiny e o Hugging Face datasets. Quando bibliotecas desse porte apostam numa camada de compatibilidade, é sinal de que o problema é real e a solução é sólida.

Quando (não) usar

O Narwhals é, por design, uma ferramenta voltada a quem constrói ferramentas — autores de bibliotecas e engenheiros que escrevem utilitários reaproveitados por vários projetos ou vários backends. Se o seu projeto é uma aplicação única que já padronizou em Polars e nunca vai sair de Polars, escrever direto na API do Polars é perfeitamente adequado — você não ganha muito adicionando uma camada.

O valor aparece quando a entrada é incerta ou plural: uma função pública que pode receber pandas ou Polars, um pipeline que consome de fontes diferentes, uma biblioteca interna que não quer impor um backend aos consumidores. Nesses cenários, o Narwhals paga o próprio custo com folga.

Como começar

pip install narwhals
# ou, se você usa uv:
uv add narwhals

Sem dependências extras: instale só os backends que já usa. A documentação oficial traz o guia de início rápido e a lista completa de operações suportadas.

Artigos relacionados

Gostou? Veja os e-books para conteúdo aprofundado.

E-books