Moral88 0.8.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Moral88/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ from .regression import mean_absolute_error, mean_squared_error, r2_score, mean_bias_deviation, adjusted_r2_score, root_mean_squared_error, mean_absolute_percentage_error, explained_variance_score
Moral88/regression.py CHANGED
@@ -1,405 +1,298 @@
1
1
  import numpy as np
2
- import warnings
3
- from typing import Union, List, Tuple
4
- from scipy import sparse
5
-
6
- class DataValidator:
7
- def __init__(self):
8
- pass
9
-
10
- def check_device_cpu(self, device):
11
- if device not in {"cpu", None}:
12
- raise ValueError(f"Unsupported device: {device!r}. Only 'cpu' is supported.")
13
-
14
- def is_1d_array(self, array: Union[np.ndarray, list], warn: bool = False) -> np.ndarray:
15
- """
16
- Ensures input is a 1D array. Raises an error if it's not 1D or convertible to 1D.
17
- """
18
- array = np.asarray(array)
19
- shape = array.shape
20
-
21
- if len(shape) == 1:
22
- return array
23
- elif len(shape) == 2 and shape[1] == 1:
24
- if warn:
25
- warnings.warn("Input is 2D but will be converted to 1D.", UserWarning)
26
- return array.ravel()
27
- else:
28
- raise ValueError(f"Input must be 1D. Found shape {shape}.")
29
-
30
- def check_samples(self, array: Union[np.ndarray, list]) -> int:
31
- """
32
- Returns the number of samples in the array.
33
- """
34
- array = np.asarray(array)
35
- if hasattr(array, 'shape') and len(array.shape) > 0:
36
- return array.shape[0]
37
- else:
38
- raise TypeError("Input must be an array-like object with at least one dimension.")
39
-
40
- def check_consistent_length(self, *arrays: Union[np.ndarray, list]):
41
- """
42
- Ensures all input arrays have the same length.
43
- """
44
- lengths = [self.check_samples(arr) for arr in arrays]
45
- if len(set(lengths)) > 1:
46
- raise ValueError(f"Inconsistent lengths: {lengths}")
47
-
48
- def validate_regression_targets(self, y_true, y_pred, dtype=np.float64):
49
- """
50
- Ensures regression target values are consistent and converted to the specified dtype.
51
- """
52
- y_true = np.asarray(y_true, dtype=dtype)
53
- y_pred = np.asarray(y_pred, dtype=dtype)
54
-
55
- if y_true.shape != y_pred.shape:
56
- raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
57
-
58
- return y_true, y_pred
59
-
60
- def check_array(self, array, ensure_2d: bool = True, dtype=np.float64, allow_nan: bool = False):
61
- """
62
- Validates input array and converts it to specified dtype.
63
- """
64
- array = np.asarray(array, dtype=dtype)
65
-
66
- if ensure_2d and array.ndim == 1:
67
- array = array.reshape(-1, 1)
68
-
69
- if not allow_nan and np.isnan(array).any():
70
- raise ValueError("Input contains NaN values, which are not allowed.")
71
-
72
- return array
73
-
74
- def check_sparse(self, array, accept_sparse: Tuple[str] = ('csr', 'csc')):
75
- """
76
- Validates sparse matrices and converts to an acceptable format.
77
- """
78
- if sparse.issparse(array):
79
- if array.format not in accept_sparse:
80
- return array.asformat(accept_sparse[0])
81
- return array
82
- else:
83
- raise ValueError("Input is not a sparse matrix.")
84
-
85
- def validate_r2_score_inputs(self, y_true, y_pred, sample_weight=None):
86
- """
87
- Ensures inputs for R2 score computation are valid.
88
- """
89
- y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
90
- if sample_weight is not None:
91
- sample_weight = self.is_1d_array(sample_weight)
92
- return y_true, y_pred, sample_weight
93
-
94
- def validate_mae_mse_inputs(self, y_true, y_pred, library=None):
95
- """
96
- Ensures inputs for MAE and MSE computation are valid.
97
- """
98
- y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
99
- if library not in {None, 'sklearn', 'torch', 'tensorflow', 'Moral88'}:
100
- raise ValueError(f"Invalid library: {library}. Choose from {{'Moral88', 'sklearn', 'torch', 'tensorflow'}}.")
101
- return y_true, y_pred
102
-
103
-
104
- class metrics:
105
- def __init__(self):
106
- pass
107
- def mean_bias_deviation(self, y_true, y_pred, library=None, flatten=True):
108
- """
109
- Computes Mean Bias Deviation (MBD).
110
- """
111
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
112
-
113
- if flatten and y_true.ndim > 1:
2
+ from Moral88.utils import DataValidator
3
+
4
+ validator = DataValidator()
5
+
6
+ def mean_bias_deviation( y_true, y_pred, library=None, flatten=True):
7
+ """
8
+ Computes Mean Bias Deviation (MBD).
9
+ """
10
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
11
+
12
+ if flatten and y_true.ndim > 1:
13
+ y_true = y_true.flatten()
14
+ y_pred = y_pred.flatten()
15
+
16
+ if library == 'sklearn':
17
+ # Sklearn does not have a direct implementation for MBD
18
+ raise NotImplementedError("Mean Bias Deviation is not implemented in sklearn.")
19
+
20
+ if library == 'torch':
21
+ import torch
22
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
23
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
24
+ bias = torch.mean(y_pred_tensor - y_true_tensor).item()
25
+ return bias
26
+
27
+ if library == 'tensorflow':
28
+ import tensorflow as tf
29
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
30
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
31
+ bias = tf.reduce_mean(y_pred_tensor - y_true_tensor).numpy()
32
+ return bias
33
+
34
+ # Default implementation
35
+ return np.mean(y_pred - y_true)
36
+
37
+ def r2_score( y_true, y_pred, sample_weight=None, library=None, flatten=True):
38
+ """
39
+ Computes R2 score.
40
+ """
41
+ y_true, y_pred, sample_weight = validator.validate_r2_score_inputs(y_true, y_pred, sample_weight)
42
+
43
+ if flatten and y_true.ndim > 1:
44
+ y_true = y_true.flatten()
45
+ y_pred = y_pred.flatten()
46
+
47
+ if library == 'sklearn':
48
+ from sklearn.metrics import r2_score as sklearn_r2
49
+ return sklearn_r2(y_true, y_pred, sample_weight=sample_weight)
50
+
51
+ if library == 'statsmodels':
52
+ import statsmodels.api as sm
53
+ model = sm.OLS(y_true, sm.add_constant(y_pred)).fit()
54
+ return model.rsquared
55
+
56
+ numerator = np.sum((y_true - y_pred) ** 2)
57
+ denominator = np.sum((y_true - np.mean(y_true)) ** 2)
58
+
59
+ if denominator == 0:
60
+ return 0.0
61
+ return 1 - (numerator / denominator)
62
+
63
+ def mean_absolute_error( y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
64
+ """
65
+ Computes Mean Absolute Error (MAE).
66
+ """
67
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
68
+
69
+ if flatten:
70
+ y_true = y_true.ravel()
71
+ y_pred = y_pred.ravel()
72
+
73
+ if library == 'Moral88':
74
+ if threshold is not None:
75
+ y_pred = np.clip(y_pred, threshold[0], threshold[1])
76
+
77
+ if y_true.ndim > 1 and flatten:
114
78
  y_true = y_true.flatten()
115
79
  y_pred = y_pred.flatten()
80
+ absolute_errors = np.abs(y_true - y_pred)
81
+
82
+ if method == 'mean':
83
+ result = np.mean(absolute_errors)
84
+ elif method == 'sum':
85
+ result = np.sum(absolute_errors)
86
+ elif method == 'none':
87
+ result = absolute_errors
88
+ else:
89
+ raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
116
90
 
117
- if library == 'sklearn':
118
- # Sklearn does not have a direct implementation for MBD
119
- raise NotImplementedError("Mean Bias Deviation is not implemented in sklearn.")
120
-
121
- if library == 'torch':
122
- import torch
123
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
124
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
125
- bias = torch.mean(y_pred_tensor - y_true_tensor).item()
126
- return bias
127
-
128
- if library == 'tensorflow':
129
- import tensorflow as tf
130
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
131
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
132
- bias = tf.reduce_mean(y_pred_tensor - y_true_tensor).numpy()
133
- return bias
134
-
135
- # Default implementation
136
- return np.mean(y_pred - y_true)
137
- def __init__(self):
138
- self.validator = DataValidator()
139
-
140
- def r2_score(self, y_true, y_pred, sample_weight=None, library=None, flatten=True):
141
- """
142
- Computes R2 score.
143
- """
144
- y_true, y_pred, sample_weight = self.validator.validate_r2_score_inputs(y_true, y_pred, sample_weight)
145
-
146
- if flatten and y_true.ndim > 1:
147
- y_true = y_true.flatten()
148
- y_pred = y_pred.flatten()
91
+ # if normalize and method != 'none':
92
+ # range_y = np.ptp(y_true)
93
+ # result = result / max(abs(range_y), 1)
149
94
 
150
- if library == 'sklearn':
151
- from sklearn.metrics import r2_score as sklearn_r2
152
- return sklearn_r2(y_true, y_pred, sample_weight=sample_weight)
95
+ return result
153
96
 
154
- if library == 'statsmodels':
155
- import statsmodels.api as sm
156
- model = sm.OLS(y_true, sm.add_constant(y_pred)).fit()
157
- return model.rsquared
97
+ elif library == 'sklearn':
98
+ from sklearn.metrics import mean_absolute_error as sklearn_mae
99
+ return sklearn_mae(y_true, y_pred)
158
100
 
159
- numerator = np.sum((y_true - y_pred) ** 2)
160
- denominator = np.sum((y_true - np.mean(y_true)) ** 2)
101
+ elif library == 'torch':
102
+ import torch
103
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
104
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
105
+ return torch.mean(torch.abs(y_true_tensor - y_pred_tensor)).item()
161
106
 
162
- if denominator == 0:
163
- return 0.0
164
- return 1 - (numerator / denominator)
165
-
166
- def mean_absolute_error(self, y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
167
- """
168
- Computes Mean Absolute Error (MAE).
169
- """
170
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
171
-
172
- if flatten:
173
- y_true = y_true.ravel()
174
- y_pred = y_pred.ravel()
175
-
176
- if library == 'Moral88':
177
- if threshold is not None:
178
- y_pred = np.clip(y_pred, threshold[0], threshold[1])
179
-
180
- if y_true.ndim > 1 and flatten:
181
- y_true = y_true.flatten()
182
- y_pred = y_pred.flatten()
183
- absolute_errors = np.abs(y_true - y_pred)
184
-
185
- if method == 'mean':
186
- result = np.mean(absolute_errors)
187
- elif method == 'sum':
188
- result = np.sum(absolute_errors)
189
- elif method == 'none':
190
- result = absolute_errors
191
- else:
192
- raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
193
-
194
- # if normalize and method != 'none':
195
- # range_y = np.ptp(y_true)
196
- # result = result / max(abs(range_y), 1)
197
-
198
- return result
199
-
200
- elif library == 'sklearn':
201
- from sklearn.metrics import mean_absolute_error as sklearn_mae
202
- return sklearn_mae(y_true, y_pred)
203
-
204
- elif library == 'torch':
205
- import torch
206
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
207
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
208
- return torch.mean(torch.abs(y_true_tensor - y_pred_tensor)).item()
209
-
210
- elif library == 'tensorflow':
211
- import tensorflow as tf
212
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
213
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
214
- return tf.reduce_mean(tf.abs(y_true_tensor - y_pred_tensor)).numpy()
215
-
216
- def mean_squared_error(self, y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
217
- """
218
- Computes Mean Squared Error (MSE).
219
- """
220
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
221
-
222
- if flatten:
223
- y_true = y_true.ravel()
224
- y_pred = y_pred.ravel()
225
-
226
- if library == 'Moral88':
227
- if threshold is not None:
228
- y_pred = np.clip(y_pred, threshold[0], threshold[1])
229
-
230
- if y_true.ndim > 1 and flatten:
231
- y_true = y_true.flatten()
232
- y_pred = y_pred.flatten()
233
- squared_errors = (y_true - y_pred) ** 2
234
-
235
- if method == 'mean':
236
- result = np.mean(squared_errors)
237
- elif method == 'sum':
238
- result = np.sum(squared_errors)
239
- elif method == 'none':
240
- result = squared_errors
241
- else:
242
- raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
243
-
244
- # if normalize and method != 'none':
245
- # range_y = np.ptp(y_true)
246
- # result = result / max(abs(range_y), 1)
247
-
248
- return result
249
-
250
- elif library == 'sklearn':
251
- from sklearn.metrics import mean_squared_error as sklearn_mse
252
- return sklearn_mse(y_true, y_pred)
253
-
254
- elif library == 'torch':
255
- import torch
256
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
257
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
258
- return torch.mean((y_true_tensor - y_pred_tensor) ** 2).item()
259
-
260
- elif library == 'tensorflow':
261
- import tensorflow as tf
262
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
263
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
264
- return tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor)).numpy()
265
-
266
- def root_mean_squared_error(self, y_true, y_pred, library=None):
267
- """
268
- Computes Root Mean Squared Error (RMSE).
269
- """
270
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
271
-
272
- if library == 'sklearn':
273
- from sklearn.metrics import mean_squared_error as sklearn_mse
274
- return np.sqrt(sklearn_mse(y_true, y_pred))
275
-
276
- if library == 'torch':
277
- import torch
278
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
279
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
280
- return torch.sqrt(torch.mean((y_true_tensor - y_pred_tensor) ** 2)).item()
281
-
282
- if library == 'tensorflow':
283
- import tensorflow as tf
284
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
285
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
286
- return tf.sqrt(tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor))).numpy()
287
-
288
- mse = self.mean_squared_error(y_true, y_pred)
289
- return np.sqrt(mse)
290
-
291
- def mean_absolute_percentage_error(self, y_true, y_pred, library=None):
292
- """
293
- Computes Mean Absolute Percentage Error (MAPE).
294
- """
295
- y_true, y_pred = self.validator.validate_regression_targets(y_true, y_pred)
296
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
297
- y_true = np.clip(y_true, 1e-8, None)
298
-
299
- if library == 'sklearn':
300
- from sklearn.metrics import mean_absolute_percentage_error as sklearn_mape
301
- return sklearn_mape(y_true, y_pred) * 100
302
-
303
- if library == 'torch':
304
- import torch
305
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
306
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
307
- return torch.mean(torch.abs((y_true_tensor - y_pred_tensor) / torch.clamp(y_true_tensor, min=1e-8))).item() * 100
308
-
309
- if library == 'tensorflow':
310
- import tensorflow as tf
311
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
312
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
313
- return tf.reduce_mean(tf.abs((y_true_tensor - y_pred_tensor) / tf.clip_by_value(y_true_tensor, 1e-8, tf.float32.max))).numpy() * 100
314
-
315
- return np.mean(np.abs((y_true - y_pred) / np.clip(np.abs(y_true), 1e-8, None))) * 100
316
-
317
- def explained_variance_score(self, y_true, y_pred, library=None, flatten=True):
318
- """
319
- Computes Explained Variance Score.
320
- """
321
- y_true, y_pred = self.validator.validate_mae_mse_inputs(y_true, y_pred, library)
322
-
323
- if library == 'sklearn':
324
- from sklearn.metrics import explained_variance_score as sklearn_evs
325
- return sklearn_evs(y_true, y_pred)
326
-
327
- if library == 'torch':
328
- import torch
329
- y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
330
- y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
331
- variance_residual = torch.var(y_true_tensor - y_pred_tensor)
332
- variance_y = torch.var(y_true_tensor)
333
- return 1 - variance_residual / variance_y if variance_y != 0 else 0
334
-
335
- if library == 'tensorflow':
336
- import tensorflow as tf
337
- y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
338
- y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
339
- variance_residual = tf.math.reduce_variance(y_true_tensor - y_pred_tensor)
340
- variance_y = tf.math.reduce_variance(y_true_tensor)
341
- return 1 - variance_residual / variance_y if variance_y != 0 else 0
342
-
343
- numerator = np.var(y_true - y_pred)
344
- denominator = np.var(y_true)
345
- return 1 - numerator / denominator if denominator != 0 else 0
346
-
347
- def adjusted_r2_score(self, y_true, y_pred, n_features, library=None, flatten=True):
348
- """
349
- Computes Adjusted R-Squared Score.
350
-
351
- Parameters:
352
- y_true: array-like of shape (n_samples,)
353
- Ground truth (correct) target values.
354
-
355
- y_pred: array-like of shape (n_samples,)
356
- Estimated target values.
357
-
358
- n_features: int
359
- Number of independent features in the model.
360
-
361
- library: str, optional (default=None)
362
- Library to use for computation. Supports {'sklearn', 'statsmodels', None}.
363
-
364
- flatten: bool, optional (default=True)
365
- If True, flattens multidimensional arrays before computation.
366
- """
367
- # Validate inputs
368
- y_true, y_pred, _ = self.validator.validate_r2_score_inputs(y_true, y_pred)
369
-
370
- # Ensure inputs are 1D arrays
371
- if y_true.ndim == 0 or y_pred.ndim == 0:
372
- y_true = np.array([y_true])
373
- y_pred = np.array([y_pred])
374
-
375
- if flatten and y_true.ndim > 1:
107
+ elif library == 'tensorflow':
108
+ import tensorflow as tf
109
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
110
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
111
+ return tf.reduce_mean(tf.abs(y_true_tensor - y_pred_tensor)).numpy()
112
+
113
+ def mean_squared_error(y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
114
+ """
115
+ Computes Mean Squared Error (MSE).
116
+ """
117
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
118
+
119
+ if flatten:
120
+ y_true = y_true.ravel()
121
+ y_pred = y_pred.ravel()
122
+
123
+ if library == 'Moral88':
124
+ if threshold is not None:
125
+ y_pred = np.clip(y_pred, threshold[0], threshold[1])
126
+
127
+ if y_true.ndim > 1 and flatten:
376
128
  y_true = y_true.flatten()
377
129
  y_pred = y_pred.flatten()
378
-
379
- if library == 'sklearn':
380
- from sklearn.metrics import r2_score
381
- r2 = r2_score(y_true, y_pred)
382
- elif library == 'statsmodels':
383
- import statsmodels.api as sm
384
- X = sm.add_constant(y_pred)
385
- model = sm.OLS(y_true, X).fit()
386
- r2 = model.rsquared
130
+ squared_errors = (y_true - y_pred) ** 2
131
+
132
+ if method == 'mean':
133
+ result = np.mean(squared_errors)
134
+ elif method == 'sum':
135
+ result = np.sum(squared_errors)
136
+ elif method == 'none':
137
+ result = squared_errors
387
138
  else:
388
- numerator = np.sum((y_true - y_pred) ** 2)
389
- denominator = np.sum((y_true - np.mean(y_true)) ** 2)
390
- r2 = 1 - (numerator / denominator) if denominator != 0 else 0.0
139
+ raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
140
+
141
+ return result
142
+
143
+ elif library == 'sklearn':
144
+ from sklearn.metrics import mean_squared_error as sklearn_mse
145
+ return sklearn_mse(y_true, y_pred)
146
+
147
+ elif library == 'torch':
148
+ import torch
149
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
150
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
151
+ return torch.mean((y_true_tensor - y_pred_tensor) ** 2).item()
152
+
153
+ elif library == 'tensorflow':
154
+ import tensorflow as tf
155
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
156
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
157
+ return tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor)).numpy()
158
+
159
+ def root_mean_squared_error(y_true, y_pred, library=None):
160
+ """
161
+ Computes Root Mean Squared Error (RMSE).
162
+ """
163
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
164
+
165
+ if library == 'sklearn':
166
+ from sklearn.metrics import mean_squared_error as sklearn_mse
167
+ return np.sqrt(sklearn_mse(y_true, y_pred))
168
+
169
+ if library == 'torch':
170
+ import torch
171
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
172
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
173
+ return torch.sqrt(torch.mean((y_true_tensor - y_pred_tensor) ** 2)).item()
174
+
175
+ if library == 'tensorflow':
176
+ import tensorflow as tf
177
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
178
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
179
+ return tf.sqrt(tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor))).numpy()
180
+
181
+ mse = mean_squared_error(y_true, y_pred)
182
+ return np.sqrt(mse)
183
+
184
+ def mean_absolute_percentage_error(y_true, y_pred, library=None):
185
+ """
186
+ Computes Mean Absolute Percentage Error (MAPE).
187
+ """
188
+ y_true, y_pred = validator.validate_regression_targets(y_true, y_pred)
189
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
190
+ y_true = np.clip(y_true, 1e-8, None)
191
+
192
+ if library == 'sklearn':
193
+ from sklearn.metrics import mean_absolute_percentage_error as sklearn_mape
194
+ return sklearn_mape(y_true, y_pred) * 100
195
+
196
+ if library == 'torch':
197
+ import torch
198
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
199
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
200
+ return torch.mean(torch.abs((y_true_tensor - y_pred_tensor) / torch.clamp(y_true_tensor, min=1e-8))).item() * 100
201
+
202
+ if library == 'tensorflow':
203
+ import tensorflow as tf
204
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
205
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
206
+ return tf.reduce_mean(tf.abs((y_true_tensor - y_pred_tensor) / tf.clip_by_value(y_true_tensor, 1e-8, tf.float32.max))).numpy() * 100
207
+
208
+ return np.mean(np.abs((y_true - y_pred) / np.clip(np.abs(y_true), 1e-8, None))) * 100
209
+
210
+ def explained_variance_score(y_true, y_pred, library=None, flatten=True):
211
+ """
212
+ Computes Explained Variance Score.
213
+ """
214
+ y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
215
+
216
+ if library == 'sklearn':
217
+ from sklearn.metrics import explained_variance_score as sklearn_evs
218
+ return sklearn_evs(y_true, y_pred)
219
+
220
+ if library == 'torch':
221
+ import torch
222
+ y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
223
+ y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
224
+ variance_residual = torch.var(y_true_tensor - y_pred_tensor)
225
+ variance_y = torch.var(y_true_tensor)
226
+ return 1 - variance_residual / variance_y if variance_y != 0 else 0
227
+
228
+ if library == 'tensorflow':
229
+ import tensorflow as tf
230
+ y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
231
+ y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
232
+ variance_residual = tf.math.reduce_variance(y_true_tensor - y_pred_tensor)
233
+ variance_y = tf.math.reduce_variance(y_true_tensor)
234
+ return 1 - variance_residual / variance_y if variance_y != 0 else 0
235
+
236
+ numerator = np.var(y_true - y_pred)
237
+ denominator = np.var(y_true)
238
+ return 1 - numerator / denominator if denominator != 0 else 0
239
+
240
+ def adjusted_r2_score(y_true, y_pred, n_features, library=None, flatten=True):
241
+ """
242
+ Computes Adjusted R-Squared Score.
243
+
244
+ Parameters:
245
+ y_true: array-like of shape (n_samples,)
246
+ Ground truth (correct) target values.
247
+
248
+ y_pred: array-like of shape (n_samples,)
249
+ Estimated target values.
250
+
251
+ n_features: int
252
+ Number of independent features in the model.
253
+
254
+ library: str, optional (default=None)
255
+ Library to use for computation. Supports {'sklearn', 'statsmodels', None}.
256
+
257
+ flatten: bool, optional (default=True)
258
+ If True, flattens multidimensional arrays before computation.
259
+ """
260
+ # Validate inputs
261
+ y_true, y_pred, _ = validator.validate_r2_score_inputs(y_true, y_pred)
262
+
263
+ # Ensure inputs are 1D arrays
264
+ if y_true.ndim == 0 or y_pred.ndim == 0:
265
+ y_true = np.array([y_true])
266
+ y_pred = np.array([y_pred])
267
+
268
+ if flatten and y_true.ndim > 1:
269
+ y_true = y_true.flatten()
270
+ y_pred = y_pred.flatten()
271
+
272
+ if library == 'sklearn':
273
+ from sklearn.metrics import r2_score
274
+ r2 = r2_score(y_true, y_pred)
275
+ elif library == 'statsmodels':
276
+ import statsmodels.api as sm
277
+ X = sm.add_constant(y_pred)
278
+ model = sm.OLS(y_true, X).fit()
279
+ r2 = model.rsquared
280
+ else:
281
+ numerator = np.sum((y_true - y_pred) ** 2)
282
+ denominator = np.sum((y_true - np.mean(y_true)) ** 2)
283
+ r2 = 1 - (numerator / denominator) if denominator != 0 else 0.0
391
284
 
392
- n_samples = len(y_true)
393
- if n_samples <= n_features + 1:
394
- raise ValueError("Number of samples must be greater than number of features plus one for adjusted R-squared computation.")
285
+ n_samples = len(y_true)
286
+ if n_samples <= n_features + 1:
287
+ raise ValueError("Number of samples must be greater than number of features plus one for adjusted R-squared computation.")
395
288
 
396
- adjusted_r2 = 1 - (1 - r2) * (n_samples - 1) / (n_samples - n_features - 1)
397
- return adjusted_r2
289
+ adjusted_r2 = 1 - (1 - r2) * (n_samples - 1) / (n_samples - n_features - 1)
290
+ return adjusted_r2
398
291
 
399
292
  if __name__ == '__main__':
400
- # Example usage
293
+ # Example usage
401
294
  validator = DataValidator()
402
- metrics = Metrics()
295
+
403
296
 
404
297
  # Test validation
405
298
  arr = [[1], [2], [3]]
@@ -410,12 +303,12 @@ if __name__ == '__main__':
410
303
  y_true = [3, -0.5, 2, 7]
411
304
  y_pred = [2.5, 0.0, 2, 8]
412
305
 
413
- print("Mean Absolute Error:", metrics.mean_absolute_error(y_true, y_pred))
414
- print("Mean Squared Error:", metrics.mean_squared_error(y_true, y_pred))
415
- print("R2 Score:", metrics.r2_score(y_true, y_pred))
416
- print("Mean Bias Deviation: ", metrics.mean_bias_deviation(y_true, y_pred))
417
- print("Explained Variance Score: ", metrics.explained_variance_score(y_true, y_pred))
418
- print("Mean Absolute Percentage Error: ", metrics.mean_absolute_percentage_error(y_true, y_pred))
419
- print("Root Mean Squared Error: ", metrics.root_mean_squared_error(y_true, y_pred))
420
- print("adjusted_r2_score: ", metrics.adjusted_r2_score(y_true, y_pred, 2))
306
+ print("Mean Absolute Error:", mean_absolute_error(y_true, y_pred))
307
+ print("Mean Squared Error:", mean_squared_error(y_true, y_pred))
308
+ print("R2 Score:", r2_score(y_true, y_pred))
309
+ print("Mean Bias Deviation: ", mean_bias_deviation(y_true, y_pred))
310
+ print("Explained Variance Score: ", explained_variance_score(y_true, y_pred))
311
+ print("Mean Absolute Percentage Error: ", mean_absolute_percentage_error(y_true, y_pred))
312
+ print("Root Mean Squared Error: ", root_mean_squared_error(y_true, y_pred))
313
+ print("adjusted_r2_score: ", adjusted_r2_score(y_true, y_pred, 2))
421
314
 
Moral88/utils.py ADDED
@@ -0,0 +1,101 @@
1
+ import numpy as np
2
+ import warnings
3
+ from typing import Union, List, Tuple
4
+ from scipy import sparse
5
+
6
+ class DataValidator:
7
+ def __init__(self):
8
+ pass
9
+
10
+ def check_device_cpu(self, device):
11
+ if device not in {"cpu", None}:
12
+ raise ValueError(f"Unsupported device: {device!r}. Only 'cpu' is supported.")
13
+
14
+ def is_1d_array(self, array: Union[np.ndarray, list], warn: bool = False) -> np.ndarray:
15
+ """
16
+ Ensures input is a 1D array. Raises an error if it's not 1D or convertible to 1D.
17
+ """
18
+ array = np.asarray(array)
19
+ shape = array.shape
20
+
21
+ if len(shape) == 1:
22
+ return array
23
+ elif len(shape) == 2 and shape[1] == 1:
24
+ if warn:
25
+ warnings.warn("Input is 2D but will be converted to 1D.", UserWarning)
26
+ return array.ravel()
27
+ else:
28
+ raise ValueError(f"Input must be 1D. Found shape {shape}.")
29
+
30
+ def check_samples(self, array: Union[np.ndarray, list]) -> int:
31
+ """
32
+ Returns the number of samples in the array.
33
+ """
34
+ array = np.asarray(array)
35
+ if hasattr(array, 'shape') and len(array.shape) > 0:
36
+ return array.shape[0]
37
+ else:
38
+ raise TypeError("Input must be an array-like object with at least one dimension.")
39
+
40
+ def check_consistent_length(self, *arrays: Union[np.ndarray, list]):
41
+ """
42
+ Ensures all input arrays have the same length.
43
+ """
44
+ lengths = [self.check_samples(arr) for arr in arrays]
45
+ if len(set(lengths)) > 1:
46
+ raise ValueError(f"Inconsistent lengths: {lengths}")
47
+
48
+ def validate_regression_targets(self, y_true, y_pred, dtype=np.float64):
49
+ """
50
+ Ensures regression target values are consistent and converted to the specified dtype.
51
+ """
52
+ y_true = np.asarray(y_true, dtype=dtype)
53
+ y_pred = np.asarray(y_pred, dtype=dtype)
54
+
55
+ if y_true.shape != y_pred.shape:
56
+ raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
57
+
58
+ return y_true, y_pred
59
+
60
+ def check_array(self, array, ensure_2d: bool = True, dtype=np.float64, allow_nan: bool = False):
61
+ """
62
+ Validates input array and converts it to specified dtype.
63
+ """
64
+ array = np.asarray(array, dtype=dtype)
65
+
66
+ if ensure_2d and array.ndim == 1:
67
+ array = array.reshape(-1, 1)
68
+
69
+ if not allow_nan and np.isnan(array).any():
70
+ raise ValueError("Input contains NaN values, which are not allowed.")
71
+
72
+ return array
73
+
74
+ def check_sparse(self, array, accept_sparse: Tuple[str] = ('csr', 'csc')):
75
+ """
76
+ Validates sparse matrices and converts to an acceptable format.
77
+ """
78
+ if sparse.issparse(array):
79
+ if array.format not in accept_sparse:
80
+ return array.asformat(accept_sparse[0])
81
+ return array
82
+ else:
83
+ raise ValueError("Input is not a sparse matrix.")
84
+
85
+ def validate_r2_score_inputs(self, y_true, y_pred, sample_weight=None):
86
+ """
87
+ Ensures inputs for R2 score computation are valid.
88
+ """
89
+ y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
90
+ if sample_weight is not None:
91
+ sample_weight = self.is_1d_array(sample_weight)
92
+ return y_true, y_pred, sample_weight
93
+
94
+ def validate_mae_mse_inputs(self, y_true, y_pred, library=None):
95
+ """
96
+ Ensures inputs for MAE and MSE computation are valid.
97
+ """
98
+ y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
99
+ if library not in {None, 'sklearn', 'torch', 'tensorflow', 'Moral88'}:
100
+ raise ValueError(f"Invalid library: {library}. Choose from {{'Moral88', 'sklearn', 'torch', 'tensorflow'}}.")
101
+ return y_true, y_pred
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Moral88
3
- Version: 0.8.0
3
+ Version: 0.10.0
4
4
  Summary: A library for regression evaluation metrics.
5
5
  Author: Morteza Alizadeh
6
6
  Author-email: alizadeh.c2m@gmail.com
@@ -0,0 +1,10 @@
1
+ Moral88/__init__.py,sha256=Z7iEZUqslxRyJU2to6iX6a5Ak1XBZxU3VT4RvOCjsEU,196
2
+ Moral88/regression.py,sha256=WjNMpX0t99KGTrUKMBFg6LccnPvlnWKnjimu65BLrkc,12061
3
+ Moral88/utils.py,sha256=ggiiY5Vp6A6MbGtghftkM0MJM0R9hhR2avUbpV43_yk,3933
4
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ tests/test_regression.py,sha256=w5A6eGTmVuh-eN0nTACPoQzzrX2wI5McyQuMyCvf07M,3122
6
+ Moral88-0.10.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ Moral88-0.10.0.dist-info/METADATA,sha256=6YVHD8ZRgbJ-4lTVmQnJS7caxgRvHGirWgNLHDtSNPw,408
8
+ Moral88-0.10.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
9
+ Moral88-0.10.0.dist-info/top_level.txt,sha256=gg4pKIcQal4JhJAb77H5W6SHC77e-BeLTy4hxfXwmfw,14
10
+ Moral88-0.10.0.dist-info/RECORD,,
tests/__init__.py ADDED
File without changes
@@ -0,0 +1,99 @@
1
+ import pytest
2
+ import numpy as np
3
+ from Moral88.regression import (
4
+ mean_absolute_error,
5
+ mean_absolute_error,
6
+ mean_squared_error,
7
+ r2_score,
8
+ mean_bias_deviation,
9
+ adjusted_r2_score,
10
+ root_mean_squared_error,
11
+ mean_absolute_percentage_error,
12
+ explained_variance_score
13
+ )
14
+ import warnings
15
+ from Moral88.utils import DataValidator
16
+
17
+ validator = DataValidator()
18
+
19
+ def test_is_1d_array():
20
+ validator = DataValidator()
21
+ array = [[1], [2], [3]]
22
+ with warnings.catch_warnings():
23
+ warnings.simplefilter("ignore", UserWarning)
24
+ result = validator.is_1d_array(array, warn=True)
25
+ assert result.ndim == 1
26
+ assert np.array_equal(result, np.array([1, 2, 3]))
27
+
28
+ def test_check_samples():
29
+ validator = DataValidator()
30
+ array = [[1, 2], [3, 4], [5, 6]]
31
+ result = validator.check_samples(array)
32
+ assert result == 3
33
+
34
+ def test_check_consistent_length():
35
+ validator = DataValidator()
36
+ array1 = [1, 2, 3]
37
+ array2 = [4, 5, 6]
38
+ validator.check_consistent_length(array1, array2) # Should not raise an error
39
+
40
+ array3 = [7, 8]
41
+ with pytest.raises(ValueError):
42
+ validator.check_consistent_length(array1, array3)
43
+
44
+ def test_mean_absolute_error():
45
+
46
+ y_true = [3, -0.5, 2, 7]
47
+ y_pred = [2.5, 0.0, 2, 8]
48
+ result = mean_absolute_error(y_true, y_pred)
49
+ assert result == pytest.approx(0.5, rel=1e-2)
50
+
51
+ def test_mean_squared_error():
52
+
53
+ y_true = [3, -0.5, 2, 7]
54
+ y_pred = [2.5, 0.0, 2, 8]
55
+ result = mean_squared_error(y_true, y_pred)
56
+ assert result == pytest.approx(0.375, rel=1e-2)
57
+
58
+ def test_r2_score():
59
+
60
+ y_true = [3, -0.5, 2, 7]
61
+ y_pred = [2.5, 0.0, 2, 8]
62
+ result = r2_score(y_true, y_pred)
63
+ assert result == pytest.approx(0.948, rel=1e-2)
64
+
65
+ def test_mean_bias_deviation():
66
+
67
+ y_true = [3, -0.5, 2, 7]
68
+ y_pred = [2.5, 0.0, 2, 8]
69
+ result = mean_bias_deviation(y_true, y_pred)
70
+ assert result == pytest.approx(0.25, rel=1e-2)
71
+
72
+ def test_explained_variance_score():
73
+
74
+ y_true = [3, -0.5, 2, 7]
75
+ y_pred = [2.5, 0.0, 2, 8]
76
+ result = explained_variance_score(y_true, y_pred)
77
+ assert result == pytest.approx(0.957, rel=1e-2)
78
+
79
+ def test_mean_absolute_percentage_error():
80
+
81
+ y_true = [3, -0.5, 2, 7]
82
+ y_pred = [2.5, 0.0, 2, 8]
83
+ result = mean_absolute_percentage_error(y_true, y_pred)
84
+ assert result == pytest.approx(32.738095, rel=1e-2)
85
+
86
+ def test_root_mean_squared_error():
87
+
88
+ y_true = [3, -0.5, 2, 7]
89
+ y_pred = [2.5, 0.0, 2, 8]
90
+ result = root_mean_squared_error(y_true, y_pred)
91
+ assert result == pytest.approx(0.612, rel=1e-2)
92
+
93
+ def test_adjusted_r2_score():
94
+
95
+ y_true = [3, -0.5, 2, 7]
96
+ y_pred = [2.5, 0.0, 2, 8]
97
+ n_features = 2
98
+ result = adjusted_r2_score(y_true, y_pred, n_features)
99
+ assert result == pytest.approx(0.8458, rel=1e-2)
Moral88/segmentation.py DELETED
@@ -1,38 +0,0 @@
1
- def validate_segmentation_inputs(y_true, y_pred):
2
- """
3
- Validate the inputs for type and shape.
4
- """
5
- if not isinstance(y_true, (list, tuple)) or not isinstance(y_pred, (list, tuple)):
6
- raise TypeError("Both y_true and y_pred must be lists or tuples.")
7
-
8
- if len(y_true) != len(y_pred):
9
- raise ValueError("Length of y_true and y_pred must be the same.")
10
-
11
- if not all(isinstance(x, (int, float)) for x in y_true + y_pred):
12
- raise TypeError("All elements in y_true and y_pred must be numeric.")
13
-
14
- def dice_score(y_true, y_pred, threshold=0.5):
15
- """
16
- Compute the Dice Score.
17
-
18
- Args:
19
- y_true (list or tuple): Ground truth binary values.
20
- y_pred (list or tuple): Predicted values (probabilities or binary).
21
- threshold (float): Threshold to binarize y_pred if necessary.
22
-
23
- Returns:
24
- float: Dice Score.
25
- """
26
- validate_segmentation_inputs(y_true, y_pred)
27
-
28
- # Binarize predictions based on threshold
29
- y_pred = [1 if p >= threshold else 0 for p in y_pred]
30
-
31
- # Calculate intersection and union
32
- intersection = sum(yt * yp for yt, yp in zip(y_true, y_pred))
33
- total = sum(y_true) + sum(y_pred)
34
-
35
- if total == 0:
36
- return 1.0 # Perfect match if both are completely empty
37
-
38
- return 2 * intersection / total
@@ -1,8 +0,0 @@
1
- Moral88/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- Moral88/regression.py,sha256=V4Iqug1rkACBvHoKZ2-4-CK-XK6VP20qok8rWqWjEdE,17302
3
- Moral88/segmentation.py,sha256=v0yqxdrKbM9LM7wVKLjJ4HrhrSrilNNeWS6-oK_27Ag,1363
4
- Moral88-0.8.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- Moral88-0.8.0.dist-info/METADATA,sha256=rlMlw0Mt2e202Je7YqkoWNFNmsieOKfleNM2DcelDGw,407
6
- Moral88-0.8.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
7
- Moral88-0.8.0.dist-info/top_level.txt,sha256=-dyn5iTprnSUHbtMpvRO-prJsIoaRxao7wlfCHLSsv4,8
8
- Moral88-0.8.0.dist-info/RECORD,,