import polars as pl import requests import io url = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv" response = requests.get(url) df = pl.read_csv(io.StringIO(response.text)) df.head()
Polars is a DataFrame library for Python, written in Rust. It covers the
same ground as pandas: reading CSV files, filtering
rows, adding columns, grouping and joining. Steps are written as
expressions such as pl.col("price") * 2, and a lazy query lets Polars
plan every step before it runs any of them. People use it to clean,
reshape and summarize tables of data. This page is an online Polars
compiler: the code runs in your browser, so you can try it without
installing anything.
Run the example first, then paste any snippet below into a new cell to try it.
requests.get() downloads mpg.csv, a table of cars from the
seaborn-data repository on GitHub, and response.text holds the file as
one string. io.StringIO() wraps that string so pl.read_csv() can read
it like a file. The result is a DataFrame of 398 cars and 9 columns.
df.head() returns the first five rows, which appear below the code as a
table. Under each column name, Polars shows the type it detected: f64
for decimals, i64 for whole numbers and str for text.
pl.col("name") refers to a column inside an expression. filter() keeps
the rows where an expression is true, with_columns() adds or replaces
columns, and select() keeps the columns you name:
import polars as pl
df = pl.DataFrame({
"city": ["Oslo", "Lima", "Pune", "Oslo", "Lima", "Pune"],
"month": ["Jan", "Jan", "Jan", "Jul", "Jul", "Jul"],
"temp_c": [-4.3, 22.8, 20.9, 16.8, 16.4, 26.6],
"rain_mm": [49, 1, 2, 81, 1, 180],
})
warm = (
df.filter(pl.col("temp_c") > 20)
.with_columns((pl.col("temp_c") * 9 / 5 + 32).round(1).alias("temp_f"))
.select("city", "month", "temp_c", "temp_f")
.sort("temp_c", descending=True)
)
print(warm)
group_by() splits the rows by the values in a column, and agg()
returns one row per group with a result for each expression you pass.
pl.len() counts the rows in each group, and an expression can combine
columns before it sums them:
import polars as pl
sales = pl.DataFrame({
"region": ["North", "South", "North", "East", "South", "North"],
"units": [12, 7, 3, 9, 14, 5],
"price": [2.5, 2.5, 4.0, 4.0, 4.0, 2.5],
})
summary = (
sales.group_by("region")
.agg(
pl.len().alias("orders"),
pl.col("units").sum(),
(pl.col("units") * pl.col("price")).sum().alias("revenue"),
)
.sort("revenue", descending=True)
)
print(summary)
join() matches rows on key columns. With how="left", every order is
kept, and one with no matching customer gets a null name. how="anti"
keeps only the rows that have no match:
import polars as pl
customers = pl.DataFrame({"id": [1, 2, 3], "name": ["Ana", "Ben", "Chloe"]})
orders = pl.DataFrame({
"order_id": [10, 11, 12, 13],
"customer_id": [1, 1, 3, 4],
"total": [25.0, 12.5, 40.0, 8.0],
})
# Every order, with the customer's name where there is one
print(orders.join(customers, left_on="customer_id", right_on="id", how="left"))
# Customers who have no orders
print(customers.join(orders, left_on="id", right_on="customer_id", how="anti"))
.lazy() turns a DataFrame into a LazyFrame. Each method after it adds
a step to a query plan instead of running it, and collect() runs the
whole plan and returns a DataFrame:
import polars as pl
pl.DataFrame({
"day": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"visits": [120, 95, 143, 110, 160],
"signups": [6, 3, 9, 4, 11],
}).write_csv("visits.csv")
query = (
pl.read_csv("visits.csv")
.lazy()
.with_columns((pl.col("signups") / pl.col("visits") * 100).round(1).alias("rate_pct"))
.filter(pl.col("visits") > 100)
.select("day", "rate_pct")
.sort("rate_pct", descending=True)
)
print(query.explain()) # the optimized plan, read from the bottom up
print(query.collect())
The code filters after with_columns(), but the plan applies the filter
first (SELECTION on the bottom line), so Polars drops Tuesday before it
computes rate_pct.
group_by() does not keep the groups in the order they first appear,
and the order can change from one run to the next. Sort the result, as
the snippet above does, or pass maintain_order=True.read_parquet() and write_parquet()
raise AttributeError, and pl.scan_csv(...).collect() stops with a
PanicException. Use pl.read_csv() followed by .lazy(), as above,
and DuckDB for Parquet files.to_pandas() needs pandas and pyarrow, and Polars looks for them only
when it is first imported. After the example has run, importing them
still leaves to_pandas() raising ModuleNotFoundError. Reload the page
and import pandas, pyarrow and then polars in your first cell.