Polars / Chapter 1 of 7

DataFrames and expressions

Create Polars Series and DataFrames in Python, inspect schemas, and understand expressions and LazyFrames before transforming a dataset.

3 topicsUpdated
01 / 50

Rows, columns, types, and pipelines#

A table organizes many records so the same operation can be applied to all of them.

Polars cleans and analyzes tables. A row is one record, a column is one attribute, and a cell holds one value. Define the row’s meaning before aggregating: three order lines represent three line records but may belong to one order.

order_idcustomerquantityunit_price_cents
1001C0121250
1002C0213000
1003C013500

A schema maps column names to types. Customer IDs stay as text to preserve leading zeros; prices use integer cents. null means missing data and differs from zero, an empty string, and NaN.

Polars stores values by column and runs native operations in Rust across CPU threads. File reads, large joins, extra columns, and Python callbacks can still dominate query time.

A pipeline reads, cleans, validates, transforms, and writes data. A query describes those operations; an aggregation summarizes rows.

Running the examples

Save a Python code card as example.py and run it. Each local program includes its input and expected output. File examples use temporary folders; use persistent paths in your own pipelines.

Official reference: Rows, columns, types, and pipelines.

02 / 50

Series, DataFrame, LazyFrame, and expressions#

Choose the object by whether you need one column, a table now, or a plan for later.

ToolMeaningWhen to use it
SeriesOne named, typed sequence of values.Inspect or calculate with one column already in memory.
DataFrameA table of equal-length, uniquely named Series.Explore small or already-loaded data; operations return results immediately.
LazyFrameA plan describing a table and its transformations.Build file pipelines; let Polars optimize the whole query before executing it.
ExprA description of a computation, such as quantity × price.Use inside select, with_columns, filter, or aggregation on either kind of frame.

Eager execution performs each table operation now. Lazy execution records operations and runs them when you call collect() or write with a lazy sink. A sink is an output destination. pl.col("quantity") creates an expression referring to a column; it is not that column's actual values. df["quantity"] returns a materialized Series. Materialized means the values have been computed and occupy memory.

ShellInstall a reproducible environment
python -m venv .venv
# macOS / Linux:
source .venv/bin/activate
# Windows PowerShell instead: .venv\\Scripts\\Activate.ps1
python -m pip install "polars==1.44.2"
python -c "import polars as pl; print(pl.__version__)"
Package versions

The examples use Python 3.12 and Polars 1.44.2. Install companion packages where indicated. Check release notes before upgrading APIs marked unstable.

PythonThe four tools working together
import polars as pl

quantity = pl.Series("quantity", [2, 1, 3], dtype=pl.Int64)
df = pl.DataFrame({"quantity": quantity, "price_cents": [1250, 3000, 500]})
revenue = (pl.col("quantity") * pl.col("price_cents")).alias("revenue_cents")
print("series:", quantity.to_list())
print("eager:", df.select(revenue)["revenue_cents"].to_list())
plan = df.lazy().select(revenue)
print("lazy:", plan.collect()["revenue_cents"].to_list())
TextExpected output
series: [2, 1, 3]
eager: [2500, 3000, 1500]
lazy: [2500, 3000, 1500]
Eager and lazy results

select returns the calculated revenue column. The lazy plan runs when collect() is called. Converting an existing DataFrame to lazy does not release its allocated data.

Official reference: Series, DataFrame, LazyFrame, and expressions.

03 / 50

Create and inspect tables#

Give empty and uncertain inputs a schema instead of guessing their types.

Create tables from dictionaries of columns or lists of row dictionaries. Inference means choosing types from input values; it can fail when later values differ. schema specifies all fields, while schema_overrides changes selected inferred fields. Use orient="row" for tuple rows so orientation is unambiguous. Polars has no implicit pandas-style row index; keep IDs as explicit columns.

PythonOrders, tuple rows, and a correctly typed empty table
import polars as pl

orders = pl.DataFrame(
    [
        {"order_id": "001", "quantity": 2, "channel": "web"},
        {"order_id": "002", "quantity": 1, "channel": None},
    ],
    schema={"order_id": pl.String, "quantity": pl.Int64, "channel": pl.String},
)
print("shape:", orders.shape)
print("schema:", dict(orders.schema))
print("head:", orders.head(1).to_dicts())
print("nulls:", orders.null_count().to_dicts())
print("quantity:", orders.get_column("quantity").to_list())
empty = pl.DataFrame(schema=orders.schema)
print("empty:", empty.shape, dict(empty.schema))
rows = pl.DataFrame(
    [("C01", 10), ("C02", 20)],
    schema=[("customer", pl.String), ("spend", pl.Int64)],
    orient="row",
)
print("tuple input:", rows.to_dicts())
TextExpected output
shape: (2, 3)
schema: {'order_id': String, 'quantity': Int64, 'channel': String}
head: [{'order_id': '001', 'quantity': 2, 'channel': 'web'}]
nulls: [{'order_id': 0, 'quantity': 0, 'channel': 1}]
quantity: [2, 1]
empty: (0, 3) {'order_id': String, 'quantity': Int64, 'channel': String}
tuple input: [{'customer': 'C01', 'spend': 10}, {'customer': 'C02', 'spend': 20}]
InspectResult
df.head(5), df.tail(5)First or last five rows.
df.glimpse()Print column types and sample values.
df.describe()Exploratory statistics; do not depend on its output format as a production contract.
df.columns, df.dtypes, df.height, df.widthNames, types, row count, column count.
df.sample(n=2, seed=7)A repeatable sample for this environment, rather than always looking at the first rows.
lf.collect_schema()Resolve a lazy schema; it may read file metadata and take time.

Official reference: Create and inspect tables.