Jonas Meyer-Ohle
03/17/2025, 4:22 PMStefan Krawczyk
03/17/2025, 4:33 PMJonas Meyer-Ohle
03/17/2025, 5:56 PMcustomer_id,segment,age,income,balance,transactions,is_active
1,high_value,56,82943.05863806607,1685.9201673377806,83,True
2,high_value,27,35977.22356123622,1903.5720344516496,61,True
3,high_value,59,49342.221356986505,1550.519270467577,58,True
4,high_value,46,23271.912403430415,2668.6989342760176,66,True
5,high_value,78,40096.08135406822,1789.1313721499523,11,True
6,high_value,36,64040.777196341194,2849.7783030529235,43,True
7,high_value,34,69644.799139234,2796.777957856947,87,True
8,high_value,69,67209.85823731136,3906.28427701675,1,True
The following is an example config file:
metrics:
metric_1:
config:
metric_name: mean
name: mean_income
segment: [segment]
variable: income
description: Mean income by customer segment
dataset: "example_customer_data.csv"
metric_2:
config:
metric_name: mean
name: mean_age
variable: age
description: Mean customer age across all segments
dataset: "example_customer_data.csv"
And how I would want the module to be generated:
import polars as pl
from hamilton.function_modifiers import parameterize, source, value, parameterize_values
from polars.lazyframe.group_by import LazyGroupBy
REQUIRED_COLUMNS = {
"metric_1_required_columns": ["income", "segment"],
"metric_2_required_columns": ["age"],
}
@parameterize_values(parameter="column_names", assigned_output=REQUIRED_COLUMNS)
def required_columns(input_lf: pl.LazyFrame, column_names: list[str]) -> pl.LazyFrame:
return input_lf.select(column_names)
@parameterize(
group_by_metric_1={"input_lf": source("metric_1_required_columns"), "group_by_col": value("segment")},
group_by_metric_2={"input_lf": source("metric_2_required_columns"), "group_by_col": value("none")},
)
def group_by(input_lf: pl.LazyFrame, group_by_col: str) -> LazyGroupBy:
return input_lf.group_by(group_by_col)
# Continue with metric calculation parameterization and result collection
So for any metric that uses the same dataset, I would generate a parameterized function that selects the required columns, then creates a group by which is then used by the actual metric implementation to generate a result. In this case that's just a mean value, but the actual engine has more complicated metrics such as adfuller etc with RAG styling. Data would be loaded and saved using materializers.Elijah Ben Izzy
03/18/2025, 4:15 AM@resolve-based approaches
a. @resolve allows you to take a configuration item an use it to shape the graph
b. E.g. produce a decorator from config
c. In this case you could either:
i. (try this first, see next item) -- Produce the code you have above, with the @parameterize and @parameterize_values wrapped in resolve that would output the decorator with n arguments (one for each metric in groupby/required_columns)
ii. Use a subdag/parameterized_subdag decorator to build out metric subdags
3. Runtime-dynamism -- use ParallelizableElijah Ben Izzy
03/18/2025, 4:16 AMimport polars as pl
from hamilton.function_modifiers import parameterize, source, value, parameterize_values
from polars.lazyframe.group_by import LazyGroupBy
# `required_columns` is a field in config -- this will have one column per each in the field
@resolve(
lambda column_config: parameterize_values(parameter="column_names", assigned_output={key + "_required_columns": [column_config[key][variable], *column_config[key][segment]] for key in column_config}) # one lazyframe each -- looping through your config
)
def required_columns(input_lf: pl.LazyFrame, column_names: list[str]) -> pl.LazyFrame:
return input_lf.select(column_names)
@resolve(
lambda column_config: # up to you to define it
parameterize(
{"input_lf" : source(col + "_required_column", "group_by_col": value(column_config[col]["segment"][0] if "segment" in column_config else "none")) for col in column_config
)
)
def group_by(input_lf: pl.LazyFrame, group_by_col: str) -> LazyGroupBy:
return input_lf.group_by(group_by_col)
# Continue with metric calculation parameterization and result collectionElijah Ben Izzy
03/18/2025, 4:29 AM@resolve -- it's a bit tricky to wrap your head around, but once you do it is super powerfulJonas Meyer-Ohle
03/18/2025, 8:41 AMJonas Meyer-Ohle
03/18/2025, 4:24 PMJonas Meyer-Ohle
03/18/2025, 4:24 PMElijah Ben Izzy
03/18/2025, 6:36 PMJonas Meyer-Ohle
03/19/2025, 8:33 AMStefan Krawczyk
03/20/2025, 12:05 AMsf-hamilton-ui library? or both?Jonas Meyer-Ohle
03/20/2025, 10:51 AMElijah Ben Izzy
03/21/2025, 4:13 AMsf-hamilton-ui==0.0.17 — sorry for the delay!Stefan Krawczyk
03/21/2025, 6:18 AM