Narwhals
raw JSON → 2.18.1 verified Tue May 12 auth: no python install: verified quickstart: stale
Narwhals is an extremely lightweight and extensible compatibility layer between dataframe libraries. It provides a unified API, largely mirroring the Polars API, enabling users to write dataframe-agnostic code that works across various backends such as pandas, Polars, cuDF, PyArrow, Dask, DuckDB, Ibis, PySpark, and SQLFrame. It is currently at version 2.18.1 and maintains an active development cycle with frequent releases, often including weekly or bi-weekly updates for bug fixes and minor enhancements.
pip install narwhals Common errors
error ModuleNotFoundError: No module named 'narwhals' ↓
cause The `narwhals` library is not installed in your Python environment.
fix
Install the library using pip:
pip install narwhals error narwhals.exceptions.ColumnNotFoundError: Column 'column_name' not found. ↓
cause An operation was attempted on a column name that does not exist in the DataFrame or LazyFrame.
fix
Verify the column name for typos or confirm its existence using
df.columns or df.collect().columns (for LazyFrames) to inspect available columns. error TypeError: Expected an object which can be converted into an expression, got <class 'int'> ↓
cause Narwhals' expression-based API requires explicit wrapping of literal values (e.g., integers, strings) with `nw.lit()` or column selections by index with `nw.col()` in operations like `select` or `with_columns`.
fix
Wrap literal values with
nw.lit() (e.g., nw.lit(0) instead of 0) and select columns by index or non-string names with nw.col() (e.g., nw.col(0) instead of 0). error AttributeError: 'DataFrame' object has no attribute 'some_polars_method' ↓
cause Narwhals implements a subset of the Polars API. The specific method or attribute being called exists in the native Polars library but is not currently exposed or supported by the Narwhals compatibility layer.
fix
Refer to the Narwhals documentation for supported API features. If the functionality is critical and unavailable, convert to the native backend using
.to_native() to perform the operation, and then wrap the result back with nw.from_native() if further Narwhals operations are needed. Warnings
gotcha Narwhals is a compatibility layer and does not provide dataframe functionality itself. You must install the underlying dataframe libraries (e.g., `pandas`, `polars`, `pyarrow`) separately for Narwhals to function with those backends. Not installing them will lead to `ModuleNotFoundError` or `TypeError` when `from_native` is used. ↓
fix Install the desired backend libraries, e.g., `pip install pandas polars pyarrow`.
breaking The main `narwhals` namespace may undergo breaking changes, deprecations, or API shifts in new releases. For critical library development requiring long-term stability, prefer `import narwhals.stable.v1 as nw_stable`. This stable API is promised to never change or remove public functions. Future stable versions (e.g., `v2`, `v3`) will be introduced if breaking changes are necessary. ↓
fix For production code or libraries, use `import narwhals.stable.v1 as nw` and adhere to its API. For experimentation, the main `narwhals` namespace is acceptable.
gotcha Narwhals implements a *subset* of the Polars API. Not all Polars functions, arguments, or behaviors are necessarily supported or identically replicated across all backends. Complex or less common Polars operations might not be available or might behave differently in specific backend implementations. Always consult the official Narwhals API completeness documentation. ↓
fix Refer to the Narwhals documentation for supported API elements. If a specific Polars feature is critical and not supported, you may need to handle that backend natively or contribute to Narwhals.
gotcha Narwhals preserves the eager/lazy execution model of the underlying dataframe. If you pass a lazy frame (e.g., Polars LazyFrame, Dask DataFrame), operations remain lazy. Explicitly call `.collect()` when an eager result is required, especially before operations like `.shape` or `.pivot()`, which necessitate materializing the data. Failing to do so can lead to errors or unexpected behavior. ↓
fix Identify points in your code where eager computation is implicitly or explicitly needed and call `.collect()` on your Narwhals LazyFrame. Alternatively, use `eager_only=True` in `nw.from_native` if you always expect an eager frame.
deprecated External library deprecations can affect Narwhals. For example, DuckDB 1.5 deprecated `fetch_arrow_table`. While Narwhals strives to adapt, relying on specific backend versions might expose you to these upstream changes. This is particularly relevant for `when/then` conditions, `join` operations, and null value handling across different backends, which have seen several fixes in recent versions. ↓
fix Keep Narwhals updated to benefit from fixes addressing upstream deprecations and inconsistencies. Thoroughly test your code across different backend versions and with various null value scenarios.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.14s 22.2M
3.10 slim (glibc) - - 0.13s 23M
3.11 alpine (musl) - - 0.21s 24.8M
3.11 slim (glibc) - - 0.24s 25M
3.12 alpine (musl) - - 0.17s 16.5M
3.12 slim (glibc) - - 0.24s 17M
3.13 alpine (musl) - - 0.15s 16.1M
3.13 slim (glibc) - - 0.20s 17M
3.9 alpine (musl) - - 0.13s 21.7M
3.9 slim (glibc) - - 0.14s 22M
Imports
- narwhals
import narwhals as nw - IntoFrameT
from narwhals.typing import IntoFrameT - stable.v1
import narwhals.stable.v1 as nw_stable
Quickstart stale last tested: 2026-04-23
import narwhals as nw
import pandas as pd
import polars as pl
from narwhals.typing import IntoFrameT
def process_data(df_native: IntoFrameT) -> IntoFrameT:
df = nw.from_native(df_native)
result = (
df.group_by(nw.col('category'))
.agg(nw.col('value').mean().alias('mean_value'))
.sort('mean_value', descending=True)
)
return result.to_native()
# Example with pandas
pd_df = pd.DataFrame({'category': ['A', 'B', 'A', 'C'], 'value': [10, 20, 15, 25]})
pd_result = process_data(pd_df)
print('Pandas Result:')
print(pd_result)
# Example with polars
pl_df = pl.DataFrame({'category': ['A', 'B', 'A', 'C'], 'value': [10, 20, 15, 25]})
pl_result = process_data(pl_df)
print('\nPolars Result:')
print(pl_result)