| import pandas as pd |
| import numpy as np |
| from typing import List, Optional, Dict, Any |
|
|
|
|
| class LightGBMModel: |
| """ |
| Tree Model 1: LightGBM Regressor / Ranker with constrained depth to prevent overfitting. |
| Falls back gracefully to sklearn HistGradientBoostingRegressor if lightgbm is not installed. |
| """ |
|
|
| def __init__( |
| self, |
| max_depth: int = 3, |
| num_leaves: int = 7, |
| learning_rate: float = 0.01, |
| n_estimators: int = 500, |
| mode: str = "regressor", |
| ): |
| self.max_depth = max_depth |
| self.num_leaves = num_leaves |
| self.learning_rate = learning_rate |
| self.n_estimators = n_estimators |
| self.mode = mode |
| self.model = None |
|
|
| def fit(self, df: pd.DataFrame, feature_cols: List[str], target_col: str): |
| clean_df = df[feature_cols + [target_col, "date"]].dropna() |
| if len(clean_df) < 50: |
| return |
|
|
| X = clean_df[feature_cols] |
| y = clean_df[target_col] |
|
|
| try: |
| import lightgbm as lgb |
| if self.mode == "ranker": |
| clean_df = clean_df.sort_values("date") |
| group_sizes = clean_df.groupby("date").size().values |
| y_quant = clean_df.groupby("date")[target_col].transform( |
| lambda s: pd.qcut(s.rank(method="first"), 5, labels=False) |
| ) |
|
|
| self.model = lgb.LGBMRanker( |
| max_depth=self.max_depth, |
| num_leaves=self.num_leaves, |
| learning_rate=self.learning_rate, |
| n_estimators=self.n_estimators, |
| subsample=0.7, |
| colsample_bytree=0.7, |
| random_state=42, |
| verbosity=-1, |
| ) |
| self.model.fit(clean_df[feature_cols], y_quant, group=group_sizes) |
| else: |
| self.model = lgb.LGBMRegressor( |
| max_depth=self.max_depth, |
| num_leaves=self.num_leaves, |
| learning_rate=self.learning_rate, |
| n_estimators=self.n_estimators, |
| subsample=0.7, |
| colsample_bytree=0.7, |
| random_state=42, |
| verbosity=-1, |
| ) |
| self.model.fit(X, y) |
| except ImportError: |
| |
| from sklearn.ensemble import HistGradientBoostingRegressor |
| self.model = HistGradientBoostingRegressor( |
| max_depth=self.max_depth, |
| learning_rate=self.learning_rate, |
| max_iter=min(200, self.n_estimators), |
| random_state=42, |
| ) |
| self.model.fit(X, y) |
|
|
| def predict(self, df: pd.DataFrame, feature_cols: List[str]) -> pd.Series: |
| if self.model is None: |
| return pd.Series(0.0, index=df.index) |
|
|
| X = df[feature_cols].fillna(0.0) |
| preds = self.model.predict(X) |
| return pd.Series(preds, index=df.index) |
|
|
| def feature_importances(self, feature_cols: List[str]) -> Dict[str, float]: |
| if self.model is None: |
| return {} |
| if hasattr(self.model, "feature_importances_"): |
| imp = self.model.feature_importances_ |
| total = float(imp.sum()) + 1e-8 |
| return {f: float(i / total) for f, i in zip(feature_cols, imp)} |
| return {f: 1.0 / len(feature_cols) for f in feature_cols} |
|
|
|
|
| class XGBoostModel: |
| """ |
| Tree Model 2: XGBoost Regressor with L2 regularization. |
| Falls back gracefully to sklearn GradientBoostingRegressor if xgboost is not installed. |
| """ |
|
|
| def __init__( |
| self, |
| max_depth: int = 3, |
| learning_rate: float = 0.01, |
| n_estimators: int = 500, |
| reg_lambda: float = 1.0, |
| ): |
| self.max_depth = max_depth |
| self.learning_rate = learning_rate |
| self.n_estimators = n_estimators |
| self.reg_lambda = reg_lambda |
| self.model = None |
|
|
| def fit(self, df: pd.DataFrame, feature_cols: List[str], target_col: str): |
| clean_df = df[feature_cols + [target_col]].dropna() |
| if len(clean_df) < 50: |
| return |
|
|
| X = clean_df[feature_cols] |
| y = clean_df[target_col] |
|
|
| try: |
| import xgboost as xgb |
| self.model = xgb.XGBRegressor( |
| max_depth=self.max_depth, |
| learning_rate=self.learning_rate, |
| n_estimators=self.n_estimators, |
| reg_lambda=self.reg_lambda, |
| subsample=0.7, |
| colsample_bytree=0.7, |
| random_state=42, |
| verbosity=0, |
| ) |
| self.model.fit(X, y) |
| except ImportError: |
| from sklearn.ensemble import GradientBoostingRegressor |
| self.model = GradientBoostingRegressor( |
| max_depth=self.max_depth, |
| learning_rate=self.learning_rate, |
| n_estimators=min(200, self.n_estimators), |
| subsample=0.7, |
| random_state=42, |
| ) |
| self.model.fit(X, y) |
|
|
| def predict(self, df: pd.DataFrame, feature_cols: List[str]) -> pd.Series: |
| if self.model is None: |
| return pd.Series(0.0, index=df.index) |
|
|
| X = df[feature_cols].fillna(0.0) |
| preds = self.model.predict(X) |
| return pd.Series(preds, index=df.index) |
|
|