Convert Pandas code to Narwhals automatically.
nw-migrate parses your Python files, renames Pandas method calls to their Narwhals equivalents, adds @nw.narwhalify decorators, and flags operations that need manual review.
pip install nw-migrateOr with uv:
uv add nw-migrateOr run without installing via uvx:
uvx nw-migrate check src/Check what can be converted (no changes made):
nw-migrate check src/src/pipeline.py:12:4 [EASY] sort_values() -> sort()
src/pipeline.py:28:4 [EASY] merge() -> join()
src/pipeline.py:45:8 [MEDIUM] fillna() -> with_columns(nw.col(...).fill_null()) -- needs manual review
src/pipeline.py:52:8 [HARD] apply() has no direct Narwhals equivalent
Found 4 convertible Pandas usage(s).
Convert files in-place:
nw-migrate convert src/pipeline.pyPreview changes as a diff:
nw-migrate convert src/pipeline.py --diff# Before
import pandas as pd
def clean_data(df):
result = df.sort_values('date')
result = result.drop(columns=['temp', 'debug'])
result = result.rename(columns={'old_name': 'new_name'})
return result.drop_duplicates()# After
import narwhals as nw
@nw.narwhalify
def clean_data(df):
result = df.sort('date')
result = result.drop('temp', 'debug')
result = result.rename({'old_name': 'new_name'})
return result.unique()The converted function now works with Pandas, Polars, PyArrow, cuDF, and Modin — no code changes needed.
| Pandas | Narwhals |
|---|---|
df.sort_values('col') |
df.sort('col') |
df.groupby('col') |
df.group_by('col') |
df.merge(other, on='id') |
df.join(other, on='id') |
df.drop_duplicates() |
df.unique() |
df.drop(columns=['a']) |
df.drop('a') |
df.rename(columns={...}) |
df.rename({...}) |
df[['col1', 'col2']] |
df.select(['col1', 'col2']) |
ascending=False is automatically converted to descending=True.
These get a # TODO(nw-migrate) comment:
fillna, isna, notna, astype, melt, value_counts
These require switching to Narwhals' expression-based API and can't be mechanically renamed.
These have no Narwhals equivalent:
apply, iterrows, itertuples, query, select_dtypes, applymap
- Parses your code with libcst (preserves formatting, comments, whitespace)
- Collects all Pandas method calls matching conversion rules
- Renames methods and transforms arguments for EASY cases
- Adds
@nw.narwhalifydecorator to functions that received conversions - Adds
import narwhals as nwat the top - Injects TODO comments for MEDIUM/HARD cases
repos:
- repo: https://github.com/jannik-cas/nw-migrate
rev: v0.1.0
hooks:
- id: nw-migrate-check- No type inference — converts any matching method call regardless of whether the receiver is actually a DataFrame. Review the output.
- Does not handle
pd.merge()(free function), onlydf.merge()(method call). - Does not convert
pd.concat()tonw.concat(). - Chained calls are converted independently — a chain mixing easy and hard operations will partially convert.
See CONTRIBUTING.md for development setup and guidelines.