Narwhals Python là lớp tương thích siêu nhẹ giúp bạn viết một đoạn code DataFrame duy nhất chạy được trên Pandas, Polars, PyArrow, Modin, cuDF, DuckDB, PySpark và Dask, mà không cần phụ thuộc vào bất kỳ thư viện nào trong số đó. Phát hành lần đầu vào tháng 2 năm 2024 bởi Marco Gorelli, Narwhals 2.16 (tháng 5/2026) đã trở thành dependency của Plotly, Altair, Marimo và Py-Shiny, biến nó thành "Esperanto" của hệ sinh thái dữ liệu Python. Honestly, khi mình ship một thư viện visualization nội bộ năm ngoái, đây là thứ duy nhất giúp mình không phải duy trì hai bản code song song.
Narwhals 2.16 (phát hành 16/05/2026) yêu cầu Python ≥ 3.10 và có zero dependencies. Nó chỉ dùng các thư viện DataFrame mà người dùng truyền vào.
Cú pháp Narwhals là một tập con của Polars API, vì vậy nếu bạn đã biết Polars thì gần như không cần học lại.
Narwhals giữ nguyên kiểu dữ liệu gốc: vào Pandas thì ra Pandas, vào Polars thì ra Polars. Không có chuyển đổi ngầm tốn bộ nhớ.
Trên benchmark 200 triệu hàng, Narwhals + Polars vẫn nhanh hơn Pandas thuần khoảng 3,3 lần dù có thêm lớp wrapper.
Đối tượng chính: tác giả thư viện (Plotly, Altair, Marimo, Bokeh) cần nhận nhiều loại DataFrame mà không buộc người dùng cài thêm dependency.
Hỗ trợ đầy đủ Pandas, Polars, PyArrow, Modin, cuDF; hỗ trợ chế độ lazy cho DuckDB, Dask, PySpark, Ibis, SQLFrame, Daft.
Narwhals là gì và vấn đề nó giải quyết
Narwhals là một compatibility layer được viết hoàn toàn bằng Python thuần, đóng vai trò "trình dịch" giữa code phân tích dữ liệu và engine DataFrame thực thi nó. Thay vì viết hai phiên bản hàm (một cho Pandas, một cho Polars), bạn viết một hàm duy nhất bằng cú pháp Polars-subset của Narwhals, và để Narwhals điều phối engine bên dưới.
Vấn đề thực tế mà các tác giả thư viện gặp phải rất rõ ràng. Polars hiện chiếm khoảng 30% thị phần dataframe trong các dự án mới, Pandas vẫn thống trị data science truyền thống, PyArrow trở thành chuẩn trao đổi dữ liệu nội bộ, còn cuDF/Modin đang phát triển nhanh trong môi trường GPU và phân tán. Một thư viện trực quan hóa như Plotly không thể chỉ hỗ trợ Pandas, vì họ sẽ mất nửa cộng đồng người dùng. Nhưng cũng không thể import cả 5 thư viện làm dependency, bởi vì như vậy sẽ phá vỡ wheel size, tăng thời gian cài đặt và gây xung đột phiên bản.
Narwhals giải bài toán đó bằng cách định nghĩa một subset API ổn định đủ rộng để xử lý hầu hết các thao tác phân tích (groupby, join, filter, agg, window function, expression), nhưng đủ hẹp để có thể ánh xạ chính xác sang mọi backend hỗ trợ.
Cài đặt Narwhals 2.16 và tương thích phiên bản 2026
Việc cài đặt Narwhals đơn giản đến mức gần như không có gì để cấu hình:
pip install narwhals
# hoặc dùng uv (khuyến nghị cho Python 2026)
uv pip install narwhals
Phiên bản 2.16 phát hành ngày 16/05/2026 yêu cầu Python ≥ 3.10. Đây là một điểm rất khác biệt so với hầu hết thư viện khoa học dữ liệu: Narwhals khai báo zero required dependencies trong pyproject.toml. Bạn chỉ cần cài thêm những backend bạn thực sự sử dụng. Nếu bạn truyền vào một pandas.DataFrame, Narwhals sẽ tìm Pandas tại runtime; nếu bạn không bao giờ chạy code trên Polars thì không cần cài Polars.
Để xác nhận phiên bản và kiểm tra backend khả dụng, dùng đoạn code sau:
import narwhals as nw
print(nw.__version__) # 2.16.0
print(nw.dependencies.get_pandas()) # <module 'pandas'> hoặc None
print(nw.dependencies.get_polars()) # <module 'polars'> hoặc None
Lưu ý quan trọng: nếu bạn cần xuất kết quả sang SQL (qua phương thức .to_sql() trên LazyFrame), Narwhals sẽ yêu cầu thêm DuckDB. Đây là dependency duy nhất bắt buộc cho một tính năng cụ thể.
Cách Narwhals hoạt động: from_native, to_native và stable API
Triết lý của Narwhals nằm gọn trong hai hàm: nw.from_native() bọc một DataFrame bản địa thành đối tượng Narwhals, và nw.to_native() trả về kiểu gốc sau khi xử lý. Ở giữa là toàn bộ pipeline biểu thức (expression-based) lấy cảm hứng từ Polars.
Hàm này hoạt động không cần sửa đổi với pandas.DataFrame, polars.DataFrame, polars.LazyFrame hoặc pyarrow.Table. Điểm cốt lõi là stable namespace: từ phiên bản v1 trở đi, Narwhals cam kết không bao giờ xóa hoặc thay đổi hành vi của các hàm public. Ngay cả khi Polars thay đổi cú pháp ở phiên bản tương lai, code của bạn vẫn chạy.
Nếu bạn đã đọc bài Pandas 3.0 migration guide, bạn sẽ thấy stable namespace là cách bảo vệ thư viện của bạn khỏi mọi breaking change ở downstream. Đó là một bảo đảm mà ít thư viện nào trong hệ Python dám đưa ra.
Ví dụ thực tế: một hàm chạy trên Pandas, Polars và PyArrow
So, hãy xem một tình huống mình từng gặp khi làm tool phân tích log đơn hàng cho team marketing. Bạn cần hỗ trợ cả ba backend phổ biến (vì team data dùng Polars còn team product analytics quen Pandas). Đây là một hàm hoàn chỉnh với phân nhóm thời gian, lọc và tính tỷ lệ:
import narwhals as nw
@nw.narwhalify
def ti_le_huy_theo_thang(df, nam: int):
return (
df.filter(nw.col("trang_thai").is_in(["completed", "cancelled"]))
.with_columns(
thang=nw.col("ngay_dat").dt.month(),
la_huy=(nw.col("trang_thai") == "cancelled").cast(nw.Int32),
)
.filter(nw.col("ngay_dat").dt.year() == nam)
.group_by("thang")
.agg(
tong_don=nw.len(),
don_huy=nw.col("la_huy").sum(),
)
.with_columns(
ti_le_huy=nw.col("don_huy") / nw.col("tong_don")
)
.sort("thang")
)
# Sử dụng với 3 backend khác nhau
import pandas as pd
import polars as pl
import pyarrow as pa
df_pd = pd.read_csv("don_hang.csv", parse_dates=["ngay_dat"])
df_pl = pl.read_csv("don_hang.csv", try_parse_dates=True)
df_pa = pa.csv.read_csv("don_hang.csv")
print(ti_le_huy_theo_thang(df_pd, 2026)) # trả về pandas.DataFrame
print(ti_le_huy_theo_thang(df_pl, 2026)) # trả về polars.DataFrame
print(ti_le_huy_theo_thang(df_pa, 2026)) # trả về pyarrow.Table
Decorator @nw.narwhalify tự động xử lý cặp from_native/to_native, biến code trở nên ngắn gọn như khi bạn viết Polars thuần. Quan trọng hơn, bạn có thể viết unit test một lần và chạy với pytest parametrize trên cả ba backend, đảm bảo logic giống hệt nhau trên mọi engine. Mình hit cái bug datetime parsing chỉ xuất hiện trên PyArrow trong lần đầu thử, và chính cách test multi-backend này phát hiện ra nó trước khi release.
Lazy evaluation, GPU và backend SQL (DuckDB, PySpark, cuDF)
Narwhals chia backend thành hai nhóm. Nhóm full API gồm Pandas, Polars (eager + lazy), PyArrow, Modin và cuDF hỗ trợ toàn bộ API. Nhóm lazy-only gồm Daft, Dask, DuckDB, Ibis, PySpark và SQLFrame, chỉ hỗ trợ các thao tác có thể được tối ưu hóa tại query planner.
Trên backend GPU, cuDF cho phép pipeline Narwhals chạy hoàn toàn trên RAM thiết bị NVIDIA mà không thay đổi một dòng code nào:
import cudf
import narwhals as nw
df_gpu = cudf.read_parquet("logs_2026.parquet")
ket_qua = ti_le_huy_theo_thang(df_gpu, 2026)
# ket_qua là cudf.DataFrame, đã tính toán trên GPU
Với DuckDB, Narwhals chỉ hoạt động ở chế độ lazy, nghĩa là bạn xây query plan rồi gọi .collect() để thực thi. Đây là sự kết hợp khá mạnh nếu bạn đã đọc hướng dẫn DuckDB Python 2026. Narwhals cho phép tái sử dụng cùng một pipeline phân tích cho DuckDB (xử lý file Parquet 100GB) và Pandas (xử lý DataFrame trong notebook), không cần viết SQL riêng.
Benchmark hiệu năng: Narwhals có chậm hơn Polars hoặc Pandas không?
Câu hỏi quan trọng nhất với mọi compatibility layer là overhead. Benchmark công khai trên pipeline join + filter + groupby + aggregation chạy trên tập dữ liệu 200 triệu hàng (~3GB) cho kết quả:
Pipeline
Thời gian
Tăng tốc so với Pandas
Pandas thuần
~33,9 giây
1,0×
Polars thuần
~7,57 giây
4,5×
Narwhals + Polars
~10,42 giây
3,3×
Narwhals + Pandas
~34,5 giây
~1,0×
Narwhals + cuDF (A100 GPU)
~1,1 giây
~31×
Overhead của Narwhals so với Polars thuần là khoảng 30–40%, do mỗi lệnh phải đi qua một lớp wrapper Python. Tuy nhiên, vì heavy lifting vẫn diễn ra bên trong engine bản địa (Polars Rust core, Arrow compute kernel, cuDF CUDA), overhead này không tăng tuyến tính theo kích thước dữ liệu. Nó là chi phí cố định ở tầng dispatch. Với tập dữ liệu lớn, overhead này gần như biến mất so với thời gian compute.
Quan trọng hơn: Narwhals + Polars vẫn nhanh gấp 3,3 lần Pandas thuần. Đó là tốc độ "miễn phí" mà người dùng thư viện của bạn nhận được chỉ bằng cách cài Polars trong môi trường của họ, không cần thay đổi code ứng dụng.
Khi nào nên dùng Narwhals và khi nào không?
Narwhals được thiết kế chính cho tác giả thư viện, không phải cho data analyst end-user. Hãy dùng Narwhals khi:
Bạn đang xây thư viện công khai (plotting, statistics, ML utility) và cần nhận nhiều loại DataFrame làm input.
Bạn đang xây internal data platform phục vụ nhiều team với stack khác nhau (team A dùng Pandas, team B dùng Polars).
Bạn muốn viết unit test một lần và chạy trên nhiều backend để kiểm tra tính nhất quán.
Bạn cần "future-proof" code khi Polars hoặc Pandas có breaking change. Stable namespace của Narwhals bảo vệ bạn.
Ngược lại, không cần Narwhals nếu:
Bạn viết script phân tích cá nhân và chỉ dùng một backend duy nhất. Dùng trực tiếp Polars hoặc Pandas đơn giản hơn.
Bạn cần các tính năng đặc thù của một backend (ví dụ pl.scan_pyarrow_dataset của Polars, hoặc Pandas extension array). Narwhals chỉ expose tập con chung.
Hiệu năng cuối cùng quan trọng đến mức 30% overhead là không chấp nhận được. Nhưng nhớ rằng trường hợp này hiếm gặp với dữ liệu lớn.
Một chú ý hữu ích: Narwhals không phải là engine mới. Nó không thay thế Polars, không cạnh tranh với Pandas. Hãy xem nó như typing.Protocol cho DataFrame, một giao diện chuẩn.
Thư viện nào đang dùng Narwhals trong sản phẩm?
Tính đến tháng 5/2026, Narwhals đã trở thành dependency của nhiều thư viện cốt lõi trong hệ sinh thái Python data:
Plotly: Plotly Express tích hợp Narwhals từ phiên bản 5.24 để chấp nhận đầu vào Polars/PyArrow mà không cần copy sang Pandas.
Altair: Sử dụng Narwhals từ phiên bản 5.4 để xử lý DataFrame và LazyFrame một cách thống nhất.
Marimo: Reactive notebook (xem bài về Marimo) dùng Narwhals để render bảng và biểu đồ từ mọi DataFrame.
Py-Shiny: Framework dashboard tương tác của Posit dựa trên Narwhals cho table widget.
Bokeh, Vegafusion, Great Tables: Đều adopt Narwhals trong 2025-2026.
Theo thống kê GitHub đầu năm 2026, Narwhals có khoảng 1.500 sao, 110 contributor và 230 release. Một tốc độ phát triển nhanh chóng cho một dự án chỉ mới 2 năm tuổi. Marco Gorelli (core dev Pandas và cựu Polars) duy trì dự án 100% độc lập, không thuộc công ty nào.
Câu hỏi thường gặp
Narwhals là gì và tại sao tôi cần dùng nó?
Narwhals là một thư viện compatibility layer của Python cho phép viết code DataFrame duy nhất chạy được trên Pandas, Polars, PyArrow, Modin, cuDF, DuckDB và PySpark mà không cần phụ thuộc vào chúng. Bạn cần dùng nó nếu xây thư viện công khai phải nhận nhiều loại DataFrame, hoặc muốn future-proof pipeline khỏi breaking change của backend.
Narwhals có thay thế Polars hoặc Pandas không?
Không. Narwhals không phải là engine xử lý dữ liệu, nó chỉ là lớp dịch. Toàn bộ compute vẫn diễn ra bên trong Pandas, Polars hoặc backend bạn chọn. Narwhals chỉ định nghĩa giao diện chuẩn để code của bạn có thể hoạt động với cả ba.
Narwhals có chậm hơn Pandas hoặc Polars khi chạy không?
Với Pandas backend, overhead gần như bằng 0 (~2%). Với Polars, overhead khoảng 30–40% do phải đi qua Python wrapper layer, nhưng Narwhals + Polars vẫn nhanh gấp 3,3 lần Pandas thuần trên benchmark 200 triệu hàng. Với dữ liệu rất lớn, overhead cố định này gần như không đáng kể.
Narwhals có hỗ trợ lazy evaluation và GPU không?
Có. Narwhals hỗ trợ đầy đủ lazy evaluation qua Polars LazyFrame, Dask, DuckDB, PySpark và Ibis. Cho GPU, Narwhals tương thích hoàn toàn với NVIDIA cuDF, cho phép pipeline Narwhals chạy 100% trên GPU mà không thay đổi code.
Khi nào tôi không nên dùng Narwhals?
Không cần dùng Narwhals nếu bạn viết script cá nhân chỉ dùng một backend, nếu bạn cần tính năng đặc thù của một thư viện (như Pandas ExtensionArray hoặc Polars streaming engine), hoặc nếu pipeline của bạn dùng các thao tác chưa nằm trong Narwhals API subset.
Học Narwhals có khó không nếu tôi chỉ biết Pandas?
Hơi tốn thời gian ban đầu vì cú pháp Narwhals là tập con của Polars chứ không phải Pandas. Bạn cần làm quen với expression-based API (nw.col("x") + nw.col("y")) thay vì style indexing kiểu Pandas. Tuy nhiên, một khi đã quen Polars hoặc Narwhals, bạn sẽ thấy code dễ đọc và dễ test hơn nhiều.
Priya is a senior data engineer with 11 years building analytics platforms, most recently at Stripe where she led the migration of the merchant analytics pipeline from pandas to polars (cut p95 batch latency from 42 minutes to under 6). Before Stripe she spent four years at Mode Analytics writing the query engine that powered customer dashboards, and two years at Etsy on the seller-insights team.
She writes mainly about polars internals, lazy evaluation patterns, and the practical edges of moving production pandas code to polars without breaking analyst muscle memory. Her side project is a 12k-row benchmark suite comparing pandas 2.x, polars, and DuckDB across realistic e-commerce joins.
Priya lives in Oakland, mentors through Women in Data, and is slowly learning to play go.
Pandera là thư viện validation DataFrame thống kê cho Python, hỗ trợ pandas, Polars, PySpark và Modin. Bài viết phân tích phiên bản 0.29 (1/2026): cú pháp DataFrameModel, decorator @check_types, tích hợp Polars LazyFrame và pattern pipeline ETL production.
Marimo 2026 — notebook Python reactive thay thế Jupyter. Hướng dẫn cài đặt, dùng widget mo.ui, cache tính toán, migrate từ .ipynb và deploy thành app web, kèm code chạy được.
Hướng dẫn toàn diện DuckDB 1.5.2 với Pandas và Polars trong Python 2026: truy vấn SQL zero-copy trên DataFrame, đọc Parquet/CSV trực tiếp, benchmark hiệu năng và quy trình hybrid tối ưu.