Skip to content

Commit 390fc1c

Browse files
author
Sam Borms
authored
Merge pull request #111 from PythonPredictions/selectable_evaluation_metric
Selectable evaluation metric
2 parents c59da30 + 66672ba commit 390fc1c

3 files changed

Lines changed: 42 additions & 10 deletions

File tree

cobra/model_building/forward_selection.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import logging
3+
from typing import Callable, Optional
34

45
import pandas as pd
56
from tqdm.auto import tqdm
@@ -76,7 +77,8 @@ def get_model_from_step(self, step: int):
7677

7778
def compute_model_performances(self, data: pd.DataFrame,
7879
target_column_name: str,
79-
splits: list=["train", "selection", "validation"]
80+
splits: list = ["train", "selection", "validation"],
81+
metric: Optional[Callable] = None,
8082
) -> pd.DataFrame:
8183
"""Compute for each model the performance for different sets (e.g.
8284
train-selection-validation) and return them along with a list of
@@ -92,6 +94,13 @@ def compute_model_performances(self, data: pd.DataFrame,
9294
Name of the target column.
9395
splits : list, optional
9496
List of splits to compute performance on.
97+
metric: Callable (function), optional
98+
Function that computes an evaluation metric to evaluate the model's
99+
performances, instead of the default metric (AUC for
100+
classification, RMSE for regression).
101+
The function should require y_true and y_pred arguments.
102+
Metric functions from sklearn can be used, for example, see
103+
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics.
95104
96105
Returns
97106
-------
@@ -116,7 +125,8 @@ def compute_model_performances(self, data: pd.DataFrame,
116125
f"{split}_performance": model.evaluate(
117126
data[data["split"] == split],
118127
data[data["split"] == split][target_column_name],
119-
split=split # parameter used for caching
128+
split=split, # parameter used for caching
129+
metric=metric
120130
)
121131
for split in splits
122132
})

cobra/model_building/models.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11

22
# third party imports
3+
from typing import Callable, Optional
4+
35
import numpy as np
46
import pandas as pd
57
from scipy import stats
@@ -144,7 +146,8 @@ def score_model(self, X: pd.DataFrame) -> np.ndarray:
144146
return self.logit.predict_proba(X[self.predictors])[:, 1]
145147

146148
def evaluate(self, X: pd.DataFrame, y: pd.Series,
147-
split: str=None) -> float:
149+
split: str=None,
150+
metric: Optional[Callable]=None) -> float:
148151
"""Evaluate the model on a given data set (X, y). The optional split
149152
parameter is to indicate that the data set belongs to
150153
(train, selection, validation), so that the computation on these sets
@@ -158,18 +161,27 @@ def evaluate(self, X: pd.DataFrame, y: pd.Series,
158161
Dataset containing the target of each observation.
159162
split : str, optional
160163
Split name of the dataset (e.g. "train", "selection", or "validation").
164+
metric: Callable (function), optional
165+
Function that computes an evaluation metric to evaluate the model's
166+
performances, instead of the default metric (AUC).
167+
The function should require y_true and y_pred arguments.
168+
Metric functions from sklearn can be used, for example, see
169+
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics.
161170
162171
Returns
163172
-------
164173
float
165-
The performance score of the model (AUC).
174+
The performance score of the model (AUC by default).
166175
"""
167176

168177
if (split is None) or (split not in self._eval_metrics_by_split):
169178

170179
y_pred = self.score_model(X)
171180

172-
performance = roc_auc_score(y_true=y, y_score=y_pred)
181+
if metric is None:
182+
performance = roc_auc_score(y_true=y, y_score=y_pred)
183+
else:
184+
performance = metric(y_true=y, y_pred=y_pred)
173185

174186
if split is None:
175187
return performance
@@ -357,7 +369,8 @@ def score_model(self, X: pd.DataFrame) -> np.ndarray:
357369
return self.linear.predict(X[self.predictors])
358370

359371
def evaluate(self, X: pd.DataFrame, y: pd.Series,
360-
split: str=None) -> float:
372+
split: str=None,
373+
metric: Optional[Callable]=None) -> float:
361374
"""Evaluate the model on a given data set (X, y). The optional split
362375
parameter is to indicate that the data set belongs to
363376
(train, selection, validation), so that the computation on these sets
@@ -371,18 +384,26 @@ def evaluate(self, X: pd.DataFrame, y: pd.Series,
371384
Dataset containing the target of each observation.
372385
split : str, optional
373386
Split name of the dataset (e.g. "train", "selection", or "validation").
387+
metric: Callable (function), optional
388+
Function that computes an evaluation metric to evaluate the model's
389+
performances, instead of the default metric (RMSE).
390+
The function should require y_true and y_pred arguments.
391+
Metric functions from sklearn can be used, for example, see
392+
https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics.
374393
375394
Returns
376395
-------
377396
float
378-
The performance score of the model (RMSE).
397+
The performance score of the model (RMSE by default).
379398
"""
380399

381400
if (split is None) or (split not in self._eval_metrics_by_split):
382401

383402
y_pred = self.score_model(X)
384-
385-
performance = sqrt(mean_squared_error(y_true=y, y_pred=y_pred))
403+
if metric is None:
404+
performance = sqrt(mean_squared_error(y_true=y, y_pred=y_pred))
405+
else:
406+
performance = metric(y_true=y, y_pred=y_pred)
386407

387408
if split is None:
388409
return performance

tests/model_building/test_forward_selection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ def mock_evaluate(self, X, y, split): # on AUC scale, but gives the same for RM
7676

7777
actual = (fw_selection
7878
.compute_model_performances(data, "target",
79-
splits=["train", "selection"]))
79+
splits=["train", "selection"],
80+
metric=None))
8081

8182
expected = pd.DataFrame([
8283
{"predictors": ["var1_enc"],

0 commit comments

Comments
 (0)