mlquantify 0.1.8__py3-none-any.whl → 0.1.10__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.
Files changed (67) hide show
  1. mlquantify/__init__.py +10 -29
  2. mlquantify/adjust_counting/__init__.py +24 -0
  3. mlquantify/adjust_counting/_adjustment.py +648 -0
  4. mlquantify/adjust_counting/_base.py +245 -0
  5. mlquantify/adjust_counting/_counting.py +153 -0
  6. mlquantify/adjust_counting/_utils.py +109 -0
  7. mlquantify/base.py +117 -519
  8. mlquantify/base_aggregative.py +209 -0
  9. mlquantify/calibration.py +1 -0
  10. mlquantify/confidence.py +329 -0
  11. mlquantify/likelihood/__init__.py +5 -0
  12. mlquantify/likelihood/_base.py +147 -0
  13. mlquantify/likelihood/_classes.py +430 -0
  14. mlquantify/meta/__init__.py +1 -0
  15. mlquantify/meta/_classes.py +785 -0
  16. mlquantify/metrics/__init__.py +21 -0
  17. mlquantify/metrics/_oq.py +109 -0
  18. mlquantify/metrics/_rq.py +98 -0
  19. mlquantify/{evaluation/measures.py → metrics/_slq.py} +51 -36
  20. mlquantify/mixture/__init__.py +7 -0
  21. mlquantify/mixture/_base.py +147 -0
  22. mlquantify/mixture/_classes.py +458 -0
  23. mlquantify/mixture/_utils.py +163 -0
  24. mlquantify/model_selection/__init__.py +9 -0
  25. mlquantify/model_selection/_protocol.py +358 -0
  26. mlquantify/model_selection/_search.py +315 -0
  27. mlquantify/model_selection/_split.py +1 -0
  28. mlquantify/multiclass.py +350 -0
  29. mlquantify/neighbors/__init__.py +9 -0
  30. mlquantify/neighbors/_base.py +168 -0
  31. mlquantify/neighbors/_classes.py +150 -0
  32. mlquantify/{classification/methods.py → neighbors/_classification.py} +37 -62
  33. mlquantify/neighbors/_kde.py +268 -0
  34. mlquantify/neighbors/_utils.py +131 -0
  35. mlquantify/neural/__init__.py +1 -0
  36. mlquantify/utils/__init__.py +47 -2
  37. mlquantify/utils/_artificial.py +27 -0
  38. mlquantify/utils/_constraints.py +219 -0
  39. mlquantify/utils/_context.py +21 -0
  40. mlquantify/utils/_decorators.py +36 -0
  41. mlquantify/utils/_exceptions.py +12 -0
  42. mlquantify/utils/_get_scores.py +159 -0
  43. mlquantify/utils/_load.py +18 -0
  44. mlquantify/utils/_parallel.py +6 -0
  45. mlquantify/utils/_random.py +36 -0
  46. mlquantify/utils/_sampling.py +273 -0
  47. mlquantify/utils/_tags.py +44 -0
  48. mlquantify/utils/_validation.py +447 -0
  49. mlquantify/utils/prevalence.py +64 -0
  50. {mlquantify-0.1.8.dist-info → mlquantify-0.1.10.dist-info}/METADATA +2 -1
  51. mlquantify-0.1.10.dist-info/RECORD +53 -0
  52. mlquantify/classification/__init__.py +0 -1
  53. mlquantify/evaluation/__init__.py +0 -14
  54. mlquantify/evaluation/protocol.py +0 -289
  55. mlquantify/methods/__init__.py +0 -37
  56. mlquantify/methods/aggregative.py +0 -1159
  57. mlquantify/methods/meta.py +0 -472
  58. mlquantify/methods/mixture_models.py +0 -1003
  59. mlquantify/methods/non_aggregative.py +0 -136
  60. mlquantify/methods/threshold_optimization.py +0 -869
  61. mlquantify/model_selection.py +0 -377
  62. mlquantify/plots.py +0 -367
  63. mlquantify/utils/general.py +0 -371
  64. mlquantify/utils/method.py +0 -449
  65. mlquantify-0.1.8.dist-info/RECORD +0 -22
  66. {mlquantify-0.1.8.dist-info → mlquantify-0.1.10.dist-info}/WHEEL +0 -0
  67. {mlquantify-0.1.8.dist-info → mlquantify-0.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
1
+ from dataclasses import dataclass
2
+ import numbers
3
+ import numpy as np
4
+ from abc import ABC, abstractmethod
5
+
6
+
7
+ @dataclass
8
+ class Interval:
9
+ """Represents a numeric range constraint."""
10
+ left: float | int | None
11
+ right: float | int | None
12
+ inclusive_left: bool = True
13
+ inclusive_right: bool = True
14
+ discrete: bool = False
15
+
16
+ def is_satisfied_by(self, value):
17
+ if not isinstance(value, (int, float, np.number)):
18
+ return False
19
+ if self.left is not None:
20
+ if self.inclusive_left and value < self.left:
21
+ return False
22
+ if not self.inclusive_left and value <= self.left:
23
+ return False
24
+ if self.right is not None:
25
+ if self.inclusive_right and value > self.right:
26
+ return False
27
+ if not self.inclusive_right and value >= self.right:
28
+ return False
29
+ if self.discrete and not float(value).is_integer():
30
+ return False
31
+ return True
32
+
33
+ def __str__(self):
34
+ left_bracket = "[" if self.inclusive_left else "("
35
+ right_bracket = "]" if self.inclusive_right else ")"
36
+ return f"{left_bracket}{self.left}, {self.right}{right_bracket}"
37
+
38
+
39
+ @dataclass
40
+ class Options:
41
+ """Represents a fixed set of allowed values."""
42
+ options: list
43
+
44
+ def is_satisfied_by(self, value):
45
+ return value in self.options
46
+
47
+ def __str__(self):
48
+ return f"one of {self.options}"
49
+
50
+ @dataclass
51
+ class _ArrayLikes:
52
+ """Constraint representing array-likes"""
53
+
54
+ def is_satisfied_by(self, val):
55
+ from mlquantify.utils._validation import _is_arraylike_not_scalar
56
+ return _is_arraylike_not_scalar(val)
57
+
58
+ def __str__(self):
59
+ return "an array-like"
60
+
61
+ @dataclass
62
+ class HasMethods:
63
+ """Ensures that an object implements specific methods."""
64
+ methods: list[str]
65
+
66
+ def is_satisfied_by(self, value):
67
+ return all(hasattr(value, m) and callable(getattr(value, m)) for m in self.methods)
68
+
69
+ def __str__(self):
70
+ return f"an object implementing {', '.join(self.methods)}"
71
+
72
+
73
+ @dataclass
74
+ class Hidden:
75
+ """Used for internal constraints not shown to the user."""
76
+ constraint: object
77
+
78
+ def is_satisfied_by(self, value):
79
+ return self.constraint.is_satisfied_by(value)
80
+
81
+ @property
82
+ def hidden(self):
83
+ return True
84
+
85
+ def __str__(self):
86
+ return "<hidden constraint>"
87
+
88
+
89
+ def _type_name(t):
90
+ """Convert type into human readable string."""
91
+ module = t.__module__
92
+ qualname = t.__qualname__
93
+ if module == "builtins":
94
+ return qualname
95
+ elif t == numbers.Real:
96
+ return "float"
97
+ elif t == numbers.Integral:
98
+ return "int"
99
+ return f"{module}.{qualname}"
100
+
101
+
102
+
103
+ class _Constraint(ABC):
104
+ """Base class for the constraint objects."""
105
+
106
+ def __init__(self):
107
+ self.hidden = False
108
+
109
+ @abstractmethod
110
+ def is_satisfied_by(self, val):
111
+ """Whether or not a value satisfies the constraint.
112
+
113
+ Parameters
114
+ ----------
115
+ val : object
116
+ The value to check.
117
+
118
+ Returns
119
+ -------
120
+ is_satisfied : bool
121
+ Whether or not the constraint is satisfied by this value.
122
+ """
123
+
124
+ @abstractmethod
125
+ def __str__(self):
126
+ """A human readable representational string of the constraint."""
127
+
128
+
129
+
130
+ class _InstancesOf(_Constraint):
131
+ """Constraint representing instances of a given type.
132
+
133
+ Parameters
134
+ ----------
135
+ type : type
136
+ The valid type.
137
+ """
138
+
139
+ def __init__(self, type):
140
+ super().__init__()
141
+ self.type = type
142
+
143
+ def is_satisfied_by(self, val):
144
+ return isinstance(val, self.type)
145
+
146
+ def __str__(self):
147
+ return f"an instance of {_type_name(self.type)!r}"
148
+
149
+
150
+ def make_constraint(obj):
151
+ """Normalize strings and simple types into constraint objects."""
152
+ if isinstance(obj, str) and obj == "array-like":
153
+ return _ArrayLikes()
154
+ if isinstance(obj, (Interval, Options, HasMethods, Hidden, CallableConstraint)):
155
+ return obj
156
+ if isinstance(obj, type):
157
+ return _InstancesOf(obj)
158
+ if isinstance(obj, str):
159
+ return StringConstraint(obj)
160
+ if obj is None:
161
+ return NoneConstraint()
162
+ raise TypeError(f"Unsupported constraint type: {obj!r}")
163
+
164
+
165
+ @dataclass
166
+ class TypeConstraint:
167
+ type_: type
168
+
169
+ def is_satisfied_by(self, value):
170
+ return isinstance(value, self.type_)
171
+
172
+ def __str__(self):
173
+ return f"instance of {self.type_.__name__}"
174
+
175
+
176
+ @dataclass
177
+ class CallableConstraint:
178
+
179
+ def is_satisfied_by(self, value):
180
+ return callable(value)
181
+
182
+ def __str__(self):
183
+ return f"a callable"
184
+
185
+
186
+ @dataclass
187
+ class StringConstraint:
188
+ """Predefined string keywords (e.g., 'array-like', 'random_state')."""
189
+ keyword: str
190
+
191
+ def is_satisfied_by(self, value):
192
+ import scipy.sparse as sp
193
+ import numpy as np
194
+
195
+ if self.keyword == "array-like":
196
+ return isinstance(value, (list, tuple, np.ndarray))
197
+ if self.keyword == "sparse matrix":
198
+ return sp.issparse(value)
199
+ if self.keyword == "boolean":
200
+ return isinstance(value, bool)
201
+ if self.keyword == "random_state":
202
+ return isinstance(value, (np.random.RandomState, int, type(None)))
203
+ if self.keyword == "nan":
204
+ return value is np.nan
205
+ return False
206
+
207
+ def __str__(self):
208
+ return self.keyword
209
+
210
+
211
+ @dataclass
212
+ class NoneConstraint:
213
+ """Allows None as valid value."""
214
+
215
+ def is_satisfied_by(self, value):
216
+ return value is None
217
+
218
+ def __str__(self):
219
+ return "None"
@@ -0,0 +1,21 @@
1
+ import contextlib
2
+ import threading
3
+
4
+ # Thread-local flag para suportar execuções paralelas
5
+ _validation_context = threading.local()
6
+
7
+
8
+ @contextlib.contextmanager
9
+ def validation_context(skip: bool = False):
10
+ """Context manager para controlar se a validação deve ser ignorada."""
11
+ old_state = getattr(_validation_context, "skip_validation", False)
12
+ _validation_context.skip_validation = skip
13
+ try:
14
+ yield
15
+ finally:
16
+ _validation_context.skip_validation = old_state
17
+
18
+
19
+ def is_validation_skipped():
20
+ """Verifica se a validação está desativada no contexto atual."""
21
+ return getattr(_validation_context, "skip_validation", False)
@@ -0,0 +1,36 @@
1
+ from functools import wraps
2
+
3
+ from mlquantify.utils._validation import _is_fitted
4
+ from mlquantify.utils._context import validation_context, is_validation_skipped
5
+
6
+
7
+ def _fit_context(prefer_skip_nested_validation: bool = False):
8
+ """
9
+ Decorator to manage validation context during the fit process.
10
+
11
+ Parameters
12
+ ----------
13
+ prefer_skip_nested_validation : bool, optional
14
+ If True, prefer to skip nested validation during fitting, by default False.
15
+ """
16
+ def decorator(fit_method):
17
+ @wraps(fit_method)
18
+ def wrapper(estimator, *args, **kwargs):
19
+ global_skip_validation = is_validation_skipped()
20
+
21
+ # Avoid validation for partial_fit if already fitted
22
+ partial_fit_and_fitted = (
23
+ fit_method.__name__ == "partial_fit" and _is_fitted(estimator)
24
+ )
25
+
26
+ if not global_skip_validation and not partial_fit_and_fitted:
27
+ estimator._validate_params()
28
+
29
+ with validation_context(
30
+ skip=(prefer_skip_nested_validation or global_skip_validation)
31
+ ):
32
+ return fit_method(estimator, *args, **kwargs)
33
+
34
+ return wrapper
35
+
36
+ return decorator
@@ -0,0 +1,12 @@
1
+ # mlquantify/utils/_exceptions.py
2
+ class InputValidationError(ValueError):
3
+ """Raised when invalid predictions are passed to a quantifier."""
4
+ pass
5
+
6
+ class InvalidParameterError(ValueError):
7
+ """Raised when a parameter value does not meet its constraint."""
8
+ pass
9
+
10
+ class NotFittedError(ValueError):
11
+ """Raised when an operation is attempted on an unfitted quantifier."""
12
+ pass
@@ -0,0 +1,159 @@
1
+ import numpy as np
2
+ from sklearn.model_selection import KFold, StratifiedKFold
3
+
4
+ def apply_cross_validation(
5
+ model,
6
+ X: np.ndarray,
7
+ y: np.ndarray,
8
+ cv= 5,
9
+ function= 'predict_proba',
10
+ stratified= True,
11
+ random_state= None,
12
+ shuffle= True):
13
+ """
14
+ Perform cross-validation and return predictions with true labels for each fold.
15
+
16
+ Parameters:
17
+ -----------
18
+ model : estimator
19
+ Model with fit and predict/predict_proba methods
20
+ X : np.ndarray
21
+ Feature matrix
22
+ y : np.ndarray
23
+ Target vector
24
+ cv : int, default=5
25
+ Number of cross-validation folds
26
+ function : str, default='predict_proba'
27
+ Method to use for predictions ('predict' or 'predict_proba' or any callable)
28
+ stratified : bool, default=True
29
+ Whether to use stratified cross-validation
30
+ random_state : int or None, default=None
31
+ Random state for reproducibility
32
+ shuffle : bool, default=True
33
+ Whether to shuffle data before splitting
34
+
35
+ Returns:
36
+ --------
37
+ Tuple[np.ndarray, np.ndarray]
38
+ predictions, true_labels for all folds
39
+ """
40
+
41
+ # Choose cross-validation strategy
42
+ if stratified:
43
+ cv_splitter = StratifiedKFold(
44
+ n_splits=cv,
45
+ shuffle=shuffle,
46
+ random_state=random_state
47
+ )
48
+ else:
49
+ cv_splitter = KFold(
50
+ n_splits=cv,
51
+ shuffle=shuffle,
52
+ random_state=random_state
53
+ )
54
+
55
+ # Pre-allocate arrays
56
+ all_predictions = []
57
+ all_true_labels = []
58
+
59
+ # Perform cross-validation
60
+ for train_idx, test_idx in cv_splitter.split(X, y):
61
+ X_train, X_test = X[train_idx], X[test_idx]
62
+ y_train, y_test = y[train_idx], y[test_idx]
63
+
64
+ # Fit model
65
+ model.fit(X_train, y_train)
66
+
67
+ if type(function) is str:
68
+ if not hasattr(model, function):
69
+ raise AttributeError(f"The model does not have the method '{function}'.")
70
+ predictions = getattr(model, function)(X_test)
71
+ elif callable(function):
72
+ predictions = function(X_test)
73
+ else:
74
+ raise ValueError("The 'function' parameter must be a string or a callable.")
75
+
76
+ all_predictions.append(predictions)
77
+ all_true_labels.append(y_test)
78
+
79
+ # Concatenate all predictions and labels
80
+ final_predictions = np.vstack(all_predictions) if function == 'predict_proba' else np.concatenate(all_predictions)
81
+ final_true_labels = np.concatenate(all_true_labels)
82
+
83
+ return final_predictions, final_true_labels
84
+
85
+
86
+
87
+ def apply_bootstrap(
88
+ model,
89
+ X: np.ndarray,
90
+ y: np.ndarray = None,
91
+ n_bootstraps: int = 100,
92
+ function: str = 'predict_proba',
93
+ random_state: int = None
94
+ ):
95
+ """
96
+ Perform bootstrap resampling and return predictions with true labels for each bootstrap sample.
97
+ If y is None, bootstrap and fit using only X, and check if model is fitted.
98
+
99
+ Parameters:
100
+ -----------
101
+ model : estimator
102
+ Model with fit and predict/predict_proba methods
103
+ X : np.ndarray
104
+ Feature matrix
105
+ y : np.ndarray or None
106
+ Target vector (optional)
107
+ n_bootstraps : int, default=100
108
+ Number of bootstrap samples
109
+ function : str or callable, default='predict_proba'
110
+ Method to use for predictions ('predict' or 'predict_proba' or any callable)
111
+ random_state : int or None, default=None
112
+ Random state for reproducibility
113
+ """
114
+
115
+ if random_state is not None:
116
+ np.random.seed(random_state)
117
+
118
+ all_predictions = []
119
+ all_true_labels = [] if y is not None else None
120
+
121
+ for _ in range(n_bootstraps):
122
+ bootstrap_indices = np.random.choice(len(X), size=len(X), replace=True)
123
+ X_bootstrap = X[bootstrap_indices]
124
+ if y is not None:
125
+ y_bootstrap = y[bootstrap_indices]
126
+ model.fit(X_bootstrap, y_bootstrap)
127
+ else:
128
+ model.fit(X_bootstrap)
129
+
130
+ # Check if model is fitted - raise error if not
131
+ if not hasattr(model, "fitted_") or not getattr(model, "fitted_"):
132
+ # Some models have other indicators, as a fallback we could just pass
133
+ # or do other checks. For simplicity, we check fitted_
134
+ # If not fitted, raise exception
135
+ raise ValueError("Model does not appear to be fitted after fit(X).")
136
+
137
+ if type(function) is str:
138
+ if not hasattr(model, function):
139
+ raise AttributeError(f"The model does not have the method '{function}'.")
140
+ predictions = getattr(model, function)(X_bootstrap)
141
+ elif callable(function):
142
+ predictions = function(X_bootstrap)
143
+ else:
144
+ raise ValueError("The 'function' parameter must be a string or a callable.")
145
+
146
+ all_predictions.append(predictions)
147
+ if y is not None:
148
+ all_true_labels.append(y_bootstrap)
149
+
150
+ if function == 'predict_proba':
151
+ final_predictions = np.vstack(all_predictions)
152
+ else:
153
+ final_predictions = np.concatenate(all_predictions)
154
+
155
+ if y is not None:
156
+ final_true_labels = np.concatenate(all_true_labels)
157
+ return final_predictions, final_true_labels
158
+ else:
159
+ return final_predictions
@@ -0,0 +1,18 @@
1
+ from joblib import load
2
+
3
+
4
+ def load_quantifier(path:str):
5
+ """
6
+ Load a quantifier from a file.
7
+
8
+ Parameters
9
+ ----------
10
+ path : str
11
+ Path to the file containing the quantifier.
12
+
13
+ Returns
14
+ -------
15
+ Quantifier
16
+ Loaded quantifier.
17
+ """
18
+ return load(path)
@@ -0,0 +1,6 @@
1
+ import os
2
+ from joblib import effective_n_jobs
3
+
4
+ def resolve_n_jobs(n_jobs=None):
5
+ """Resolve n_jobs like sklearn, with support for -1 and nested contexts."""
6
+ return effective_n_jobs(n_jobs)
@@ -0,0 +1,36 @@
1
+ import numpy as np
2
+ from numpy.random import RandomState, Generator, default_rng
3
+
4
+
5
+ def check_random_state(seed=None):
6
+ """
7
+ Turn seed into a np.random.RandomState or np.random.Generator instance.
8
+
9
+ Parameters
10
+ ----------
11
+ seed : None, int, RandomState, Generator
12
+ - If None, return the global RandomState singleton used by np.random.
13
+ - If int, return a new RandomState instance seeded with seed.
14
+ - If RandomState or Generator, return it.
15
+ - Otherwise, raise ValueError.
16
+
17
+ Returns
18
+ -------
19
+ rng : np.random.Generator
20
+ A numpy random generator compatible with modern numpy APIs.
21
+ """
22
+ if seed is None or seed is np.random:
23
+ return default_rng() # new independent generator each call
24
+ if isinstance(seed, (int, np.integer)):
25
+ return default_rng(seed)
26
+ if isinstance(seed, Generator):
27
+ return seed
28
+ if isinstance(seed, RandomState):
29
+ # Wrap legacy RandomState inside a Generator for uniformity
30
+ bitgen = np.random.MT19937()
31
+ bitgen.state = seed.get_state()
32
+ return Generator(bitgen)
33
+ raise ValueError(
34
+ f"{seed!r} cannot be used to seed a numpy random number generator. "
35
+ "Valid options are None, int, RandomState, or Generator."
36
+ )