Quantile regressions with lift and feed
InĀ [1]:
Copied!
!uv pip install scikit-learn lightgbm pandas pyarrow matplotlib --quiet
!uv pip install scikit-learn lightgbm pandas pyarrow matplotlib --quiet
Using Python 3.14.4 environment at: /home/niksm/code/lefts/.venv
Checked 5 packages in 4ms
InĀ [2]:
Copied!
import io
import urllib.request
import zipfile
import polars as pl
from lightgbm import LGBMRegressor
from lefts import ensemble, leaf, lift
from lefts.helpers import tabular_model
from functools import partial
import datetime as dt
import warnings
import io
import urllib.request
import zipfile
import polars as pl
from lightgbm import LGBMRegressor
from lefts import ensemble, leaf, lift
from lefts.helpers import tabular_model
from functools import partial
import datetime as dt
import warnings
InĀ [3]:
Copied!
warnings.filterwarnings(
"ignore",
message="X does not have valid feature names, but LGBMRegressor was fitted with feature names",
)
warnings.filterwarnings(
"ignore",
message="X does not have valid feature names, but LGBMRegressor was fitted with feature names",
)
InĀ [4]:
Copied!
url = "https://archive.ics.uci.edu/static/public/275/bike+sharing+dataset.zip"
with urllib.request.urlopen(url) as response:
archive = zipfile.ZipFile(io.BytesIO(response.read()))
df = pl.read_csv(io.BytesIO(archive.read("hour.csv")))
df = df.with_columns(
datetime=pl.col("dteday").str.to_datetime() + pl.duration(hours=pl.col("hr"))
).drop("dteday")
url = "https://archive.ics.uci.edu/static/public/275/bike+sharing+dataset.zip"
with urllib.request.urlopen(url) as response:
archive = zipfile.ZipFile(io.BytesIO(response.read()))
df = pl.read_csv(io.BytesIO(archive.read("hour.csv")))
df = df.with_columns(
datetime=pl.col("dteday").str.to_datetime() + pl.duration(hours=pl.col("hr"))
).drop("dteday")
InĀ [5]:
Copied!
features = ["temp", "atemp", "hum", "windspeed", "hr", "weekday", "mnth"]
target = "cnt"
quantiles = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
test_period_start_dates = pl.datetime_range(
start=dt.datetime(2011, 3, 1),
end=dt.datetime(2012, 12, 1),
interval="1mo",
eager=True,
).to_list()
quantile_models = []
for q in quantiles:
# Convert LGBMRegressor into the format required by lefts
base_model = leaf(
tabular_model(
partial(LGBMRegressor, objective="quantile", alpha=q),
features=features,
target=target,
),
label=f"q{q}",
)
# 'Lift' each per-quantile model into a family of models, each with a different train and test period
rolling_retrain = lift(
base_model,
name=f"q{q}_rolling_retrain",
values=test_period_start_dates,
# A row is in a given train period if it is
train_filter=lambda test_period_start_date: (
pl.col("datetime") < test_period_start_date
),
test_filter=lambda test_period_start_date: (
pl.col("datetime").dt.month() == test_period_start_date.month
),
# Coalesce the outputs of the lift into a single column
aggregate_with=pl.coalesce,
)
quantile_models.append(rolling_retrain)
# Stitches all the lifted models into a single ensemble that fits and predicts in parallel.
model = ensemble("quantiles", *quantile_models)
features = ["temp", "atemp", "hum", "windspeed", "hr", "weekday", "mnth"]
target = "cnt"
quantiles = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
test_period_start_dates = pl.datetime_range(
start=dt.datetime(2011, 3, 1),
end=dt.datetime(2012, 12, 1),
interval="1mo",
eager=True,
).to_list()
quantile_models = []
for q in quantiles:
# Convert LGBMRegressor into the format required by lefts
base_model = leaf(
tabular_model(
partial(LGBMRegressor, objective="quantile", alpha=q),
features=features,
target=target,
),
label=f"q{q}",
)
# 'Lift' each per-quantile model into a family of models, each with a different train and test period
rolling_retrain = lift(
base_model,
name=f"q{q}_rolling_retrain",
values=test_period_start_dates,
# A row is in a given train period if it is
train_filter=lambda test_period_start_date: (
pl.col("datetime") < test_period_start_date
),
test_filter=lambda test_period_start_date: (
pl.col("datetime").dt.month() == test_period_start_date.month
),
# Coalesce the outputs of the lift into a single column
aggregate_with=pl.coalesce,
)
quantile_models.append(rolling_retrain)
# Stitches all the lifted models into a single ensemble that fits and predicts in parallel.
model = ensemble("quantiles", *quantile_models)
InĀ [6]:
Copied!
# You can inspect the structure of the full model before running .fit
model.print_tree()
# You can inspect the structure of the full model before running .fit
model.print_tree()
Ensemble 'quantiles' (198 models) ā outputs: [q0.1_rolling_retrain, ..., q0.9_rolling_retrain]
āāā Lift 'q0.1_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.1_rolling_retrain"
ā āāā Leaf 'q0.1'
āāā Lift 'q0.2_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.2_rolling_retrain"
ā āāā Leaf 'q0.2'
āāā Lift 'q0.3_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.3_rolling_retrain"
ā āāā Leaf 'q0.3'
āāā Lift 'q0.4_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.4_rolling_retrain"
ā āāā Leaf 'q0.4'
āāā Lift 'q0.5_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.5_rolling_retrain"
ā āāā Leaf 'q0.5'
āāā Lift 'q0.6_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.6_rolling_retrain"
ā āāā Leaf 'q0.6'
āāā Lift 'q0.7_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.7_rolling_retrain"
ā āāā Leaf 'q0.7'
āāā Lift 'q0.8_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.8_rolling_retrain"
ā āāā Leaf 'q0.8'
āāā Lift 'q0.9_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00] ā coalesce ā "q0.9_rolling_retrain"
āāā Leaf 'q0.9'
InĀ [7]:
Copied!
# Fits |quantiles| x |test_period_start_dates| = 9 * 22 = 198 models
model.fit(df, logging="drop", errors="raise")
# Fits |quantiles| x |test_period_start_dates| = 9 * 22 = 198 models
model.fit(df, logging="drop", errors="raise")
InĀ [8]:
Copied!
# Adds |quantiles| columns, each with the unique prediction associated with that test row.
predictions = model.predict(df, errors="raise")
# Adds |quantiles| columns, each with the unique prediction associated with that test row.
predictions = model.predict(df, errors="raise")
InĀ [10]:
Copied!
predictions.filter(pl.col("datetime").dt.date() == dt.date(2012, 12, 1)).select(
"datetime", *model.collect_labels()
).to_pandas().set_index("datetime").plot(figsize=[15, 4], alpha=0.3)
predictions.filter(pl.col("datetime").dt.date() == dt.date(2012, 12, 1)).select(
"datetime", *model.collect_labels()
).to_pandas().set_index("datetime").plot(figsize=[15, 4], alpha=0.3)
Out[10]:
<Axes: xlabel='datetime'>
InĀ [9]:
Copied!