Movatterモバイル変換


[0]ホーム

URL:


— FREE Email Series —

🐍 Python Tricks 💌

Python Tricks Dictionary Merge

🔒 No spam. Unsubscribe any time.

Browse TopicsGuided Learning Paths
Basics Intermediate Advanced
aialgorithmsapibest-practicescareercommunitydatabasesdata-sciencedata-structuresdata-vizdevopsdjangodockereditorsflaskfront-endgamedevguimachine-learningnewsnumpyprojectspythonstdlibtestingtoolsweb-devweb-scraping

Table of Contents

Writing DataFrame-Agnostic Python Code With Narwhals

Writing DataFrame-Agnostic Python Code With Narwhals

byIan EyrePublication date Dec 15, 2025Reading time estimate 24madvanceddata-sciencepython

Table of Contents

Remove ads

Narwhals is intended for Python library developers who need to analyze DataFrames in a range of standard formats, including Polars, pandas, DuckDB, and others. It does this by providing a compatibility layer of code that handles any differences between the various formats.

In this tutorial, you’ll learn how to use the same Narwhals code to analyze data produced by the latest versions of two very common data libraries. You’ll also discover how Narwhals utilizes the efficiencies of your source data’s underlying library when analyzing your data. Furthermore, because Narwhals uses syntax that is a subset of Polars, you can reuse your existing Polars knowledge to quickly gain proficiency with Narwhals.

The table below will allow you to quickly decide whether or not Narwhals is for you:

Use CaseUse NarwhalsUse Another Tool
You need to produce DataFrame-agnostic code.
You want to learn a new DataFrame library.

Whether you’re wondering how to develop a Python library to cope with DataFrames from a range of common formats, or just curious to find out if this is even possible, this tutorial is for you. The Narwhals library could provide exactly what you’re looking for.

Get Your Code:Click here to download the free sample code and data files that you’ll use to work with Narwhals in Python.

Take the Quiz: Test your knowledge with our interactive “Writing DataFrame-Agnostic Python Code With Narwhals” quiz. You’ll receive a score upon completion to help you track your learning progress:


Writing DataFrame-Agnostic Python Code With Narwhals

Interactive Quiz

Writing DataFrame-Agnostic Python Code With Narwhals

If you're a Python library developer wondering how to write DataFrame-agnostic code, the Narwhals library is the solution you're looking for.

Get Ready to Explore Narwhals

Before you start, you’ll need to install Narwhals and have some data to play around with. You should also be familiar with the idea of a DataFrame. Although having an understanding of several DataFrame libraries isn’t mandatory, you’ll find a familiarity withPolars’ expressions and contexts syntax extremely useful. This is because Narwhals’ syntax is based on a subset of Polars’ syntax. However, Narwhals doesn’t replace Polars.

In this example, you’ll use data stored in thepresidentsParquet file included in your downloadable materials.

This file contains the following six fields to describeUnited States presidents:

HeadingMeaning
last_nameThe president’s last name
first_nameThe president’s first name
term_startStart of the presidential term
term_endEnd of the presidential term
party_nameThe president’s political party
centuryCentury the president’s term started

To work through this tutorial, you’ll need to install thepandas,Polars,PyArrow, andNarwhals libraries:

Shell
$python-mpipinstallpandaspolarspyarrownarwhals

A key feature of Narwhals is that it’s DataFrame-agnostic, meaning your code can work with several formats. But you still need both Polars and pandas because Narwhals will use them to process the data you pass to it. You’ll also need them to create your DataFrames to pass to Narwhals to begin with.

You installed the PyArrow library to correctly read the Parquet files. Finally, you installed Narwhals itself.

With everything installed, make sure you create the project’s folder and place your downloadedpresidents.parquet file inside it. You might also like to add both thebooks.parquet andauthors.parquet files as well. You’ll need them later.

With that lot done, you’re good to go!

Understand How Narwhals Works

The documentation describes Narwhals as follows:

Extremely lightweight and extensible compatibility layer between dataframe libraries! (Source)

Narwhals islightweight because it wraps the original DataFrame in its own object ecosystem while still using the source DataFrame’s library to process it. Any data passed into it for processing doesn’t need to be duplicated, removing an otherwise resource-intensive and time-consuming operation.

Narwhals is alsoextensible. For example, you can write Narwhals code to work with the full API of the following libraries:

It also supports the lazy API of the following:

The Narwhals developers are always looking to add more libraries and even encourage you tocontribute your own. In addition, its developers are convinced that extending Narwhals to support new libraries is relatively straightforward.

To understand the high-level architecture of Narwhals, take a look at the diagram below:

diagram showing an overview of narwhals architecture

In this diagram, you’re passing a pandas DataFrame into Narwhals code, although DataFrames orLazyFrames from any of the supported formats will work just as well. Once you pass the pandas DataFrame in, Narwhals encapsulates it for analysis.

To analyze your Narwhals DataFrame, regardless of its original format, you write syntax similar to Polars. The Narwhals wrapper then transforms your analysis code into a library-specific format. In this example, it’ll create the pandas equivalents of the Narwhals expressions that you’re using to analyze the data.

Narwhals then passes these pandas expressions to the original pandas library for processing. The pandas library then uses these expressions to analyze the original data in the usual way. As the diagram shows, the pandas library operates on the original data, meaning that no separate copy is ever made.

Once the data has been analyzed, Narwhals again takes over and passes its results back to you. This will usually be in the same format as the original DataFrame, but it doesn’t have to be. For example, passing a pandas DataFrame in could result in a Polars DataFrame being returned to you. Just make sure the returned format’s library is installed on your system so you can work with it.

One important point to note is that Narwhals supports a subset of the analysis capabilities of standard data analysis libraries such as pandas and Polars. By itself, Narwhals complements their use rather than providing an alternative.

Now that you’ve got the heads-up on what Narwhals offers, it’s time to see it in action.

Write DataFrame-Agnostic Python Code With Narwhals

To use Narwhals, you pass it data in a format it supports. Narwhals then converts the DataFrame or LazyFrame to its own format, analyzes it, and finally returns the result to you in a format of your choice. You’ll now see this workflow in action.

Perform the Same Processing on Multiple Sources

Suppose you want a function that’s equally happy grouping data from either a pandas or Polars DataFrame, or from any other Narwhals-supported format. Create a file nameduniversal_processing.py in your project’s folder, and add in auniversal_groupby_v1() function as shown:

Pythonuniversal_processing.py
importnarwhalsasnwfromnarwhals.typingimportIntoFrameTdefuniversal_groupby_v1(df:IntoFrameT)->IntoFrameT:return(nw.from_native(df).group_by("party_name").agg(nw.col("last_name").count()).sort("party_name").to_native())

To begin with, you import the Narwhals library into your code using its standardnw alias. You’ve also decided to use theIntoFrameT type hint, so you’ve imported that fromnarwhals.typing. TheIntoFrameTtype hint indicates an object that’s convertible to either a Narwhals DataFrame or LazyFrame. Your function will accept an instance of this type as its input and return it back to the caller as output.

Youruniversal_groupby_v1() function takes a single parameter,df, of typeIntoFrameT. The function then uses thenw.from_native() function to add a wrapper around the original input. This wrapper allows the Narwhals API to access your DataFrame. The specific object that Narwhals creates depends on the original input format.

Narwhals then performs a grouping operation. In this example, it groups the data byparty_name, counts the number of entries in thelast_name column for each group, and then sorts the results byparty_name.

If you’re familiar withPolars expressions, then this syntax will be immediately familiar to you. Remember, Narwhals uses a subset of Polars. TheAPI Completeness section of the documentation will tell you precisely what is or isn’t supported.

Finally, Narwhals takes the result of its analysis and uses.to_native() to return it in its original form. So if you pass in a Polars DataFrame, that’s what you’ll get back, and similarly for any other supported format.

Note: There areseveral Narwhals type hints available. As with all type hints in Python, they don’t have a runtime effect on your code, but libraries such asmypy orty, as well as someIDEs, will warn you when invalid data types are passed. Type hints also help make your code a little more readable.

One final point to note is that theuniversal_groupby_v1() function requires only theNarwhals library and the optionalnarwhals.typing.IntoFrameT. Nowhere do you import the source data’s library.

Now it’s time to test your function. Open aPython REPL within your project’s folder and run the following code:

Python
>>>importpandasaspd>>>importpolarsaspl>>>fromuniversal_processingimportuniversal_groupby_v1>>>presidents_pd=pd.read_parquet("presidents.parquet")>>>presidents_pl=pl.read_parquet("presidents.parquet")

After importing both the pandas and Polars libraries, you import youruniversal_groupby_v1() function. You also create both thepresidents_pd pandas DataFrame and thepresidents_pl Polars DataFrame.

To test youruniversal_groupby_v1() function, you first pass in a pandas DataFrame:

Python
>>>universal_groupby_v1(presidents_pd)              party_name  last_name3             Democratic         172  Democratic Republican          41             Federalist          15             Republican         200           Unaffiliated          14                   Whig          4

As you can see from the results, Narwhals can cope with your pandas DataFrame. The result is a pandas DataFrame because the function uses.to_native() to return the analyzed data in its original format.

Next, you’ll see if your function can also cope with a Polars DataFrame:

Python
>>>universal_groupby_v1(presidents_pl)shape: (6, 2)┌───────────────────────┬───────────┐│ party_name            ┆ last_name ││ ---                   ┆ ---       ││ str                   ┆ u32       │╞═══════════════════════╪═══════════╡│ Democratic            ┆ 17        ││ Democratic Republican ┆ 4         ││ Federalist            ┆ 1         ││ Republican            ┆ 20        ││ Unaffiliated          ┆ 1         ││ Whig                  ┆ 4         │└───────────────────────┴───────────┘

Yes, it can. This time, your function has returned a Polars DataFrame containing the same result.

Note: Given that pandas DataFrames contain anindex and Polars don’t, you may be wondering how Narwhals copes with this major difference. Narwhals’ native DataFrame format doesn’t include an index either. However, if you pass it a pandas DataFrame, Narwhals will ignore the index during analysis and then add it back when.to_native() presents the results.

Look back at the two outputs shown above, and you’ll see that the first result has an index, while the second result doesn’t.

Use Some Syntactic Sugar

Although the previous example works perfectly well, the Narwhals library provides some useful decoratorsyntactic sugar to make your code simpler and more readable.

Add a new version of your earlier function nameduniversal_groupby_v2() into your existinguniversal_processing.py file:

Pythonuniversal_processing.py
importnarwhalsasnwfromnarwhals.typingimportFrameT,IntoFrameT# ...@nw.narwhalifydefuniversal_groupby_v2(df:FrameT)->FrameT:return(df.group_by("party_name").agg(nw.col("last_name").count()).sort("party_name"))

This time, you’ve made use of the@nw.narwhalify functiondecorator to simplify your code. Usually, code needs to explicitly convert the DataFrame to a Narwhals format, analyze it, and then convert it back again to its original format before returning it. Because@nw.narwhalify does both conversions for you implicitly, you can concentrate on the data analysis steps.

Take a look back at the example above, and you’ll see neitherfrom_native() nor.to_native() is explicitly used.

This time, you use theFrameT type hint instead ofIntoFrameT. The Narwhals documentation definesFrameT as a type that represents anw.DataFrame ornw.LazyFrame. The documentation favorsFrameT when@nw.narwhalify is used. Because type hints aren’t enforced, theIntoFrameT hint would also work equally well.

But does version two of your function still produce the same result as version one did? Before you can find out, you’ll first need to exit your current Python REPL session by typingexit or pressingCtrl+D, and then start a fresh session. Now, for the moment of truth:

Python
>>>importpandasaspd>>>fromuniversal_processingimportuniversal_groupby_v2>>>presidents_pd=pd.read_parquet("presidents.parquet")>>>universal_groupby_v2(presidents_pd)              party_name  last_name3             Democratic         172  Democratic Republican          41             Federalist          15             Republican         200           Unaffiliated          14                   Whig          4

It certainly gives you what you’d expect when a pandas DataFrame has been passed to it. Feel free to try it using your earlierpresidents_pl DataFrame. You shouldn’t be disappointed.

Return a Different Output Format

So far, the returned DataFrame has been of the same type as the original DataFrame that you passed into your function. The DataFrame’s.to_native() method ensured this. You can specify an alternative output format using one of a range of available methods, such as.to_pandas() and.to_polars(), but you can’t use the@nw.narwhalify decorator.

You’ll next create a third version of your function thatalways returns a Polars DataFrame. Add this new version to your existing file:

Pythonuniversal_processing.py
# ...defuniversal_groupby_v3(df:IntoFrameT)->IntoFrameT:return(nw.from_native(df).group_by("party_name").agg(nw.col("last_name").count()).sort("party_name").to_polars())

This version looks very similar to version one. However, you specify that you always want a Polars DataFrame returned by using.to_polars() instead of.to_native().

As usual, restart your Python REPL and test your new function:

Python
>>>importpandasaspd>>>importpolarsaspl>>>fromuniversal_processingimportuniversal_groupby_v3>>>presidents_pd=pd.read_parquet("presidents.parquet")>>>presidents_pl=pl.read_parquet("presidents.parquet")>>>type(universal_groupby_v3(presidents_pd))<class 'polars.dataframe.frame.DataFrame'>>>>type(universal_groupby_v3(presidents_pl))<class 'polars.dataframe.frame.DataFrame'>

First of all, you pass a pandas DataFrame into your function, and then you pass a Polars DataFrame. In both cases, a Polars DataFrame is produced. Feel free to print both results to your console, and you’ll see that they’re indeed identical.

Work With Both Eager and Lazy Modes

If you’ve worked with Polars, you might be aware that it provides both eager and lazy APIs. Narwhals also supports both through itsnarwhals.DataFrame andnarwhals.LazyFrame classes.

In eager mode, a query is executed immediately, whereas in lazy mode, a query plan is created by analyzing the query and determining its most efficient execution plan. Execution of the query to obtain the result is deferred until the code requests it.

Despite its somewhat derogatory name,lazy evaluation delivers significant performance gains, particularly when handling large volumes of data.

In general, if you pass a DataFrame or LazyFrame into Narwhals, that’s what.to_native() will return to you. You can test this using either the first or second version of your function because both versions return an object in the same format as they receive it:

Python
>>>importpolarsaspl>>>fromuniversal_processingimportuniversal_groupby_v1>>>presidents_df=pl.read_parquet("presidents.parquet")>>>presidents_lf=pl.scan_parquet("presidents.parquet")>>>type(universal_groupby_v1(presidents_df))<class 'polars.dataframe.frame.DataFrame'>>>>type(universal_groupby_v1(presidents_lf))<class 'polars.lazyframe.frame.LazyFrame'>

As you can see, by passing in a Polars DataFrame to youruniversal_analysis_v1() function, you get a DataFrame back, and similarly for passing a LazyFrame. However, as with Polars, you need to take care when using Narwhals’ functions with LazyFrames, because not all analysis can be performed in lazy mode. More specifically, any analysis that needs to read data can only work in eager mode.

To investigate a situation where a LazyFrame fails, you decide to create a newuniversal_pivot_v1() function that produces apivot table of your data:

Pythonuniversal_processing.py
# ...defuniversal_pivot_v1(df:IntoFrameT)->IntoFrameT:return(nw.from_native(df).pivot(on="party_name",index="century",values="last_name",aggregate_function="count",).to_native())

This function performs a pivot table operation on the presidential data, allowing you to see a count of the number of presidents from each party in each century. To do this, you use Narwhals’ version of.pivot().

To display a separate column for each political party, you passon="party_name", and to return a separate row for each of the different centuries, you passindex="century". With the rows and columns defined, you then tell.pivot() to produce a count of each century-and-party combination by counting each president’s last name. You useaggregate_function="count" andvalues="last_name" to do this.

You decide to try out your function with a Polars DataFrame, but you could also pass it a pandas DataFrame:

Python
>>>importpolarsaspl>>>fromuniversal_processingimportuniversal_pivot_v1>>>presidents_df=pl.read_parquet("presidents.parquet")>>>universal_pivot_v1(presidents_df)shape: (4, 7)┌─────────┬──────────────┬────────────┬───────────────────────┬  \│ century ┆ Unaffiliated ┆ Federalist ┆ Democratic Republican ┆  \│ ---     ┆ ---          ┆ ---        ┆ ---                   ┆  \│ str     ┆ u32          ┆ u32        ┆ u32                   ┆  \╞═════════╪══════════════╪════════════╪═══════════════════════╪  \│ 18th    ┆ 1            ┆ 1          ┆ 0                     ┆  \│ 19th    ┆ 0            ┆ 0          ┆ 4                     ┆  \│ 20th    ┆ 0            ┆ 0          ┆ 0                     ┆  \│ 21st    ┆ 0            ┆ 0          ┆ 0                     ┆  \└─────────┴──────────────┴────────────┴───────────────────────┴  \────────────┬──────┬────────────┐ Democratic ┆ Whig ┆ Republican │ ---        ┆ ---  ┆ ---        │ u32        ┆ u32  ┆ u32        │════════════╪══════╪════════════╡ 0          ┆ 0    ┆ 0          │ 8          ┆ 4    ┆ 7          │ 7          ┆ 0    ┆ 10         │ 2          ┆ 0    ┆ 3          │────────────┴──────┴────────────┘

It worked! Next, you decide to try passing in a LazyFrame:

Python
>>>presidents_lf=pl.scan_parquet("presidents.parquet")>>>universal_pivot_v1(presidents_lf)Traceback (most recent call last):...AttributeError:'LazyFrame' object has no attribute 'pivot'.⮑ Did you mean: 'unpivot'?

This time, your analysis fails. Instead of a result, you get anAttributeErrorexception. The problem, as the exception’s message points out, is that.pivot() doesn’t apply to LazyFrames. You can only use.pivot() with DataFrames because they contain the data that.pivot() needs to perform its calculations.

If you need to write Narwhals code that uses lazy mode, then make sure to check that the functionality you need is supported. The documentation’sAPI Completeness section provides bothsupported DataFrame methods andsupported LazyFrame methods, giving you a heads-up on what’s available in each mode. Also, theAPI Reference section provides details on how each method works.

Note: Thefrom_native() function also supports aneager_only parameter. This is set toFalse by default to allow both LazyFrames and DataFrames to be converted to Narwhals format.

If you seteager_only=True, only DataFrames will be accepted. Passing a LazyFrame will causefrom_native() to raise aTypeError. While this won’t solve the underlying problem, the program will halt before any lengthy processing begins. Without this, your code will fail during processing, with anAttributeError.

If you want youruniversal_pivot() function to work with both DataFrames and LazyFrames, you could use the.collect() method on the LazyFrame passed to it before you pass it to.pivot(). To see this working, you decide to create auniversal_pivot_v2() function as shown:

Pythonuniversal_processing.py
# ...defuniversal_pivot_v2(df:IntoFrameT)->IntoFrameT:df=nw.from_native(df)ifisinstance(df,nw.LazyFrame):df=df.collect()returndf.pivot(on="party_name",index="century",values="last_name",aggregate_function="count",).to_native()

This time, version two of your function tests for the presence of a LazyFrame using Python’s built-inisinstance() function. If a LazyFrame is present, afterfrom_native() adds a Narwhals wrapper, you call.collect() to populate it with data and create a DataFrame. You then pass the DataFrame through the same pivoting operation as before.

If either a DataFrame or LazyFrame is passed touniversal_pivot_v2() , it’s analyzed in the same way as it was earlier withuniversal_pivot_v1(). Feel free to test it for yourself, and you’ll see it produces the same results in both cases.

Note: In cases where your processing demands you switch from a Narwhals DataFrame into a LazyFrame, you can do so by using.lazy().

The final aspect of Narwhals you should be aware of is that its syntax is similar to that of Polars. You’ll investigate this next.

Apply Your Existing Polars Skills to Narwhals

Now that you’ve gained some idea of what Narwhals is and what it offers you, in this final section, you’ll see how you can use your existing Polars knowledge and apply it to a Narwhals problem. Remember, theNarwhals documentation explains the subset of the Polars-like syntax that Narwhals supports.

To complete this section, you’ll use both thebooks.parquet andauthors.parquet files included in your downloadables. You’ll see they contain details of several books and their authors.

Thebooks.parquet file contains the following four fields:

HeadingMeaning
book_titleTitle of the book
languageLanguage of first publication
year_publishedYear of book’s publication
author_idAuthor’s identification number

Meanwhile, theauthors.parquet file contains the following three fields:

HeadingMeaning
author_idAuthor’s identification number
first_nameAuthor’s first name
last_nameAuthor’s last name

Both files share a commonauthor_id column. This allows their rows to be linked together.

Now see if you can complete the following challenge exercise:

  1. Read the content of thebooks.parquet file into a Polars DataFrame.

  2. Read the content of theauthors.parquet file into a Polars LazyFrame.

  3. Use your wizardry to write a function namedrowling_books() that uses each of your Polars objects and produces a list of the books written by British authorJ. K. Rowling. Your function should return each book’s title, the year in which it was published, as well as the author’s full name. The books should be output as a pandas DataFrame in the order of their publication.

  4. Test your function.

Hint: There should be seven book titles in the final result.

One possible solution could be something like this:

Pythonexercise_solution.py
importnarwhalsasnwfromnarwhals.typingimportIntoFrameTdefrowling_books(df:IntoFrameT,lf:IntoFrameT)->IntoFrameT:return(nw.from_native(df).join(nw.from_native(lf).filter(nw.col("last_name").str.contains("Rowling")).collect(),on="author_id",).select(["book_title","year_published","first_name","last_name"]).sort("year_published").to_pandas())

To join the data from both files, Narwhals uses.join() in a similar way to Polars. You must convert both the DataFrame parameterdf and the LazyFrame parameterlf into the same type of Narwhals object before joining them. It doesn’t make sense to join a Narwhals DataFrame and a LazyFrame together.

In the case ofdf, you pass it to.from_native() to produce aPolarsDataFrame Narwhals object. This uses the Polars library for processing.

Your LazyFrame needs a bit more preprocessing before you can join it to your DataFrame.

You again usefrom_native() onlf to convert it to a Narwhals-equivalentLazyFrame object. Once it’s converted, you use both.filter(), as well as.str.contains("Rowling"), to select only rows containing authors with alast_name ofRowling. However, this will still be in aLazyFrame, so to convert it to aPolarsDataFrame Narwhals object, you need to use.collect() in a way similar to how you would in Polars.

Once you have your twoPolarsDataFrame objects, you can join them into a largerPolarsDataFrame using.join() and specifying"author_id" as the column to join them on.

You next use.select() to specify the columns you want to see, and.sort() to sort the output in the desired order of the publication year. These serve the same purpose as their equivalents in Polars.

At this point, you now have your final Narwhals DataFrame containing what you want. To get your desired output format, you use.to_pandas() to convert it into a pandas DataFrame.

Of course, you decide to test your function:

Python
>>>importpolarsaspl>>>fromexercise_solutionimportrowling_books>>>books_df=pl.read_parquet("books.parquet")>>>authors_lf=pl.scan_parquet("authors.parquet")>>>rowling_books(books_df,authors_lf)                                 book_title  \0  Harry Potter and the Philosopher's Stone  \1   Harry Potter and the Chamber of Secrets  \2  Harry Potter and the Prisoner of Azkaban  \3       Harry Potter and the Goblet of Fire  \4     Harry Potter and the Order of Phoenix  \5    Harry Potter and the Half-Blood Prince  \6      Harry Potter and the Deathly Hallows  \year_published first_name   last_name          1997    Joanne   K. Rowling          1998    Joanne   K. Rowling          1999    Joanne   K. Rowling          2000    Joanne   K. Rowling          2003    Joanne   K. Rowling          2005    Joanne   K. Rowling          2007    Joanne   K. Rowling

When you call your function, you see a list of theHarry Potter books stored in the DataFrame.

Conclusion

Narwhals enables Python library developers to create DataFrame-agnostic code that processes both DataFrames and LazyFrames, regardless of their original source.

It’s a lightweight library because it wraps the original source data in its own object ecosystem but delegates data processing back to the original source library to take advantage of its efficiencies.

Narwhals is also great for developers who have experience with the very popular Polars library, since its syntax is based on Polars.

In this tutorial, you’ve learned how to:

  • Install Narwhals
  • Write analysis code using data fromdifferent data analysis libraries
  • Work in bothlazy and eager modes
  • Control theoutput format of what Narwhals returns to you
  • Use your existingPolars skills to write Narwhals code

Narwhals is still a young library, and its developers are actively improving it. This tutorial has aimed to make you aware of the library and its key features. While it’s unlikely these will change, it’s still wise to keep an eye on the Narwhals documentation from time to time to see how its key features evolve and expand, as well as which new data analysis libraries Narwhals will support in the future.

Get Your Code:Click here to download the free sample code and data files that you’ll use to work with Narwhals in Python.

Frequently Asked Questions

Now that you have some experience with Narwhals in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click theShow/Hide toggle beside each question to reveal the answer.

Narwhals lets library authors write DataFrame-agnostic code that runs against pandas, Polars, DuckDB, and more with the same API. It’s aimed at Python library developers who need one code path to analyze common DataFrame formats.

No. Narwhals wraps the input and translates your expressions, then hands execution to the original library. It complements pandas and Polars rather than replacing them.

Narwhals avoids copying by operating on the original DataFrame and delegating work to the source backend. This keeps memory use low and preserves the performance characteristics of the underlying library.

Wrap inputs withnw.from_native() and return the original type with.to_native(). If you always want a specific output, call.to_pandas() or.to_polars() instead, or use the@nw.narwhalify decorator to auto-handle the conversions around your function body.

Narwhals mirrors Polars withnw.DataFrame for eager andnw.LazyFrame for lazy execution. Some operations like.pivot() require materialized data, so convert a lazy input with.collect() before those steps.

Take the Quiz: Test your knowledge with our interactive “Writing DataFrame-Agnostic Python Code With Narwhals” quiz. You’ll receive a score upon completion to help you track your learning progress:


Writing DataFrame-Agnostic Python Code With Narwhals

Interactive Quiz

Writing DataFrame-Agnostic Python Code With Narwhals

If you're a Python library developer wondering how to write DataFrame-agnostic code, the Narwhals library is the solution you're looking for.

🐍 Python Tricks 💌

Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

AboutIan Eyre

Ian is an avid Pythonista and Real Python contributor who loves to learn and teach others.

» More about Ian

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

MasterReal-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

MasterReal-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.


Looking for a real-time conversation? Visit theReal Python Community Chat or join the next“Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics:advanceddata-sciencepython

Related Tutorials:

Keep reading Real Python by creating a free account or signing in:

Already have an account?Sign-In

Almost there! Complete this form and click the button below to gain instant access:

Writing DataFrame-Agnostic Python Code With Narwhals

Writing DataFrame-Agnostic Python Code With Narwhals (Sample Code)

🔒 No spam. We take your privacy seriously.


[8]ページ先頭

©2009-2026 Movatter.jp