File size: 5,446 Bytes
b54319d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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:
            # Fallback to sklearn HistGradientBoostingRegressor
            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)