Expressions and contexts

Polars · Essentials · 15 min

Expressions are the heart of Polars: almost every operation is written as one, and they are what the engine optimizes and parallelizes.

An expression such as pl.col("price") * 1.2 is not a result: it is a recipe describing how to compute a column. Nothing runs until you hand it to a context, a DataFrame method that evaluates expressions:

  • df.select(...) returns only the columns you compute.
  • df.with_columns(...) returns all existing columns plus new or replaced ones.
  • df.filter(...) keeps rows where a boolean expression is true.
  • df.group_by(...).agg(...) evaluates expressions per group (next lessons).

Build expressions from pl.col("name"), literals pl.lit(5), and operators (+, *, >, &, |, ~). Methods chain: pl.col("name").str.to_uppercase(), pl.col("x").round(1), pl.col("x").cast(pl.Float32). Name the output with .alias("new"), or use keyword arguments: with_columns(total=pl.col("a") + pl.col("b")).

Expressions inside one context are independent, so Polars runs them in parallel on different cores. The flip side is that a column created in a with_columns call is not visible to other expressions in that same call; chain a second with_columns if one new column depends on another.

Because an expression is just a value, you can store it in a variable or return it from a function and reuse it across queries, which keeps pipelines short and readable.

Example

import polars as pl

df = pl.DataFrame({"item": ["tea", "cake", "jam"],
                   "price": [3.0, 4.5, 2.0], "qty": [2, 1, 5]})

revenue = pl.col("price") * pl.col("qty")      # a reusable recipe

out = (
    df.with_columns(revenue.alias("revenue"),
                    item=pl.col("item").str.to_uppercase())
      .with_columns(big=pl.col("revenue") > 5)  # needs the column above
      .filter(pl.col("qty") > 1)
      .select("item", "revenue", "big")
)
print(out.rows())   # [('TEA', 6.0, True), ('JAM', 10.0, True)]

Remember

  • An expression describes a computation; a context runs it
  • select keeps only computed columns, with_columns adds to the frame
  • Expressions in one context run in parallel and can't see each other's outputs

Watch out for

  • Referring to a column created earlier in the same with_columns call: it doesn't exist yet.

Check yourself

  1. df.with_columns(a2=pl.col('a') * 2, a4=pl.col('a2') * 2) fails. Why?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pola.rs/user-guide/concepts/expressions-and-contexts/