Lightweight and extensible compatibility layer between dataframe libraries!
See the codeExtremely lightweight and extensible compatibility layer between dataframe libraries!
Seamlessly support all, without depending on any!
Get started!
pip install narwhals
conda install -c conda-forge narwhals
There are three steps to writing dataframe-agnostic code using Narwhals:
use narwhals.from_native to wrap a pandas/Polars/Modin/cuDF/PyArrow
DataFrame/LazyFrame in a Narwhals class
use narwhals.to_native to return an object to the user in its original
dataframe flavour. For example:
Narwhals allows you to define dataframe-agnostic functions. For example:
import narwhals as nw
from narwhals.typing import IntoDataFrameT, IntoLazyFrameT
def agnostic_function(
df_native: IntoDataFrameT | IntoLazyFrameT,
) -> IntoDataFrameT | IntoLazyFrameT:
return (
nw.from_native(df_native)
.with_columns(
category=nw.when(nw.col("animal").str.contains("whale"))
.then(nw.lit("whale"))
.otherwise(nw.lit("other"))
)
.to_native()
)
You can then pass pandas.DataFrame, polars.DataFrame, polars.LazyFrame, duckdb.DuckDBPyRelation,
pyspark.sql.DataFrame, pyarrow.Table, and more, to agnostic_function. In each case, no additional
dependencies will be required, and computation will stay native to the input library:
import duckdb
import polars as pl
import pandas as pd
data = {
"animal": ["blue whale", "orca", "dolphin", "humpback whale", "seal"],
"length_m": [30.0, 8, 2.5, 17, 2.2],
"weight_kg": [150000, 4000, 200, 30000, 85],
}
print("Polars result")
df_pl = pl.DataFrame(data)
print(agnostic_function(df_pl))
print("DuckDB result")
print(agnostic_function(duckdb.sql("select * from df_pl")))
print("pandas result")
df_pd = pd.DataFrame(data)
print(agnostic_function(df_pd))
Polars result
shape: (5, 4)
┌────────────────┬──────────┬───────────┬──────────┐
│ animal ┆ length_m ┆ weight_kg ┆ category │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ i64 ┆ str │
╞════════════════╪══════════╪═══════════╪══════════╡
│ blue whale ┆ 30.0 ┆ 150000 ┆ whale │
│ orca ┆ 8.0 ┆ 4000 ┆ other │
│ dolphin ┆ 2.5 ┆ 200 ┆ other │
│ humpback whale ┆ 17.0 ┆ 30000 ┆ whale │
│ seal ┆ 2.2 ┆ 85 ┆ other │
└────────────────┴──────────┴───────────┴──────────┘
DuckDB result
┌────────────────┬──────────┬───────────┬──────────┐
│ animal │ length_m │ weight_kg │ category │
│ varchar │ double │ int64 │ varchar │
├────────────────┼──────────┼───────────┼──────────┤
│ blue whale │ 30.0 │ 150000 │ whale │
│ orca │ 8.0 │ 4000 │ other │
│ dolphin │ 2.5 │ 200 │ other │
│ humpback whale │ 17.0 │ 30000 │ whale │
│ seal │ 2.2 │ 85 │ other │
└────────────────┴──────────┴───────────┴──────────┘
pandas result
animal length_m weight_kg category
0 blue whale 30.0 150000 whale
1 orca 8.0 4000 other
2 dolphin 2.5 200 other
3 humpback whale 17.0 30000 whale
4 seal 2.2 85 other
See the tutorial for several examples!
If you said yes to both, we'd love to hear from you!
See roadmap discussion on GitHub for an up-to-date plan of future work.
Join the party!
Feel free to add your project to the list if it's missing, and/or chat with us on Discord if you'd like any support.
Narwhals is 100% independent, community-driven, and community-owned. We are extremely grateful to the following organisations for having provided some funding / development time:
If you contribute to Narwhals on your organization's time, please let us know. We'd be happy to add your employer to this list!
If you'd like to say "thank you", please give us a ⭐ star ⭐.
Please contact hello_narwhals@proton.me if you would like to:
Narwhals has been featured in several talks, podcasts, and blog posts:
Inspiring Computing Podcast The Rise of Narwhals in Open-Source
PyCon DE & PyData 2025 How Narwhals is silently bringing pandas, Polars, DuckDB, PyArrow, and more together
The Python Exchange March 2025 What Can Narwhals Do for You?
PyData London 2025 How Narwhals brings Polars, DuckDB, PyArrow, & pandas together
Talk Python to me Podcast Ahoy, Narwhals are bridging the data science APIs
Python Bytes Podcast Episode 402, topic #2
Super Data Science: ML & AI Podcast Narwhals: For Pandas-to-Polars DataFrame Compatibility
Sample Space Podcast | probabl How Narwhals has many end users ... that never use it directly. - Marco Gorelli
The Real Python Podcast Narwhals: Expanding DataFrame Compatibility Between Libraries
Pycon Lithuania 2024 Marco Gorelli - DataFrame interoperatiblity - what's been achieved, and what comes next?
Pycon Italy 2024 How you can write a dataframe-agnostic library - Marco Gorelli
Polars Blog Post Polars has a new lightweight plotting backend
Quansight Labs blog post (w/ Scikit-Lego) How Narwhals and scikit-lego came together to achieve dataframe-agnosticism
Thanks to Olha Urdeichuk for the illustration!
(top 24 of 26)
2,189 followers · starred Sep 2024
107 followers · starred Jun 2026
986 followers · starred Dec 2025
155 followers · starred Jan 2025
Lightweight and extensible compatibility layer between dataframe libraries!
See the codeExtremely lightweight and extensible compatibility layer between dataframe libraries!
Seamlessly support all, without depending on any!
Get started!
pip install narwhals
conda install -c conda-forge narwhals
There are three steps to writing dataframe-agnostic code using Narwhals:
use narwhals.from_native to wrap a pandas/Polars/Modin/cuDF/PyArrow
DataFrame/LazyFrame in a Narwhals class
use narwhals.to_native to return an object to the user in its original
dataframe flavour. For example:
Narwhals allows you to define dataframe-agnostic functions. For example:
import narwhals as nw
from narwhals.typing import IntoDataFrameT, IntoLazyFrameT
def agnostic_function(
df_native: IntoDataFrameT | IntoLazyFrameT,
) -> IntoDataFrameT | IntoLazyFrameT:
return (
nw.from_native(df_native)
.with_columns(
category=nw.when(nw.col("animal").str.contains("whale"))
.then(nw.lit("whale"))
.otherwise(nw.lit("other"))
)
.to_native()
)
You can then pass pandas.DataFrame, polars.DataFrame, polars.LazyFrame, duckdb.DuckDBPyRelation,
pyspark.sql.DataFrame, pyarrow.Table, and more, to agnostic_function. In each case, no additional
dependencies will be required, and computation will stay native to the input library:
import duckdb
import polars as pl
import pandas as pd
data = {
"animal": ["blue whale", "orca", "dolphin", "humpback whale", "seal"],
"length_m": [30.0, 8, 2.5, 17, 2.2],
"weight_kg": [150000, 4000, 200, 30000, 85],
}
print("Polars result")
df_pl = pl.DataFrame(data)
print(agnostic_function(df_pl))
print("DuckDB result")
print(agnostic_function(duckdb.sql("select * from df_pl")))
print("pandas result")
df_pd = pd.DataFrame(data)
print(agnostic_function(df_pd))
Polars result
shape: (5, 4)
┌────────────────┬──────────┬───────────┬──────────┐
│ animal ┆ length_m ┆ weight_kg ┆ category │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ i64 ┆ str │
╞════════════════╪══════════╪═══════════╪══════════╡
│ blue whale ┆ 30.0 ┆ 150000 ┆ whale │
│ orca ┆ 8.0 ┆ 4000 ┆ other │
│ dolphin ┆ 2.5 ┆ 200 ┆ other │
│ humpback whale ┆ 17.0 ┆ 30000 ┆ whale │
│ seal ┆ 2.2 ┆ 85 ┆ other │
└────────────────┴──────────┴───────────┴──────────┘
DuckDB result
┌────────────────┬──────────┬───────────┬──────────┐
│ animal │ length_m │ weight_kg │ category │
│ varchar │ double │ int64 │ varchar │
├────────────────┼──────────┼───────────┼──────────┤
│ blue whale │ 30.0 │ 150000 │ whale │
│ orca │ 8.0 │ 4000 │ other │
│ dolphin │ 2.5 │ 200 │ other │
│ humpback whale │ 17.0 │ 30000 │ whale │
│ seal │ 2.2 │ 85 │ other │
└────────────────┴──────────┴───────────┴──────────┘
pandas result
animal length_m weight_kg category
0 blue whale 30.0 150000 whale
1 orca 8.0 4000 other
2 dolphin 2.5 200 other
3 humpback whale 17.0 30000 whale
4 seal 2.2 85 other
See the tutorial for several examples!
If you said yes to both, we'd love to hear from you!
See roadmap discussion on GitHub for an up-to-date plan of future work.
Join the party!
Feel free to add your project to the list if it's missing, and/or chat with us on Discord if you'd like any support.
Narwhals is 100% independent, community-driven, and community-owned. We are extremely grateful to the following organisations for having provided some funding / development time:
If you contribute to Narwhals on your organization's time, please let us know. We'd be happy to add your employer to this list!
If you'd like to say "thank you", please give us a ⭐ star ⭐.
Please contact hello_narwhals@proton.me if you would like to:
Narwhals has been featured in several talks, podcasts, and blog posts:
Inspiring Computing Podcast The Rise of Narwhals in Open-Source
PyCon DE & PyData 2025 How Narwhals is silently bringing pandas, Polars, DuckDB, PyArrow, and more together
The Python Exchange March 2025 What Can Narwhals Do for You?
PyData London 2025 How Narwhals brings Polars, DuckDB, PyArrow, & pandas together
Talk Python to me Podcast Ahoy, Narwhals are bridging the data science APIs
Python Bytes Podcast Episode 402, topic #2
Super Data Science: ML & AI Podcast Narwhals: For Pandas-to-Polars DataFrame Compatibility
Sample Space Podcast | probabl How Narwhals has many end users ... that never use it directly. - Marco Gorelli
The Real Python Podcast Narwhals: Expanding DataFrame Compatibility Between Libraries
Pycon Lithuania 2024 Marco Gorelli - DataFrame interoperatiblity - what's been achieved, and what comes next?
Pycon Italy 2024 How you can write a dataframe-agnostic library - Marco Gorelli
Polars Blog Post Polars has a new lightweight plotting backend
Quansight Labs blog post (w/ Scikit-Lego) How Narwhals and scikit-lego came together to achieve dataframe-agnosticism
Thanks to Olha Urdeichuk for the illustration!
(top 24 of 26)
2,189 followers · starred Sep 2024
107 followers · starred Jun 2026
986 followers · starred Dec 2025
155 followers · starred Jan 2025