Coverage for mlinsights/mlmodel/target_predictors.py: 94%
85 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-28 08:46 +0100
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-28 08:46 +0100
1"""
2@file
3@brief Implements a slightly different
4version of the :epkg:`sklearn:compose:TransformedTargetRegressor`.
5"""
6from sklearn.base import BaseEstimator, RegressorMixin, ClassifierMixin, clone
7from sklearn.exceptions import NotFittedError
8from sklearn.linear_model import LinearRegression, LogisticRegression
9from sklearn.metrics import r2_score, accuracy_score
10from .sklearn_transform_inv import BaseReciprocalTransformer
11from .sklearn_transform_inv_fct import FunctionReciprocalTransformer, PermutationReciprocalTransformer
14def _common_get_transform(transformer, is_regression):
15 if isinstance(transformer, str):
16 closest = is_regression
17 if transformer == 'permute':
18 return PermutationReciprocalTransformer(closest=closest)
19 else:
20 return FunctionReciprocalTransformer(transformer)
21 elif isinstance(transformer, BaseReciprocalTransformer):
22 return clone(transformer)
23 raise TypeError(
24 f"Transformer {type(transformer)} must be a string or "
25 f"on object of type BaseReciprocalTransformer.")
28class TransformedTargetRegressor2(BaseEstimator, RegressorMixin):
29 """
30 Meta-estimator to regress on a transformed target.
31 Useful for applying a non-linear transformation in regression
32 problems.
34 Parameters
35 ----------
36 regressor : object, default=LinearRegression()
37 Regressor object such as derived from ``RegressorMixin``. This
38 regressor will automatically be cloned each time prior to fitting.
39 transformer : str or object of type @see cl BaseReciprocalTransformer
41 Attributes
42 ----------
43 regressor_ : object
44 Fitted regressor.
45 transformer_ : object
46 Transformer used in ``fit`` and ``predict``.
48 Examples
49 --------
51 .. runpython::
52 :showcode:
54 import numpy
55 from sklearn.linear_model import LinearRegression
56 from mlinsights.mlmodel import TransformedTargetRegressor2
58 tt = TransformedTargetRegressor2(regressor=LinearRegression(),
59 transformer='log')
60 X = numpy.arange(4).reshape(-1, 1)
61 y = numpy.exp(2 * X).ravel()
62 print(tt.fit(X, y))
63 print(tt.score(X, y))
64 print(tt.regressor_.coef_)
66 See notebook :ref:`sklearntransformedtargetrst` for a more complete example.
67 """
69 def __init__(self, regressor=None, transformer=None):
70 self.regressor = regressor
71 self.transformer = transformer
73 def fit(self, X, y, sample_weight=None):
74 """
75 Fits the model according to the given training data.
77 :param X: {array-like, sparse matrix}, shape (n_samples, n_features)
78 Training vector, where n_samples is the number of samples and
79 n_features is the number of features.
80 :param y: array-like, shape (n_samples,)
81 Target values.
82 :param sample_weight: array-like, shape (n_samples,) optional
83 Array of weights that are assigned to individual samples.
84 If not provided, then each sample is given unit weight.
85 :return: self, object
86 """
87 self.transformer_ = _common_get_transform(self.transformer, True)
88 self.transformer_.fit(X, y, sample_weight=sample_weight)
89 X_trans, y_trans = self.transformer_.transform(X, y)
91 if self.regressor is None:
92 self.regressor_ = LinearRegression()
93 else:
94 self.regressor_ = clone(self.regressor)
96 if sample_weight is None:
97 self.regressor_.fit(X_trans, y_trans)
98 else:
99 self.regressor_.fit(X_trans, y_trans, sample_weight=sample_weight)
101 return self
103 def predict(self, X):
104 """
105 Predicts using the base regressor, applying inverse.
107 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features)
108 Samples.
109 :return: y_hat : array, shape = (n_samples,)
110 Predicted values.
111 """
112 if not hasattr(self, 'regressor_'):
113 raise NotFittedError( # pragma: no cover
114 f"This instance {type(self)} is not fitted yet. Call 'fit' with "
115 f"appropriate arguments before using this method.")
116 X_trans, _ = self.transformer_.transform(X, None)
117 pred = self.regressor_.predict(X_trans)
119 inv = self.transformer_.get_fct_inv()
120 _, pred_inv = inv.transform(X_trans, pred)
121 return pred_inv
123 def score(self, X, y, sample_weight=None):
124 """
125 Scores the model with
126 :epkg:`sklearn:metrics:r2_score`.
127 """
128 yp = self.predict(X)
129 return r2_score(y, yp, sample_weight=sample_weight)
131 def _more_tags(self):
132 return {'poor_score': True, 'no_validation': True}
135class TransformedTargetClassifier2(BaseEstimator, ClassifierMixin):
136 """
137 Meta-estimator to classify on a transformed target.
138 Useful for applying permutation transformation in classification
139 problems.
141 Parameters
142 ----------
143 classifier : object, default=LogisticRegression()
144 Classifier object such as derived from ``ClassifierMixin``. This
145 classifier will automatically be cloned each time prior to fitting.
146 transformer : str or object of type @see cl BaseReciprocalTransformer
148 Attributes
149 ----------
150 classifier_ : object
151 Fitted classifier.
152 transformer_ : object
153 Transformer used in ``fit``, ``predict``, ``decision_function``,
154 ``predict_proba``.
156 Examples
157 --------
159 .. runpython::
160 :showcode:
162 import numpy
163 from sklearn.linear_model import LogisticRegression
164 from mlinsights.mlmodel import TransformedTargetClassifier2
166 tt = TransformedTargetClassifier2(classifier=LogisticRegression(),
167 transformer='permute')
168 X = numpy.arange(4).reshape(-1, 1)
169 y = numpy.array([0, 1, 0, 1])
170 print(tt.fit(X, y))
171 print(tt.score(X, y))
172 print(tt.classifier_.coef_)
174 See notebook :ref:`sklearntransformedtargetrst` for a more complete example.
175 """
177 def __init__(self, classifier=None, transformer=None):
178 self.classifier = classifier
179 self.transformer = transformer
181 def fit(self, X, y, sample_weight=None):
182 """
183 Fits the model according to the given training data.
185 :param X: {array-like, sparse matrix}, shape (n_samples, n_features)
186 Training vector, where n_samples is the number of samples and
187 n_features is the number of features.
188 :param y: array-like, shape (n_samples,)
189 Target values.
190 :param sample_weight: array-like, shape (n_samples,) optional
191 Array of weights that are assigned to individual samples.
192 If not provided, then each sample is given unit weight.
193 :return: self, object
194 """
195 self.transformer_ = _common_get_transform(self.transformer, False)
196 self.transformer_.fit(X, y, sample_weight=sample_weight)
197 X_trans, y_trans = self.transformer_.transform(X, y)
199 if self.classifier is None:
200 self.classifier_ = LogisticRegression()
201 else:
202 self.classifier_ = clone(self.classifier)
204 if sample_weight is None:
205 self.classifier_.fit(X_trans, y_trans)
206 else:
207 self.classifier_.fit(X_trans, y_trans, sample_weight=sample_weight)
209 return self
211 def _check_is_fitted(self):
212 if not hasattr(self, 'classifier_'):
213 raise NotFittedError( # pragma: no cover
214 f"This instance {type(self)} is not fitted yet. Call 'fit' with "
215 f"appropriate arguments before using this method.")
217 @property
218 def classes_(self):
219 """
220 Returns the classes.
221 """
222 self._check_is_fitted()
223 inv = self.transformer_.get_fct_inv()
224 _, pred_inv = inv.transform(None, self.classifier_.classes_)
225 return pred_inv
227 def _apply(self, X, method):
228 """
229 Calls *predict*, *predict_proba* or *decision_function*
230 using the base classifier, applying inverse.
232 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features)
233 Samples.
234 :return: y_hat, array, shape = (n_samples,)
235 Predicted values.
236 """
237 self._check_is_fitted()
238 if not hasattr(self.classifier_, method):
239 raise RuntimeError( # pragma: no cover
240 f"Unable to find method {method!r} in model "
241 f"{type(self.classifier_)}.")
242 meth = getattr(self.classifier_, method)
243 X_trans, _ = self.transformer_.transform(X, None)
244 pred = meth(X_trans)
245 inv = self.transformer_.get_fct_inv()
246 _, pred_inv = inv.transform(X_trans, pred)
247 return pred_inv
249 def predict(self, X):
250 """
251 Predicts using the base classifier, applying inverse.
253 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features)
254 Samples.
255 :return: y_hat, array, shape = (n_samples,)
256 Predicted values.
257 """
258 return self._apply(X, 'predict')
260 def predict_proba(self, X):
261 """
262 Predicts using the base classifier, applying inverse.
264 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features)
265 Samples.
266 :return: predict probabilities, array, shape = (n_samples, n_classes)
267 Predicted values.
268 """
269 return self._apply(X, 'predict_proba')
271 def decision_function(self, X):
272 """
273 Predicts using the base classifier, applying inverse.
275 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features)
276 Samples.
277 :return: raw score : array, shape = (n_samples, ?)
278 """
279 return self._apply(X, 'decision_function')
281 def score(self, X, y, sample_weight=None):
282 """
283 Scores the model with
284 :epkg:`sklearn:metrics:accuracy_score`.
285 """
286 yp = self.predict(X)
287 return accuracy_score(y, yp, sample_weight=sample_weight)
289 def _more_tags(self):
290 return {'poor_score': True, 'no_validation': True}