bikes.core.models

Define trainable machine learning models.

  1"""Define trainable machine learning models."""
  2
  3# %% IMPORTS
  4
  5import abc
  6import typing as T
  7
  8import pandas as pd
  9import pydantic as pdt
 10import shap
 11import skops.io as sio
 12from sklearn import compose, ensemble, pipeline, preprocessing
 13from sklearn.base import BaseEstimator, RegressorMixin
 14
 15from bikes.core import schemas
 16
 17# %% TYPES
 18
 19# Model params
 20ParamKey = str
 21ParamValue = T.Any
 22Params = dict[ParamKey, ParamValue]
 23
 24# %% MODELS
 25
 26
 27class _RegressorTagger(RegressorMixin, BaseEstimator):
 28    """Source of default scikit-learn regressor tags (sklearn >= 1.6 tags API)."""
 29
 30
 31class Model(abc.ABC, pdt.BaseModel, strict=True, frozen=False, extra="forbid"):
 32    """Base class for a project model.
 33
 34    Use a model to adapt AI/ML frameworks.
 35    e.g., to swap easily one model with another.
 36    """
 37
 38    KIND: str
 39
 40    def __sklearn_tags__(self) -> T.Any:
 41        """Return scikit-learn estimator tags.
 42
 43        Meta-estimators (e.g. GridSearchCV) query this since scikit-learn 1.6. The
 44        project Model is a Pydantic adapter rather than a scikit-learn BaseEstimator,
 45        so it borrows the default regressor tags.
 46
 47        Returns:
 48            T.Any: scikit-learn estimator tags for a regressor.
 49        """
 50        return _RegressorTagger().__sklearn_tags__()
 51
 52    def get_params(self, deep: bool = True) -> Params:  # noqa: ARG002  # sklearn get_params interface
 53        """Get the model params.
 54
 55        Args:
 56            deep (bool, optional): ignored.
 57
 58        Returns:
 59            Params: internal model parameters.
 60        """
 61        params: Params = {}
 62        for key, value in self.model_dump().items():
 63            if not key.startswith("_") and not key.isupper():
 64                params[key] = value
 65        return params
 66
 67    def set_params(self, **params: ParamValue) -> T.Self:
 68        """Set the model params in place.
 69
 70        Returns:
 71            T.Self: instance of the model.
 72        """
 73        for key, value in params.items():
 74            setattr(self, key, value)
 75        return self
 76
 77    @abc.abstractmethod
 78    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> T.Self:
 79        """Fit the model on the given inputs and targets.
 80
 81        Args:
 82            inputs (schemas.Inputs): model training inputs.
 83            targets (schemas.Targets): model training targets.
 84
 85        Returns:
 86            T.Self: instance of the model.
 87        """
 88
 89    @abc.abstractmethod
 90    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
 91        """Generate outputs with the model for the given inputs.
 92
 93        Args:
 94            inputs (schemas.Inputs): model prediction inputs.
 95
 96        Returns:
 97            schemas.Outputs: model prediction outputs.
 98        """
 99
100    def explain_model(self) -> schemas.FeatureImportances:
101        """Explain the internal model structure.
102
103        Returns:
104            schemas.FeatureImportances: feature importances.
105        """
106        raise NotImplementedError
107
108    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
109        """Explain model outputs on input samples.
110
111        Returns:
112            schemas.SHAPValues: SHAP values.
113        """
114        raise NotImplementedError
115
116    def get_internal_model(self) -> T.Any:
117        """Return the internal model in the object.
118
119        Raises:
120            NotImplementedError: method not implemented.
121
122        Returns:
123            T.Any: any internal model (either empty or fitted).
124        """
125        raise NotImplementedError
126
127    def save_internal_model(self, path: str) -> None:
128        """Save the fitted internal model to a file in a safe (non-pickle) format.
129
130        Args:
131            path (str): file path to write.
132
133        Raises:
134            NotImplementedError: method not implemented.
135        """
136        raise NotImplementedError
137
138    def load_internal_model(self, path: str) -> T.Self:
139        """Load the internal model from a file written by `save_internal_model`.
140
141        Args:
142            path (str): file path to read.
143
144        Raises:
145            NotImplementedError: method not implemented.
146
147        Returns:
148            T.Self: instance of the model.
149        """
150        raise NotImplementedError
151
152
153class BaselineSklearnModel(Model):
154    """Simple baseline model based on scikit-learn.
155
156    Parameters:
157        max_depth (int): maximum depth of the random forest.
158        n_estimators (int): number of estimators in the random forest.
159        random_state (int, optional): random state of the machine learning pipeline.
160    """
161
162    KIND: T.Literal["BaselineSklearnModel"] = "BaselineSklearnModel"
163
164    # skops only reloads the types it knows are safe. Tree storage holds raw node indices
165    # that scikit-learn reads without bounds checks, so it is trusted explicitly here,
166    # for files this project writes, instead of trusting whatever a file declares.
167    TRUSTED_TYPES: T.ClassVar[list[str]] = ["sklearn.tree._tree.Tree"]
168
169    # params
170    max_depth: int = 20
171    n_estimators: int = 200
172    random_state: int | None = 42
173    # private
174    _pipeline: pipeline.Pipeline | None = None
175    _numericals: list[str] = [
176        "yr",
177        "mnth",
178        "hr",
179        "holiday",
180        "weekday",
181        "workingday",
182        "temp",
183        "atemp",
184        "hum",
185        "windspeed",
186        # casual and registered are left out: they add up to the target (cnt = casual + registered)
187    ]
188    _categoricals: list[str] = [
189        "season",
190        "weathersit",
191    ]
192
193    @T.override
194    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> BaselineSklearnModel:
195        if not inputs.index.equals(targets.index):
196            raise ValueError("Input and target sample IDs must match in the same order.")
197        # subcomponents
198        categoricals_transformer = preprocessing.OneHotEncoder(sparse_output=False, handle_unknown="ignore")
199        # components
200        transformer = compose.ColumnTransformer(
201            [
202                ("categoricals", categoricals_transformer, self._categoricals),
203                ("numericals", "passthrough", self._numericals),
204            ],
205            remainder="drop",
206        )
207        regressor = ensemble.RandomForestRegressor(
208            max_depth=self.max_depth,
209            n_estimators=self.n_estimators,
210            random_state=self.random_state,
211        )
212        # pipeline
213        self._pipeline = pipeline.Pipeline(
214            steps=[
215                ("transformer", transformer),
216                ("regressor", regressor),
217            ]
218        )
219        self._pipeline.fit(X=inputs, y=targets[schemas.TargetsSchema.cnt])
220        return self
221
222    @T.override
223    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
224        model = self.get_internal_model()
225        # round to the nearest count: the uint32 output schema would otherwise truncate
226        prediction = model.predict(inputs).round()
227        outputs_ = pd.DataFrame(data={schemas.OutputsSchema.prediction: prediction}, index=inputs.index)
228        return schemas.OutputsSchema.check(data=outputs_)
229
230    @T.override
231    def explain_model(self) -> schemas.FeatureImportances:
232        model = self.get_internal_model()
233        regressor = model.named_steps["regressor"]
234        transformer = model.named_steps["transformer"]
235        feature = transformer.get_feature_names_out()
236        feature_importances_ = pd.DataFrame(
237            data={
238                "feature": feature,
239                "importance": regressor.feature_importances_,
240            }
241        )
242        return schemas.FeatureImportancesSchema.check(data=feature_importances_)
243
244    @T.override
245    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
246        model = self.get_internal_model()
247        regressor = model.named_steps["regressor"]
248        transformer = model.named_steps["transformer"]
249        transformed = transformer.transform(X=inputs)
250        explainer = shap.TreeExplainer(model=regressor)
251        shap_values_ = pd.DataFrame(
252            data=explainer.shap_values(X=transformed),
253            columns=transformer.get_feature_names_out(),
254            index=inputs.index,
255        )
256        return schemas.SHAPValuesSchema.check(data=shap_values_)
257
258    @T.override
259    def get_internal_model(self) -> pipeline.Pipeline:
260        model = self._pipeline
261        if model is None:
262            raise ValueError("Model is not fitted yet!")
263        return model
264
265    @T.override
266    def save_internal_model(self, path: str) -> None:
267        sio.dump(self.get_internal_model(), path)
268
269    @T.override
270    def load_internal_model(self, path: str) -> T.Self:
271        self._pipeline = sio.load(path, trusted=self.TRUSTED_TYPES)
272        return self
273
274
275ModelKind = BaselineSklearnModel
ParamKey = <class 'str'>
ParamValue = typing.Any
Params = dict[str, typing.Any]
class Model(abc.ABC, pydantic.main.BaseModel):
 32class Model(abc.ABC, pdt.BaseModel, strict=True, frozen=False, extra="forbid"):
 33    """Base class for a project model.
 34
 35    Use a model to adapt AI/ML frameworks.
 36    e.g., to swap easily one model with another.
 37    """
 38
 39    KIND: str
 40
 41    def __sklearn_tags__(self) -> T.Any:
 42        """Return scikit-learn estimator tags.
 43
 44        Meta-estimators (e.g. GridSearchCV) query this since scikit-learn 1.6. The
 45        project Model is a Pydantic adapter rather than a scikit-learn BaseEstimator,
 46        so it borrows the default regressor tags.
 47
 48        Returns:
 49            T.Any: scikit-learn estimator tags for a regressor.
 50        """
 51        return _RegressorTagger().__sklearn_tags__()
 52
 53    def get_params(self, deep: bool = True) -> Params:  # noqa: ARG002  # sklearn get_params interface
 54        """Get the model params.
 55
 56        Args:
 57            deep (bool, optional): ignored.
 58
 59        Returns:
 60            Params: internal model parameters.
 61        """
 62        params: Params = {}
 63        for key, value in self.model_dump().items():
 64            if not key.startswith("_") and not key.isupper():
 65                params[key] = value
 66        return params
 67
 68    def set_params(self, **params: ParamValue) -> T.Self:
 69        """Set the model params in place.
 70
 71        Returns:
 72            T.Self: instance of the model.
 73        """
 74        for key, value in params.items():
 75            setattr(self, key, value)
 76        return self
 77
 78    @abc.abstractmethod
 79    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> T.Self:
 80        """Fit the model on the given inputs and targets.
 81
 82        Args:
 83            inputs (schemas.Inputs): model training inputs.
 84            targets (schemas.Targets): model training targets.
 85
 86        Returns:
 87            T.Self: instance of the model.
 88        """
 89
 90    @abc.abstractmethod
 91    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
 92        """Generate outputs with the model for the given inputs.
 93
 94        Args:
 95            inputs (schemas.Inputs): model prediction inputs.
 96
 97        Returns:
 98            schemas.Outputs: model prediction outputs.
 99        """
100
101    def explain_model(self) -> schemas.FeatureImportances:
102        """Explain the internal model structure.
103
104        Returns:
105            schemas.FeatureImportances: feature importances.
106        """
107        raise NotImplementedError
108
109    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
110        """Explain model outputs on input samples.
111
112        Returns:
113            schemas.SHAPValues: SHAP values.
114        """
115        raise NotImplementedError
116
117    def get_internal_model(self) -> T.Any:
118        """Return the internal model in the object.
119
120        Raises:
121            NotImplementedError: method not implemented.
122
123        Returns:
124            T.Any: any internal model (either empty or fitted).
125        """
126        raise NotImplementedError
127
128    def save_internal_model(self, path: str) -> None:
129        """Save the fitted internal model to a file in a safe (non-pickle) format.
130
131        Args:
132            path (str): file path to write.
133
134        Raises:
135            NotImplementedError: method not implemented.
136        """
137        raise NotImplementedError
138
139    def load_internal_model(self, path: str) -> T.Self:
140        """Load the internal model from a file written by `save_internal_model`.
141
142        Args:
143            path (str): file path to read.
144
145        Raises:
146            NotImplementedError: method not implemented.
147
148        Returns:
149            T.Self: instance of the model.
150        """
151        raise NotImplementedError

Base class for a project model.

Use a model to adapt AI/ML frameworks. e.g., to swap easily one model with another.

KIND: str = PydanticUndefined
def get_params(self, deep: bool = True) -> dict[str, typing.Any]:
53    def get_params(self, deep: bool = True) -> Params:  # noqa: ARG002  # sklearn get_params interface
54        """Get the model params.
55
56        Args:
57            deep (bool, optional): ignored.
58
59        Returns:
60            Params: internal model parameters.
61        """
62        params: Params = {}
63        for key, value in self.model_dump().items():
64            if not key.startswith("_") and not key.isupper():
65                params[key] = value
66        return params

Get the model params.

Arguments:
  • deep (bool, optional): ignored.
Returns:

Params: internal model parameters.

def set_params(self, **params: Any) -> Self:
68    def set_params(self, **params: ParamValue) -> T.Self:
69        """Set the model params in place.
70
71        Returns:
72            T.Self: instance of the model.
73        """
74        for key, value in params.items():
75            setattr(self, key, value)
76        return self

Set the model params in place.

Returns:

T.Self: instance of the model.

@abc.abstractmethod
def fit( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema], targets: pandera.typing.pandas.DataFrame[bikes.core.schemas.TargetsSchema]) -> Self:
78    @abc.abstractmethod
79    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> T.Self:
80        """Fit the model on the given inputs and targets.
81
82        Args:
83            inputs (schemas.Inputs): model training inputs.
84            targets (schemas.Targets): model training targets.
85
86        Returns:
87            T.Self: instance of the model.
88        """

Fit the model on the given inputs and targets.

Arguments:
  • inputs (schemas.Inputs): model training inputs.
  • targets (schemas.Targets): model training targets.
Returns:

T.Self: instance of the model.

@abc.abstractmethod
def predict( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
90    @abc.abstractmethod
91    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
92        """Generate outputs with the model for the given inputs.
93
94        Args:
95            inputs (schemas.Inputs): model prediction inputs.
96
97        Returns:
98            schemas.Outputs: model prediction outputs.
99        """

Generate outputs with the model for the given inputs.

Arguments:
  • inputs (schemas.Inputs): model prediction inputs.
Returns:

schemas.Outputs: model prediction outputs.

def explain_model( self) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.FeatureImportancesSchema]:
101    def explain_model(self) -> schemas.FeatureImportances:
102        """Explain the internal model structure.
103
104        Returns:
105            schemas.FeatureImportances: feature importances.
106        """
107        raise NotImplementedError

Explain the internal model structure.

Returns:

schemas.FeatureImportances: feature importances.

def explain_samples( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.SHAPValuesSchema]:
109    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
110        """Explain model outputs on input samples.
111
112        Returns:
113            schemas.SHAPValues: SHAP values.
114        """
115        raise NotImplementedError

Explain model outputs on input samples.

Returns:

schemas.SHAPValues: SHAP values.

def get_internal_model(self) -> Any:
117    def get_internal_model(self) -> T.Any:
118        """Return the internal model in the object.
119
120        Raises:
121            NotImplementedError: method not implemented.
122
123        Returns:
124            T.Any: any internal model (either empty or fitted).
125        """
126        raise NotImplementedError

Return the internal model in the object.

Raises:
  • NotImplementedError: method not implemented.
Returns:

T.Any: any internal model (either empty or fitted).

def save_internal_model(self, path: str) -> None:
128    def save_internal_model(self, path: str) -> None:
129        """Save the fitted internal model to a file in a safe (non-pickle) format.
130
131        Args:
132            path (str): file path to write.
133
134        Raises:
135            NotImplementedError: method not implemented.
136        """
137        raise NotImplementedError

Save the fitted internal model to a file in a safe (non-pickle) format.

Arguments:
  • path (str): file path to write.
Raises:
  • NotImplementedError: method not implemented.
def load_internal_model(self, path: str) -> Self:
139    def load_internal_model(self, path: str) -> T.Self:
140        """Load the internal model from a file written by `save_internal_model`.
141
142        Args:
143            path (str): file path to read.
144
145        Raises:
146            NotImplementedError: method not implemented.
147
148        Returns:
149            T.Self: instance of the model.
150        """
151        raise NotImplementedError

Load the internal model from a file written by save_internal_model.

Arguments:
  • path (str): file path to read.
Raises:
  • NotImplementedError: method not implemented.
Returns:

T.Self: instance of the model.

class BaselineSklearnModel(Model):
154class BaselineSklearnModel(Model):
155    """Simple baseline model based on scikit-learn.
156
157    Parameters:
158        max_depth (int): maximum depth of the random forest.
159        n_estimators (int): number of estimators in the random forest.
160        random_state (int, optional): random state of the machine learning pipeline.
161    """
162
163    KIND: T.Literal["BaselineSklearnModel"] = "BaselineSklearnModel"
164
165    # skops only reloads the types it knows are safe. Tree storage holds raw node indices
166    # that scikit-learn reads without bounds checks, so it is trusted explicitly here,
167    # for files this project writes, instead of trusting whatever a file declares.
168    TRUSTED_TYPES: T.ClassVar[list[str]] = ["sklearn.tree._tree.Tree"]
169
170    # params
171    max_depth: int = 20
172    n_estimators: int = 200
173    random_state: int | None = 42
174    # private
175    _pipeline: pipeline.Pipeline | None = None
176    _numericals: list[str] = [
177        "yr",
178        "mnth",
179        "hr",
180        "holiday",
181        "weekday",
182        "workingday",
183        "temp",
184        "atemp",
185        "hum",
186        "windspeed",
187        # casual and registered are left out: they add up to the target (cnt = casual + registered)
188    ]
189    _categoricals: list[str] = [
190        "season",
191        "weathersit",
192    ]
193
194    @T.override
195    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> BaselineSklearnModel:
196        if not inputs.index.equals(targets.index):
197            raise ValueError("Input and target sample IDs must match in the same order.")
198        # subcomponents
199        categoricals_transformer = preprocessing.OneHotEncoder(sparse_output=False, handle_unknown="ignore")
200        # components
201        transformer = compose.ColumnTransformer(
202            [
203                ("categoricals", categoricals_transformer, self._categoricals),
204                ("numericals", "passthrough", self._numericals),
205            ],
206            remainder="drop",
207        )
208        regressor = ensemble.RandomForestRegressor(
209            max_depth=self.max_depth,
210            n_estimators=self.n_estimators,
211            random_state=self.random_state,
212        )
213        # pipeline
214        self._pipeline = pipeline.Pipeline(
215            steps=[
216                ("transformer", transformer),
217                ("regressor", regressor),
218            ]
219        )
220        self._pipeline.fit(X=inputs, y=targets[schemas.TargetsSchema.cnt])
221        return self
222
223    @T.override
224    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
225        model = self.get_internal_model()
226        # round to the nearest count: the uint32 output schema would otherwise truncate
227        prediction = model.predict(inputs).round()
228        outputs_ = pd.DataFrame(data={schemas.OutputsSchema.prediction: prediction}, index=inputs.index)
229        return schemas.OutputsSchema.check(data=outputs_)
230
231    @T.override
232    def explain_model(self) -> schemas.FeatureImportances:
233        model = self.get_internal_model()
234        regressor = model.named_steps["regressor"]
235        transformer = model.named_steps["transformer"]
236        feature = transformer.get_feature_names_out()
237        feature_importances_ = pd.DataFrame(
238            data={
239                "feature": feature,
240                "importance": regressor.feature_importances_,
241            }
242        )
243        return schemas.FeatureImportancesSchema.check(data=feature_importances_)
244
245    @T.override
246    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
247        model = self.get_internal_model()
248        regressor = model.named_steps["regressor"]
249        transformer = model.named_steps["transformer"]
250        transformed = transformer.transform(X=inputs)
251        explainer = shap.TreeExplainer(model=regressor)
252        shap_values_ = pd.DataFrame(
253            data=explainer.shap_values(X=transformed),
254            columns=transformer.get_feature_names_out(),
255            index=inputs.index,
256        )
257        return schemas.SHAPValuesSchema.check(data=shap_values_)
258
259    @T.override
260    def get_internal_model(self) -> pipeline.Pipeline:
261        model = self._pipeline
262        if model is None:
263            raise ValueError("Model is not fitted yet!")
264        return model
265
266    @T.override
267    def save_internal_model(self, path: str) -> None:
268        sio.dump(self.get_internal_model(), path)
269
270    @T.override
271    def load_internal_model(self, path: str) -> T.Self:
272        self._pipeline = sio.load(path, trusted=self.TRUSTED_TYPES)
273        return self

Simple baseline model based on scikit-learn.

Arguments:
  • max_depth (int): maximum depth of the random forest.
  • n_estimators (int): number of estimators in the random forest.
  • random_state (int, optional): random state of the machine learning pipeline.
KIND: Literal['BaselineSklearnModel'] = 'BaselineSklearnModel'
TRUSTED_TYPES: ClassVar[list[str]] = ['sklearn.tree._tree.Tree']
max_depth: int = 20
n_estimators: int = 200
random_state: int | None = 42
@T.override
def fit( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema], targets: pandera.typing.pandas.DataFrame[bikes.core.schemas.TargetsSchema]) -> BaselineSklearnModel:
194    @T.override
195    def fit(self, inputs: schemas.Inputs, targets: schemas.Targets) -> BaselineSklearnModel:
196        if not inputs.index.equals(targets.index):
197            raise ValueError("Input and target sample IDs must match in the same order.")
198        # subcomponents
199        categoricals_transformer = preprocessing.OneHotEncoder(sparse_output=False, handle_unknown="ignore")
200        # components
201        transformer = compose.ColumnTransformer(
202            [
203                ("categoricals", categoricals_transformer, self._categoricals),
204                ("numericals", "passthrough", self._numericals),
205            ],
206            remainder="drop",
207        )
208        regressor = ensemble.RandomForestRegressor(
209            max_depth=self.max_depth,
210            n_estimators=self.n_estimators,
211            random_state=self.random_state,
212        )
213        # pipeline
214        self._pipeline = pipeline.Pipeline(
215            steps=[
216                ("transformer", transformer),
217                ("regressor", regressor),
218            ]
219        )
220        self._pipeline.fit(X=inputs, y=targets[schemas.TargetsSchema.cnt])
221        return self

Fit the model on the given inputs and targets.

Arguments:
  • inputs (schemas.Inputs): model training inputs.
  • targets (schemas.Targets): model training targets.
Returns:

T.Self: instance of the model.

@T.override
def predict( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
223    @T.override
224    def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
225        model = self.get_internal_model()
226        # round to the nearest count: the uint32 output schema would otherwise truncate
227        prediction = model.predict(inputs).round()
228        outputs_ = pd.DataFrame(data={schemas.OutputsSchema.prediction: prediction}, index=inputs.index)
229        return schemas.OutputsSchema.check(data=outputs_)

Generate outputs with the model for the given inputs.

Arguments:
  • inputs (schemas.Inputs): model prediction inputs.
Returns:

schemas.Outputs: model prediction outputs.

@T.override
def explain_model( self) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.FeatureImportancesSchema]:
231    @T.override
232    def explain_model(self) -> schemas.FeatureImportances:
233        model = self.get_internal_model()
234        regressor = model.named_steps["regressor"]
235        transformer = model.named_steps["transformer"]
236        feature = transformer.get_feature_names_out()
237        feature_importances_ = pd.DataFrame(
238            data={
239                "feature": feature,
240                "importance": regressor.feature_importances_,
241            }
242        )
243        return schemas.FeatureImportancesSchema.check(data=feature_importances_)

Explain the internal model structure.

Returns:

schemas.FeatureImportances: feature importances.

@T.override
def explain_samples( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.SHAPValuesSchema]:
245    @T.override
246    def explain_samples(self, inputs: schemas.Inputs) -> schemas.SHAPValues:
247        model = self.get_internal_model()
248        regressor = model.named_steps["regressor"]
249        transformer = model.named_steps["transformer"]
250        transformed = transformer.transform(X=inputs)
251        explainer = shap.TreeExplainer(model=regressor)
252        shap_values_ = pd.DataFrame(
253            data=explainer.shap_values(X=transformed),
254            columns=transformer.get_feature_names_out(),
255            index=inputs.index,
256        )
257        return schemas.SHAPValuesSchema.check(data=shap_values_)

Explain model outputs on input samples.

Returns:

schemas.SHAPValues: SHAP values.

@T.override
def get_internal_model(self) -> sklearn.pipeline.Pipeline:
259    @T.override
260    def get_internal_model(self) -> pipeline.Pipeline:
261        model = self._pipeline
262        if model is None:
263            raise ValueError("Model is not fitted yet!")
264        return model

Return the internal model in the object.

Raises:
  • NotImplementedError: method not implemented.
Returns:

T.Any: any internal model (either empty or fitted).

@T.override
def save_internal_model(self, path: str) -> None:
266    @T.override
267    def save_internal_model(self, path: str) -> None:
268        sio.dump(self.get_internal_model(), path)

Save the fitted internal model to a file in a safe (non-pickle) format.

Arguments:
  • path (str): file path to write.
Raises:
  • NotImplementedError: method not implemented.
@T.override
def load_internal_model(self, path: str) -> Self:
270    @T.override
271    def load_internal_model(self, path: str) -> T.Self:
272        self._pipeline = sio.load(path, trusted=self.TRUSTED_TYPES)
273        return self

Load the internal model from a file written by save_internal_model.

Arguments:
  • path (str): file path to read.
Raises:
  • NotImplementedError: method not implemented.
Returns:

T.Self: instance of the model.

Inherited Members
Model
get_params
set_params
ModelKind = <class 'BaselineSklearnModel'>