Skip to content

Guides

Expressions and data types

The type of an expression holds its code. When the expression meets a frame, the checker computes its column from that code and the data types of the frame, with the typing rules of Polars.

expressions.pymypy: no errors
1class Trades(Columns):
2 symbol: pl.String
3 price: pl.Float64
4 quantity: pl.Int64
5
6
7trades: TypedDataFrame[Trades]
8summary = trades.select(
9 pl.col("price") * 2,
10 pl.col("symbol").str.len_chars(),
11 pl.when(pl.col("quantity") > 0).then(pl.col("price")).otherwise(0).alias("buy_price"),
12)
Resultsummarydeclared
priceFloat64symbolUInt32buy_priceFloat64
Revealed type
TypedDataFrame[TypedDict({'price': Float64, 'symbol': UInt32, 'buy_price': Float64}, closed=True), None]
  • pl.col("price") * 2 keeps the name price, and gives a Float64.
  • str.len_chars() gives a UInt32.
  • when/then/otherwise gives the common data type of its branches. The literal 0 becomes a Float64 next to price.

The same expression can give other data types in other frames, because the checker computes it at the data types of each frame.

An aggregation, for example sum(), gives one value for a group of rows. Its frame decides what that means:

expressions.pymypy: no errors
1class Trades(Columns):
2 symbol: pl.String
3 price: pl.Float64
4 quantity: pl.Int64
5
6
7trades: TypedDataFrame[Trades]
8one_row = trades.select(pl.col("quantity").sum())
9broadcast = trades.with_columns(total=pl.col("quantity").sum())
10per_symbol = trades.group_by("symbol").agg(pl.col("quantity").sum())
Resultone_rowdeclared
quantityInt64
Revealed type
TypedDataFrame[TypedDict({'quantity': Int64}, closed=True), None]
Resultbroadcastdeclaredadded
symbolStringpriceFloat64quantityInt64totalInt64
Revealed type
TypedDataFrame[TypedDict({'symbol': String, 'price': Float64, 'quantity': Int64, 'total': Int64}, closed=True), None]
Resultper_symboldeclared
symbolStringquantityInt64
Revealed type
TypedDataFrame[TypedDict({'symbol': String, 'quantity': Int64}, closed=True), None]
  • In select, the sum gives a frame of one row.
  • In with_columns, Polars repeats the sum on each row.
  • In agg, the sum gives one value for each group.

A declaration can state that an expression works row by row, and the checker then refuses an aggregation. See Declare expression types.

If Polars refuses an expression at the data types of the frame, the checker reports it:

expressions.pymypy: 3 errors
1from typing import Literal
2
3import polars as pl
4
5from astrolabe import Columns, Datetime, TypedDataFrame
6
7
8class Fills(Columns):
9 symbol: pl.String
10 time: Datetime[Literal["us"], None]
11
12
13fills: TypedDataFrame[Fills]
14fills.select(pl.col("symbol").round(2))
error: "symbol" has no result type at this frame's column types: round does not accept String, and polars raises InvalidOperationError [unsupported-dtype-op]
15fills.select(pl.col("time").cast(pl.Boolean))
error: This expression cannot cast from Datetime(time_unit='us', time_zone=None) to Boolean [invalid-cast]
16fills.select(pl.col("symbol") + pl.col("time"))
error: "symbol" has no result type at this frame's column types: Plus not allowed on String and Datetime(time_unit='us', time_zone=None), and polars raises InvalidOperationError [unsupported-dtype-op]
  • round needs a number.
  • Polars cannot cast a Datetime to a Boolean.
  • Polars cannot add a Datetime to a String.

An expression that Polars refuses at every data type is an error where you write it, before it meets a frame.