6. Using Polars

This tutorial is based on example code which can be found in the TRAC GitHub Repository under examples/models/python.

The runtime supports Polars as an alternative to Pandas for reading and writing tabular data. This tutorial builds the same PnL aggregation model from Wrapping Models & Using Data, using Polars instead of Pandas, to show how the two compare.

Reading and writing Polars tables

To work with Polars instead of Pandas, use get_polars_table() and put_polars_table() in place of the Pandas equivalents. Everything else about defining inputs and outputs is unchanged - the schema, parameters and job config are all identical to the Pandas version of this model.

src/tutorial/using_polars.py
 89    def run_model(self, ctx: trac.TracContext):
 90
 91        eur_usd_rate = ctx.get_parameter("eur_usd_rate")
 92        default_weighting = ctx.get_parameter("default_weighting")
 93        filter_defaults = ctx.get_parameter("filter_defaults")
 94
 95        customer_loans = ctx.get_polars_table("customer_loans")
 96
 97        profit_by_region = calculate_profit_by_region_polars(
 98            customer_loans, eur_usd_rate,
 99            default_weighting, filter_defaults)
100
101        ctx.put_polars_table("profit_by_region", profit_by_region)

The calculation itself uses Polars’ lazy API. Building the query with .lazy() defers execution until the result is actually needed, which allows Polars to optimise the whole sequence of operations rather than running each step eagerly. Calling .collect() evaluates the lazy query and returns a materialised dataframe, which is what the runtime expects when saving an output.

24def calculate_profit_by_region_polars(
25        customer_loans: "polars.DataFrame",
26        eur_usd_rate: float,
27        default_weighting: float,
28        filter_defaults: bool):
29
30    if filter_defaults:
31        customer_loans = customer_loans.filter(polars.col("loan_condition_cat") == 0)
32
33    # Build a weighting vector, use default_weighting for bad loans and 1.0 for good loans
34    condition_weighting = customer_loans \
35            .get_column("loan_condition_cat") \
36            .map_elements(lambda c: default_weighting if c > 0 else 1.0) \
37            .cast(polars.Decimal(38, 10))
38
39    # Use lazy processing
40    customer_loans = customer_loans.lazy() \
41            .with_columns(gross_profit_unweighted = (polars.col("total_pymnt") - polars.col("loan_amount"))) \
42            .with_columns(gross_profit_weighted = (polars.col("gross_profit_unweighted") * condition_weighting)) \
43            .with_columns(gross_profit = (polars.col("gross_profit_weighted") * eur_usd_rate))
44
45    profit_by_region = customer_loans \
46        .group_by("region") \
47        .agg(polars.col("gross_profit").sum())
48
49    # Evaluate lazy result before giving the result to TRAC
50    return profit_by_region.collect()

Model attributes

Note

Model attributes are an experimental API that is not yet stabilised, expect changes in future versions of TRAC D.A.P.

Models can define a set of attributes to catalogue and describe themselves, by implementing define_attributes(). Attributes are defined using define_attributes(), which takes a number of individual attributes built with A() (a shorthand alias for define_attribute()).

55    def define_attributes(self) -> tp.List[trac.TagUpdate]:
56
57        return trac.define_attributes(
58            trac.A("model_description", "A example model, for testing purposes"),
59            trac.A("business_segment", "retail_products", categorical=True),
60            trac.A("classifiers", ["loans", "uk", "examples"], attr_type=trac.STRING)
61        )

A name and value are always required for each attribute. Attribute type is optional for single-valued attributes but required for multivalued attributes, such as the classifiers attribute in this example. The categorical flag can be applied to STRING attributes, to mark them for use in categorical searches and filters.

See also

Full source code is available for the Using Polars example on GitHub.