pandas vs Polars
pandas has been Python's standard DataFrame library for over a decade: nearly every data tutorial, plotting library, and machine-learning tool accepts its DataFrames. Polars is a newer DataFrame library written in Rust on Apache Arrow's columnar memory format. It runs queries across all CPU cores, can optimize whole pipelines before executing them, and often processes data many times faster with much less memory.
Short verdict: use Polars for new data pipelines, large files, and performance-sensitive transformations; keep pandas where its ecosystem matters — exploratory notebooks with many pandas-based tools, libraries that require pandas, and existing codebases that work. The two interoperate cheaply through Arrow, so you can adopt Polars one pipeline at a time instead of rewriting everything.
TL;DR
- pandas: eager, single-threaded for most operations, index-based, huge ecosystem.
- Polars: multi-threaded, Arrow-native, expression API, optional lazy execution with query optimization.
- Polars is commonly several times to an order of magnitude faster on joins, group-bys, and file scans, with lower memory use.
- Polars has no index, strict types, and a clear distinction between null and NaN.
- Lazy queries (
scan_parquet...collect) push filters and column selection into file reads and can stream larger-than-memory data. - Convert between them with
pl.from_pandas()anddf.to_pandas(); many libraries now accept Polars directly.
Quick Example
Revenue per country from paid orders. pandas:
Polars, lazily — the filter and the four needed columns are pushed down into the Parquet scan:
The pandas version reads every column of every row into memory before filtering. The Polars version builds a query plan, reads only what it needs, and runs the aggregation in parallel.
Core Concepts
Head-to-Head
Expressions
Polars code is built from expressions: pl.col("price") * 1.2, pl.col("name").str.to_lowercase(), pl.col("amount").sum().over("customer_id"). Expressions inside select, with_columns, filter, and agg run in parallel and are optimized together. Row-wise Python functions (map_elements) exist but discard most of the speed — the same lesson as avoiding apply in pandas.
Lazy Execution
pl.scan_* functions and .lazy() create a LazyFrame: operations build a plan that Polars optimizes — predicate and projection pushdown, common subexpression elimination, join reordering — before .collect() runs it. .explain() shows the optimized plan. Lazy queries can also run on the streaming engine to process data larger than RAM.
Types and Missing Data
Polars is strict: a column has one type, and mixing types raises an error rather than silently producing an object column. Missing values are null for every type, so integer columns stay integers when values are missing — in pandas, they historically became floats unless you used nullable dtypes. Recent pandas versions narrow the gap with Arrow-backed dtypes and copy-on-write semantics.
The Index
pandas aligns operations on the index, which is powerful for time series and labeled data but a frequent source of surprises after filters, merges, and groupby. Polars has no index; joins and alignment are always explicit, and group_by returns ordinary columns.
Ecosystem and Interoperability
- Conversion:
pl.from_pandas(df)anddf.to_pandas()(zero-copy where Arrow types allow). - Machine learning: scikit-learn transformers can output Polars (
set_output(transform="polars")), and many estimators accept Polars input. - Plotting: Polars has a
.plotnamespace built on Altair; libraries such as Plotly and seaborn accept Polars DataFrames, or convert to pandas. - SQL: Polars supports SQL queries over frames, and DuckDB queries both pandas and Polars DataFrames directly.
- Library authors: Narwhals provides a DataFrame-agnostic API so libraries can support both.
Adopting Polars Incrementally
- Start with batch pipelines that read large files and run joins or aggregations — the biggest wins.
- Use lazy scans (
scan_parquet,scan_csv) from the start. - Convert to pandas at boundaries where a library requires it.
- Translate idioms: boolean indexing →
filter,assign→with_columns,groupby().agg()→group_by().agg(),merge→join,apply→ expressions. - Compare results on real data, paying attention to nulls, types, and sort order.
Best Practices
Think in Columns, Not Rows
In both libraries, vectorized column operations are fast and row-wise Python is slow. Polars rewards this more strongly: express logic as expressions.
Default to Lazy for Files
Scanning lazily lets Polars skip unneeded columns and rows, which often matters more than raw compute speed.
Keep pandas Where It Shines
Interactive exploration with pandas-based tools, statistical libraries, and established code are good reasons to stay. Use the right tool per job rather than standardizing dogmatically.
Measure on Your Data
Benchmarks vary with data shape and operations. Time your actual pipelines before and after switching. See pandas Performance.
Common Mistakes
Writing pandas Code in Polars
Collecting Too Early
Assuming Identical Null Semantics
FAQ
Is Polars faster than pandas?
Usually, often substantially, for joins, group-bys, filters, and file reads, thanks to multi-threading, Arrow memory, and query optimization. For small DataFrames the difference may not matter.
Should I replace pandas with Polars?
For new pipelines and performance-sensitive work, Polars is a strong default. Keep pandas where libraries require it or code already works well; the two convert cheaply.
Does Polars work with scikit-learn?
Yes. Many scikit-learn estimators accept Polars input, and transformers can output Polars DataFrames. Some libraries still require pandas or NumPy, in which case you convert at the boundary.
Can Polars handle data larger than memory?
Lazy queries can run on Polars' streaming engine, processing data in batches. For very large or distributed workloads, Spark or a query engine such as DuckDB or Trino may still be more appropriate.
Why does Polars not have an index?
By design: explicit columns make joins and alignment predictable and parallel execution simpler. Operations that used the index in pandas use explicit columns and joins in Polars.
Related Topics
- pandas — pandas in depth
- pandas Performance — Making pandas faster
- DuckDB — In-process SQL over DataFrames and files
- Spark — Distributed DataFrames for very large data
- Python for Data Science — The wider ecosystem
- Python — The language both libraries serve