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.
Install & Compatibility
Where this runs
tested against v2.22.1 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.925 runs
installs and imports cleanly · install 0.0s · import 0.152s · 22.3MB
glibcpy 3.10–3.925 runs
installs and imports cleanly · install 1.9s · import 0.154s · 23MB
21MB installed
● package 21MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
narwhals
✓ import narwhals as nw
IntoFrameT
✓ from narwhals.typing import IntoFrameT
Useful for type hinting dataframe-agnostic functions
stable.v1
✓ import narwhals.stable.v1 as nw_stable
Use this for long-term API stability in library development, though the main 'narwhals' namespace is more actively developed for new features.
This quickstart demonstrates how to write a dataframe-agnostic function using Narwhals. The function `process_data` accepts any supported native dataframe type (e.g., pandas DataFrame, Polars DataFrame) via `nw.from_native`, applies a group-by and aggregation operation using Narwhals' Polars-like API, and then converts the result back to the original native dataframe type using `to_native()`.
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)
Debug
Known issues
gotchaNarwhals 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.fixInstall the desired backend libraries, e.g., `pip install pandas polars pyarrow`.
affects: All versions
breakingThe 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.fixFor production code or libraries, use `import narwhals.stable.v1 as nw` and adhere to its API. For experimentation, the main `narwhals` namespace is acceptable.
affects: All versions, for code not using `narwhals.stable.v1`
gotchaNarwhals 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.fixRefer 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.
affects: All versions
gotchaNarwhals 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.fixIdentify 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.
affects: All versions
deprecatedExternal 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.fixKeep 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.
affects: V2.10.2 onwards (intermittent fixes)
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'narwhals'
The `narwhals` library is not installed in your Python environment.
fixInstall the library using pip: `pip install narwhals`
narwhals.exceptions.ColumnNotFoundError: Column 'column_name' not found.
An operation was attempted on a column name that does not exist in the DataFrame or LazyFrame.
fixVerify the column name for typos or confirm its existence using `df.columns` or `df.collect().columns` (for LazyFrames) to inspect available columns.
TypeError: Expected an object which can be converted into an expression, got <class 'int'>
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`.
fixWrap 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`).
AttributeError: 'DataFrame' object has no attribute 'some_polars_method'
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.
fixRefer 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.
Audit
Dependencies
pandasoptionalCommon backend for Narwhals compatibility
polarsoptionalPrimary API inspiration and common backend for Narwhals compatibility
pyarrowoptionalCommon backend for Narwhals compatibility