Apply a custom function with full LazyFrame context.
fnCallable[[pl.LazyFrame, pl.Expr], pl.Expr]
Called as fn(lf, col_ref) → pl.Expr for each matched column. col_ref evaluates to the column’s values in lf; it works correctly for any expression shape, including transforms and when/then/otherwise. Use col_ref.meta.output_name() when the string column name is needed.
Examples:
def center_scale(lf: pl.LazyFrame, col_ref: pl.Expr) -> pl.Expr: stats = lf.select( col_ref.mean().alias("m"), col_ref.std().alias("s") ).collect() m, s = stats["m"][0], stats["s"][0]return (col_ref - m) / spl.DataFrame({"x": [1.0, 2.0, 3.0, 4.0, 5.0]}).ps.with_columns( pl.col("x").ps.apply(center_scale))
Cast a column to Enum, optionally deriving categories from the data.
When categories are derived from the data, they are sorted by the column’s native dtype before being cast to string. This means integers sort numerically (1, 2, 10), dates chronologically, and strings alphabetically — rather than all sorting lexicographically.
categoriesSequence[str] | None
Fixed set of allowed values. If omitted, derived from the data as the unique values sorted by native dtype order.
Optional callable that receives the non-null counts DataFrame (columns: category column + "n", sorted by frequency descending) and returns a boolean sequence where True marks categories to collapse.
Insert new categories without changing any values.
categoriesSequence[str]
New category labels to add.
beforeint | None
Insert before this 0-based index of the existing categories. None (default) appends at the end. Negative indices count from the end. Any value ≥ len(categories) is equivalent to None (end).
Move specified categories to a given position, keeping all others in their relative order.
*levelsstr = ()
beforeint | None = 0
Insert before this 0-based index of the remaining categories. 0 (default) moves to the front. None appends at the end. Negative indices count from the end of the remaining categories. Any value ≥ len(remaining) is equivalent to None (end).
Returns an Enum-typed column whose category names are the bin labels. Integer columns use fully-closed [a, b] notation; single-element bins are written as {x}.
Category labels (must be len(breaks) + 1). Auto-generated if omitted.
left_closedbool = True
If True (default), intervals are [lo, hi); otherwise (lo, hi].
fmtstr | Callable | None
Formatter for auto-generated labels. For numeric, a format-spec string (e.g. “.2f”) or callable. For temporal, a callable or None (uses str()).
extendbool = True
For numeric only — if True (default), outermost labels extend to -∞/+∞. For unsigned integers: 0/+∞. If False, uses data min/max. Temporal breaks always use data bounds regardless of this setting.
return_structbool = False
If True, return a struct {lo, hi} instead of just the label.
Expr.ps_chop.n_elements( n, tail='split', labels=None, left_closed=True, fmt='g', extend=False, return_struct=False,)
Chop into groups of n observations each.
Returns an Enum-typed column whose category names are the bin labels. Boundaries are drawn after every nth element (sorted order). Ties are never split — the boundary advances to the next distinct value if needed.
nint
Number of observations per group.
tailLiteral['split', 'merge'] = 'split'
What to do when the total doesn’t divide evenly. “split” (default) keeps the smaller final group; “merge” absorbs it into the preceding group.
labelsSequence[str] | None
Category labels. Auto-generated if omitted.
left_closedbool = True
If True (default), intervals are [lo, hi); otherwise (lo, hi].
fmtstr | Callable[[float], str] = 'g'
Number formatter for auto-generated labels (numeric columns only).
extendbool = False
If True, extend outermost labels to -∞ / +∞ (or 0 / +∞ for unsigned integers). If False (default), the first label opens at the data minimum and the last closes at the data maximum.
return_structbool = False
If True, return a struct instead of just the label.
Returns an Enum-typed column whose category names are the bin labels.
probsSequence[float]
Quantile probabilities in (0, 1), e.g. [0.25, 0.5, 0.75] for quartiles.
labelsSequence[str] | None
Category labels (must be len(probs) + 1). Auto-generated if omitted.
left_closedbool = True
If True (default), intervals are [lo, hi); otherwise (lo, hi].
fmtstr | Callable | None
Formatter for auto-generated labels. For numeric, defaults to “.0%” (percentages) when raw=False and “g” when raw=True. For temporal, a callable or None (uses str()).
rawbool = False
If True, label with the actual break values instead of percentages. Ignored for temporal columns (always uses actual values).
extendbool = False
If True, extend outermost labels to -∞ / +∞ (only affects numeric raw=True). Default False. For unsigned columns, lower bound is 0.
return_structbool = False
If True, return a struct instead of just the label.
Truncate each string to fit within width characters.
Collapses whitespace and appends placeholder when the text is cut.
widthint = 5
Maximum length of the result, including the placeholder.
sideLiteral['right', 'left', 'center'] = 'right'
Which side to truncate — ‘right’ (default), ‘left’, or ‘center’.
placeholderstr = '…'
String inserted where the text is cut.
Examples:
pl.DataFrame({"x": ["short", "a much longer string"]}).select( pl.col("x").ps_str.trunc(width=10))
shape: (2, 1)
┌────────────┐
│ x │
│ --- │
│ str │
╞════════════╡
│ short │
│ a much lo… │
└────────────┘
Internals
FrameExpr
FrameExpr( col_expr, resolver,)
An expression that requires a LazyFrame context to resolve into a list of pl.Expr.
A plain pl.Expr is insufficient for operations like ps_enum.make() or ps_chop.chop() because Polars needs to know the output dtype (e.g. the exact pl.Enum([...]) category list) at plan-construction time — before any data is seen. FrameExpr defers that resolution to a two-phase execution model:
Phase 1 — peekps.with_columns calls resolve(lf) with the currentLazyFrame. The resolver runs a small aggregation (e.g. unique().sort() for category discovery, a handful of quantiles for binning) and collects it. Because the resolver receives the full lazy plan up to that point, any preceding .filter() or .select() calls are already embedded and Polars’ predicate/projection pushdown applies — only the relevant rows and columns are scanned.
Phase 2 — expression The resolver uses the aggregation result to construct a concrete pl.Expr with all dtype information baked in (e.g. pl.col("x").cast(pl.Enum(["a", "b", "c"]))). This expression is inserted back into the lazy plan and executed lazily together with all subsequent operations.