Moral88 0.2.0__py3-none-any.whl → 0.3.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/regression.py +189 -62
- Moral88/segmentation.py +38 -0
- {Moral88-0.2.0.dist-info → Moral88-0.3.0.dist-info}/METADATA +1 -1
- Moral88-0.3.0.dist-info/RECORD +8 -0
- Moral88-0.2.0.dist-info/RECORD +0 -7
- {Moral88-0.2.0.dist-info → Moral88-0.3.0.dist-info}/LICENSE +0 -0
- {Moral88-0.2.0.dist-info → Moral88-0.3.0.dist-info}/WHEEL +0 -0
- {Moral88-0.2.0.dist-info → Moral88-0.3.0.dist-info}/top_level.txt +0 -0
Moral88/regression.py
CHANGED
@@ -1,36 +1,51 @@
|
|
1
|
-
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
def validate_inputs(y_true, y_pred):
|
4
|
+
"""
|
5
|
+
Validate the inputs for type and length.
|
6
|
+
"""
|
7
|
+
if not isinstance(y_true, (list, tuple, np.ndarray)) or not isinstance(y_pred, (list, tuple, np.ndarray)):
|
8
|
+
raise TypeError("Both y_true and y_pred must be lists, tuples, or numpy arrays.")
|
9
|
+
|
10
|
+
y_true = np.array(y_true)
|
11
|
+
y_pred = np.array(y_pred)
|
12
|
+
|
13
|
+
if y_true.shape != y_pred.shape:
|
14
|
+
raise ValueError("Shapes of y_true and y_pred must be the same.")
|
15
|
+
|
16
|
+
if not np.issubdtype(y_true.dtype, np.number) or not np.issubdtype(y_pred.dtype, np.number):
|
17
|
+
raise TypeError("All elements in y_true and y_pred must be numeric.")
|
18
|
+
|
19
|
+
def mean_absolute_error(y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
|
2
20
|
"""
|
3
|
-
Calculate Mean Absolute Error (MAE)
|
4
|
-
|
5
|
-
Parameters:
|
6
|
-
- y_true (list or array): True values (required)
|
7
|
-
- y_pred (list or array): Predicted values (required)
|
8
|
-
- normalize (bool): If True, normalize the result (default: True)
|
9
|
-
- threshold (tuple, optional): Apply a threshold to the predictions (default: None)
|
10
|
-
- method (str): Method of calculation. Options: {'mean', 'sum', 'none'}. Default: 'mean'
|
11
|
-
- library (str): Library to use for calculations. Options: {'Moral88', 'sklearn', 'torch', 'tensor', 'statsmodel', 'Dask-ML', 'MLlib'}. Default: 'Moral88'.
|
12
|
-
|
13
|
-
Returns:
|
14
|
-
- float or list: Calculated error based on selected method and library.
|
21
|
+
Calculate Mean Absolute Error (MAE) for single or multi-dimensional data.
|
15
22
|
"""
|
23
|
+
validate_inputs(y_true, y_pred)
|
24
|
+
|
25
|
+
# y_true = np.array(y_true)
|
26
|
+
# y_pred = np.array(y_pred)
|
27
|
+
|
28
|
+
if flatten:
|
29
|
+
y_true = y_true.ravel()
|
30
|
+
y_pred = y_pred.ravel()
|
31
|
+
|
16
32
|
if library == 'Moral88':
|
17
|
-
# Original implementation
|
18
33
|
if threshold is not None:
|
19
|
-
y_pred =
|
34
|
+
y_pred = np.clip(y_pred, threshold[0], threshold[1])
|
20
35
|
|
21
|
-
absolute_errors =
|
36
|
+
absolute_errors = np.abs(y_true - y_pred)
|
22
37
|
|
23
38
|
if method == 'mean':
|
24
|
-
result =
|
39
|
+
result = np.mean(absolute_errors)
|
25
40
|
elif method == 'sum':
|
26
|
-
result = sum(absolute_errors)
|
41
|
+
result = np.sum(absolute_errors)
|
27
42
|
elif method == 'none':
|
28
43
|
result = absolute_errors
|
29
44
|
else:
|
30
45
|
raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
|
31
46
|
|
32
47
|
if normalize and method != 'none':
|
33
|
-
range_y =
|
48
|
+
range_y = np.ptp(y_true)
|
34
49
|
result = result / max(abs(range_y), 1)
|
35
50
|
|
36
51
|
return result
|
@@ -51,52 +66,39 @@ def mean_absolute_error(y_true, y_pred, normalize=True, threshold=None, method='
|
|
51
66
|
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
52
67
|
return tf.reduce_mean(tf.abs(y_true_tensor - y_pred_tensor)).numpy()
|
53
68
|
|
54
|
-
# elif library == 'statsmodel':
|
55
|
-
# raise NotImplementedError("Statsmodel does not have a built-in MAE implementation.")
|
56
|
-
|
57
|
-
# elif library == 'Dask-ML':
|
58
|
-
# raise NotImplementedError("Dask-ML support is not implemented yet.")
|
59
|
-
|
60
|
-
# elif library == 'MLlib':
|
61
|
-
# raise NotImplementedError("MLlib support is not implemented yet.")
|
62
|
-
|
63
69
|
else:
|
64
70
|
raise ValueError(f"Invalid library: {library}. Choose from {'Moral88', 'sklearn', 'torch', 'tensorflow'}.")
|
65
71
|
|
66
|
-
|
67
|
-
def mean_squared_error(y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88'):
|
72
|
+
def mean_squared_error(y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
|
68
73
|
"""
|
69
|
-
Calculate Mean Squared Error (MSE)
|
70
|
-
|
71
|
-
Parameters:
|
72
|
-
- y_true (list or array): True values (required)
|
73
|
-
- y_pred (list or array): Predicted values (required)
|
74
|
-
- normalize (bool): If True, normalize the result (default: True)
|
75
|
-
- threshold (tuple, optional): Apply a threshold to the predictions (default: None)
|
76
|
-
- method (str): Method of calculation. Options: {'mean', 'sum', 'none'}. Default: 'mean'
|
77
|
-
- library (str): Library to use for calculations. Options: {'Moral88', 'sklearn', 'torch', 'tensor', 'statsmodel', 'Dask-ML', 'MLlib'}. Default: 'Moral88'.
|
78
|
-
|
79
|
-
Returns:
|
80
|
-
- float or list: Calculated error based on selected method and library.
|
74
|
+
Calculate Mean Squared Error (MSE) for single or multi-dimensional data.
|
81
75
|
"""
|
76
|
+
validate_inputs(y_true, y_pred)
|
77
|
+
|
78
|
+
# y_true = np.array(y_true)
|
79
|
+
# y_pred = np.array(y_pred)
|
80
|
+
|
81
|
+
if flatten:
|
82
|
+
y_true = y_true.ravel()
|
83
|
+
y_pred = y_pred.ravel()
|
84
|
+
|
82
85
|
if library == 'Moral88':
|
83
|
-
# Original implementation
|
84
86
|
if threshold is not None:
|
85
|
-
y_pred =
|
87
|
+
y_pred = np.clip(y_pred, threshold[0], threshold[1])
|
86
88
|
|
87
|
-
squared_errors =
|
89
|
+
squared_errors = (y_true - y_pred) ** 2
|
88
90
|
|
89
91
|
if method == 'mean':
|
90
|
-
result =
|
92
|
+
result = np.mean(squared_errors)
|
91
93
|
elif method == 'sum':
|
92
|
-
result = sum(squared_errors)
|
94
|
+
result = np.sum(squared_errors)
|
93
95
|
elif method == 'none':
|
94
96
|
result = squared_errors
|
95
97
|
else:
|
96
98
|
raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
|
97
99
|
|
98
100
|
if normalize and method != 'none':
|
99
|
-
range_y =
|
101
|
+
range_y = np.ptp(y_true)
|
100
102
|
result = result / max(abs(range_y), 1)
|
101
103
|
|
102
104
|
return result
|
@@ -117,23 +119,148 @@ def mean_squared_error(y_true, y_pred, normalize=True, threshold=None, method='m
|
|
117
119
|
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
118
120
|
return tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor)).numpy()
|
119
121
|
|
120
|
-
# elif library == 'statsmodel':
|
121
|
-
# raise NotImplementedError("Statsmodel does not have a built-in MSE implementation.")
|
122
|
-
|
123
|
-
# elif library == 'Dask-ML':
|
124
|
-
# raise NotImplementedError("Dask-ML support is not implemented yet.")
|
125
|
-
|
126
|
-
# elif library == 'MLlib':
|
127
|
-
# raise NotImplementedError("MLlib support is not implemented yet.")
|
128
|
-
|
129
122
|
else:
|
130
123
|
raise ValueError(f"Invalid library: {library}. Choose from {'Moral88', 'sklearn', 'torch', 'tensorflow'}.")
|
131
|
-
|
124
|
+
|
125
|
+
def r_squared(y_true, y_pred, flatten=True):
|
132
126
|
"""
|
133
|
-
Compute R-Squared
|
127
|
+
Compute R-Squared for single or multi-dimensional data.
|
134
128
|
"""
|
135
|
-
|
136
|
-
|
129
|
+
validate_inputs(y_true, y_pred)
|
130
|
+
|
131
|
+
y_true = np.array(y_true)
|
132
|
+
y_pred = np.array(y_pred)
|
133
|
+
|
134
|
+
if flatten:
|
135
|
+
y_true = y_true.ravel()
|
136
|
+
y_pred = y_pred.ravel()
|
137
|
+
|
137
138
|
ss_total = np.sum((y_true - np.mean(y_true)) ** 2)
|
138
139
|
ss_residual = np.sum((y_true - y_pred) ** 2)
|
139
|
-
return 1 - (ss_residual / ss_total)
|
140
|
+
return 1 - (ss_residual / ss_total)
|
141
|
+
|
142
|
+
import numpy as np
|
143
|
+
import warnings
|
144
|
+
from typing import Union, List, Tuple
|
145
|
+
from scipy import sparse
|
146
|
+
|
147
|
+
class DataValidator:
|
148
|
+
def __init__(self):
|
149
|
+
pass
|
150
|
+
|
151
|
+
def check_device_cpu(self, device):
|
152
|
+
if device not in {"cpu", None}:
|
153
|
+
raise ValueError(f"Unsupported device: {device!r}. Only 'cpu' is supported.")
|
154
|
+
|
155
|
+
def is_1d_array(self, array: Union[np.ndarray, list], warn: bool = False) -> np.ndarray:
|
156
|
+
"""
|
157
|
+
Ensures input is a 1D array. Raises an error if it's not 1D or convertible to 1D.
|
158
|
+
"""
|
159
|
+
array = np.asarray(array)
|
160
|
+
shape = array.shape
|
161
|
+
|
162
|
+
if len(shape) == 1:
|
163
|
+
return array
|
164
|
+
elif len(shape) == 2 and shape[1] == 1:
|
165
|
+
if warn:
|
166
|
+
warnings.warn("Input is 2D but will be converted to 1D.", UserWarning)
|
167
|
+
return array.ravel()
|
168
|
+
else:
|
169
|
+
raise ValueError(f"Input must be 1D. Found shape {shape}.")
|
170
|
+
|
171
|
+
def check_samples(self, array: Union[np.ndarray, list]) -> int:
|
172
|
+
"""
|
173
|
+
Returns the number of samples in the array.
|
174
|
+
"""
|
175
|
+
if hasattr(array, 'shape') and len(array.shape) > 0:
|
176
|
+
return array.shape[0]
|
177
|
+
else:
|
178
|
+
raise TypeError("Input must be an array-like object with at least one dimension.")
|
179
|
+
|
180
|
+
def check_consistent_length(self, *arrays: Union[np.ndarray, list]):
|
181
|
+
"""
|
182
|
+
Ensures all input arrays have the same length.
|
183
|
+
"""
|
184
|
+
lengths = [self.check_samples(arr) for arr in arrays]
|
185
|
+
if len(set(lengths)) > 1:
|
186
|
+
raise ValueError(f"Inconsistent lengths: {lengths}")
|
187
|
+
|
188
|
+
def validate_regression_targets(self, y_true, y_pred, dtype=np.float64):
|
189
|
+
"""
|
190
|
+
Ensures regression target values are consistent and converted to the specified dtype.
|
191
|
+
"""
|
192
|
+
y_true = np.asarray(y_true, dtype=dtype)
|
193
|
+
y_pred = np.asarray(y_pred, dtype=dtype)
|
194
|
+
|
195
|
+
if y_true.shape != y_pred.shape:
|
196
|
+
raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
|
197
|
+
|
198
|
+
return y_true, y_pred
|
199
|
+
|
200
|
+
def check_array(self, array, ensure_2d: bool = True, dtype=np.float64, allow_nan: bool = False):
|
201
|
+
"""
|
202
|
+
Validates input array and converts it to specified dtype.
|
203
|
+
"""
|
204
|
+
array = np.asarray(array, dtype=dtype)
|
205
|
+
|
206
|
+
if ensure_2d and array.ndim == 1:
|
207
|
+
array = array.reshape(-1, 1)
|
208
|
+
|
209
|
+
if not allow_nan and np.isnan(array).any():
|
210
|
+
raise ValueError("Input contains NaN values, which are not allowed.")
|
211
|
+
|
212
|
+
return array
|
213
|
+
|
214
|
+
def check_sparse(self, array, accept_sparse: Tuple[str] = ('csr', 'csc')):
|
215
|
+
"""
|
216
|
+
Validates sparse matrices and converts to an acceptable format.
|
217
|
+
"""
|
218
|
+
if sparse.issparse(array):
|
219
|
+
if array.format not in accept_sparse:
|
220
|
+
return array.asformat(accept_sparse[0])
|
221
|
+
return array
|
222
|
+
else:
|
223
|
+
raise ValueError("Input is not a sparse matrix.")
|
224
|
+
|
225
|
+
def validate_r2_score_inputs(self, y_true, y_pred, sample_weight=None):
|
226
|
+
"""
|
227
|
+
Ensures inputs for R2 score computation are valid.
|
228
|
+
"""
|
229
|
+
y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
|
230
|
+
if sample_weight is not None:
|
231
|
+
sample_weight = self.is_1d_array(sample_weight)
|
232
|
+
return y_true, y_pred, sample_weight
|
233
|
+
|
234
|
+
|
235
|
+
class Metrics:
|
236
|
+
def __init__(self):
|
237
|
+
self.validator = DataValidator()
|
238
|
+
|
239
|
+
def r2_score(self, y_true, y_pred, sample_weight=None):
|
240
|
+
"""
|
241
|
+
Computes R2 score.
|
242
|
+
"""
|
243
|
+
y_true, y_pred, sample_weight = self.validator.validate_r2_score_inputs(y_true, y_pred, sample_weight)
|
244
|
+
|
245
|
+
numerator = np.sum((y_true - y_pred) ** 2)
|
246
|
+
denominator = np.sum((y_true - np.mean(y_true)) ** 2)
|
247
|
+
|
248
|
+
if denominator == 0:
|
249
|
+
return 0.0
|
250
|
+
return 1 - (numerator / denominator)
|
251
|
+
|
252
|
+
|
253
|
+
if __name__ == '__main__':
|
254
|
+
# Example usage
|
255
|
+
validator = DataValidator()
|
256
|
+
metrics = Metrics()
|
257
|
+
|
258
|
+
# Test validation
|
259
|
+
arr = [[1], [2], [3]]
|
260
|
+
print("1D array:", validator.is_1d_array(arr))
|
261
|
+
print("Samples:", validator.check_samples(arr))
|
262
|
+
|
263
|
+
# Test R2 score
|
264
|
+
y_true = [3, -0.5, 2, 7]
|
265
|
+
y_pred = [2.5, 0.0, 2, 8]
|
266
|
+
print("R2 Score:", metrics.r2_score(y_true, y_pred))
|
Moral88/segmentation.py
ADDED
@@ -0,0 +1,38 @@
|
|
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
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Moral88/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
Moral88/regression.py,sha256=kfWQcdtdZVlHW_iIRbS9_rNrKJOYNiT9RaeYpVIvl7I,9355
|
3
|
+
Moral88/segmentation.py,sha256=v0yqxdrKbM9LM7wVKLjJ4HrhrSrilNNeWS6-oK_27Ag,1363
|
4
|
+
Moral88-0.3.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
Moral88-0.3.0.dist-info/METADATA,sha256=rKJtI5aX0pT3DSORLtFmsMPYB8fiAbGNQKmEndRNPtI,407
|
6
|
+
Moral88-0.3.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
7
|
+
Moral88-0.3.0.dist-info/top_level.txt,sha256=-dyn5iTprnSUHbtMpvRO-prJsIoaRxao7wlfCHLSsv4,8
|
8
|
+
Moral88-0.3.0.dist-info/RECORD,,
|
Moral88-0.2.0.dist-info/RECORD
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
Moral88/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
Moral88/regression.py,sha256=W_galfrJHIbyP8S1YL0_GMBCKVKfbI2uJfI1PRsAaOk,5939
|
3
|
-
Moral88-0.2.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
Moral88-0.2.0.dist-info/METADATA,sha256=L3o3beMiux_cQtS6PE8sWQbC6Q2K4RuId_n6X7-tEtg,407
|
5
|
-
Moral88-0.2.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
6
|
-
Moral88-0.2.0.dist-info/top_level.txt,sha256=-dyn5iTprnSUHbtMpvRO-prJsIoaRxao7wlfCHLSsv4,8
|
7
|
-
Moral88-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|