erroranalysis 0.5.5__tar.gz → 0.6.0__tar.gz
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.
- {erroranalysis-0.5.5/erroranalysis.egg-info → erroranalysis-0.6.0}/PKG-INFO +17 -8
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/README.md +1 -1
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/cohort_filter.py +142 -66
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/matrix_filter.py +1000 -899
- erroranalysis-0.6.0/erroranalysis/_internal/metrics.py +681 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/surrogate_error_tree.py +1214 -1091
- erroranalysis-0.6.0/erroranalysis/_internal/utils.py +633 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/analyzer/error_analyzer.py +33 -6
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/error_correlation_methods/gbm.py +2 -1
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/version.py +2 -2
- {erroranalysis-0.5.5 → erroranalysis-0.6.0/erroranalysis.egg-info}/PKG-INFO +17 -8
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis.egg-info/SOURCES.txt +15 -1
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis.egg-info/requires.txt +2 -2
- erroranalysis-0.6.0/requirements.txt +6 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/setup.py +1 -3
- erroranalysis-0.6.0/tests/test_binary_heatmap_inferred_classes.py +67 -0
- erroranalysis-0.6.0/tests/test_binary_heatmap_two_features.py +76 -0
- erroranalysis-0.6.0/tests/test_binary_metric_semantics.py +152 -0
- erroranalysis-0.6.0/tests/test_binary_metric_views.py +86 -0
- erroranalysis-0.6.0/tests/test_binary_tree_inferred_classes.py +121 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_importances.py +21 -0
- erroranalysis-0.6.0/tests/test_integer_mae_views.py +138 -0
- erroranalysis-0.6.0/tests/test_integer_residual_boundaries.py +72 -0
- erroranalysis-0.6.0/tests/test_metric_consistency_followup.py +3362 -0
- erroranalysis-0.6.0/tests/test_optional_spark_import.py +20 -0
- erroranalysis-0.6.0/tests/test_prediction_list_residuals.py +21 -0
- erroranalysis-0.6.0/tests/test_prediction_residuals.py +56 -0
- erroranalysis-0.6.0/tests/test_pyspark_surrogate_error_tree.py +86 -0
- erroranalysis-0.6.0/tests/test_review_followup_regressions.py +108 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_root_stats.py +2 -2
- erroranalysis-0.6.0/tests/test_root_stats_regressions.py +97 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_surrogate_error_tree.py +2 -2
- erroranalysis-0.6.0/tests/test_tree_mean_prediction.py +41 -0
- erroranalysis-0.5.5/erroranalysis/_internal/metrics.py +0 -280
- erroranalysis-0.5.5/erroranalysis/_internal/utils.py +0 -53
- erroranalysis-0.5.5/requirements.txt +0 -6
- erroranalysis-0.5.5/tests/test_pyspark_surrogate_error_tree.py +0 -59
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/LICENSE +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/MANIFEST.in +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/constants.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/error_analyzer/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/error_report/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/process_categoricals.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/_internal/version_checker.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/analyzer/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/error_correlation_methods/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/error_correlation_methods/ebm.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/report/__init__.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis/report/error_report.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis.egg-info/dependency_links.txt +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/erroranalysis.egg-info/top_level.txt +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/requirements-object-detection.txt +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/setup.cfg +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_cohort_filter.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_error_report.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_matrix_filter.py +0 -0
- {erroranalysis-0.5.5 → erroranalysis-0.6.0}/tests/test_metrics.py +0 -0
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: erroranalysis
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Core error analysis APIs
|
|
5
5
|
Home-page: https://github.com/microsoft/responsible-ai-widgets
|
|
6
6
|
Author: Roman Lutz, Ilya Matiach, Ke Xu
|
|
7
7
|
Author-email: raiwidgets-maintain@microsoft.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
8
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
12
|
Classifier: Operating System :: OS Independent
|
|
15
13
|
Classifier: Development Status :: 3 - Alpha
|
|
16
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.9
|
|
17
15
|
Description-Content-Type: text/markdown
|
|
18
16
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: numpy<
|
|
20
|
-
Requires-Dist: pandas<
|
|
17
|
+
Requires-Dist: numpy<3,>=1.17.2
|
|
18
|
+
Requires-Dist: pandas<3,>=0.25.1
|
|
21
19
|
Requires-Dist: scipy>=1.4.1
|
|
22
20
|
Requires-Dist: scikit-learn>=0.22.1
|
|
23
21
|
Requires-Dist: lightgbm>=2.0.11
|
|
24
22
|
Requires-Dist: raiutils>=0.4.2
|
|
25
23
|
Provides-Extra: object-detection
|
|
26
24
|
Requires-Dist: vision_explanation_methods; extra == "object-detection"
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
27
36
|
|
|
28
37
|
# Error Analysis SDK for Python
|
|
29
38
|
|
|
30
|
-
### This package has been tested with Python 3.
|
|
39
|
+
### This package has been tested with Python 3.9, 3.10 and 3.11
|
|
31
40
|
|
|
32
41
|
The error analysis sdk enables users to get a deeper understanding of machine learning model errors. When evaluating a machine learning model, aggregate accuracy is not sufficient and single-score evaluation may hide important conditions of inaccuracies. Use Error Analysis to identify cohorts with higher error rates and diagnose the root causes behind these errors.
|
|
33
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Error Analysis SDK for Python
|
|
2
2
|
|
|
3
|
-
### This package has been tested with Python 3.
|
|
3
|
+
### This package has been tested with Python 3.9, 3.10 and 3.11
|
|
4
4
|
|
|
5
5
|
The error analysis sdk enables users to get a deeper understanding of machine learning model errors. When evaluating a machine learning model, aggregate accuracy is not sufficient and single-score evaluation may hide important conditions of inaccuracies. Use Error Analysis to identify cohorts with higher error rates and diagnose the root causes behind these errors.
|
|
6
6
|
|
|
@@ -11,14 +11,60 @@ from erroranalysis._internal.constants import (ARG, COLUMN, COMPOSITE_FILTERS,
|
|
|
11
11
|
ROW_INDEX, TRUE_Y,
|
|
12
12
|
CohortFilterMethods,
|
|
13
13
|
CohortFilterOps, ModelTask)
|
|
14
|
-
from erroranalysis._internal.metrics import
|
|
15
|
-
|
|
14
|
+
from erroranalysis._internal.metrics import (encode_classification_labels,
|
|
15
|
+
get_ordered_classes,
|
|
16
|
+
validate_ordered_classes)
|
|
17
|
+
from erroranalysis._internal.utils import (compute_classification_diff,
|
|
18
|
+
compute_regression_residuals,
|
|
19
|
+
is_spark,
|
|
20
|
+
normalize_multioutput_array,
|
|
21
|
+
normalize_single_output_array,
|
|
22
|
+
object_label_series)
|
|
16
23
|
|
|
17
24
|
MODEL = 'model'
|
|
18
25
|
CLASSIFICATION_OUTCOME = 'Classification outcome'
|
|
19
26
|
REGRESSION_ERROR = 'Regression error'
|
|
20
27
|
|
|
21
28
|
|
|
29
|
+
def _get_object_detection_values(values):
|
|
30
|
+
"""Return one object cell per sample for nested labels."""
|
|
31
|
+
if isinstance(values, pd.DataFrame):
|
|
32
|
+
if values.shape[1] != 1:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
"Object detection labels must have exactly one column")
|
|
35
|
+
values = values.iloc[:, 0]
|
|
36
|
+
elif isinstance(values, np.ndarray) and \
|
|
37
|
+
values.ndim == 2 and values.shape[1] == 1:
|
|
38
|
+
values = values[:, 0]
|
|
39
|
+
result = np.empty(len(values), dtype=object)
|
|
40
|
+
result[:] = list(values)
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_cohort_values(values):
|
|
45
|
+
"""Return scalar labels or one object cell per multi-output row."""
|
|
46
|
+
multioutput = normalize_multioutput_array(values)
|
|
47
|
+
if multioutput is not None:
|
|
48
|
+
result = np.empty(len(multioutput), dtype=object)
|
|
49
|
+
result[:] = list(multioutput)
|
|
50
|
+
return result
|
|
51
|
+
normalized = normalize_single_output_array(values)
|
|
52
|
+
if normalized.ndim == 2 and normalized.shape[1] > 1:
|
|
53
|
+
result = np.empty(len(normalized), dtype=object)
|
|
54
|
+
result[:] = list(normalized)
|
|
55
|
+
return result
|
|
56
|
+
return normalized
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _assign_label_column(df, column, values):
|
|
60
|
+
"""Assign object labels without pandas temporal or numeric inference."""
|
|
61
|
+
if (isinstance(values, np.ndarray) and
|
|
62
|
+
values.dtype.kind in ("M", "O", "m")):
|
|
63
|
+
df[column] = object_label_series(values, index=df.index)
|
|
64
|
+
else:
|
|
65
|
+
df[column] = values
|
|
66
|
+
|
|
67
|
+
|
|
22
68
|
def filter_from_cohort(analyzer, filters, composite_filters,
|
|
23
69
|
include_original_columns_only=False):
|
|
24
70
|
"""Filters the dataset on the analyzer based on the specified filters.
|
|
@@ -124,7 +170,7 @@ class FilterDataWithCohortFilters:
|
|
|
124
170
|
# Note: we make a non-deep copy of the input DataFrame since
|
|
125
171
|
# we will add columns below
|
|
126
172
|
df = df.copy()
|
|
127
|
-
self._add_filter_cols(df, filters)
|
|
173
|
+
self._add_filter_cols(df, (filters or []) + (composite_filters or []))
|
|
128
174
|
df = self._apply_recursive_filter(
|
|
129
175
|
df, filters, self.categorical_features, self.categories)
|
|
130
176
|
df = self._apply_recursive_filter(
|
|
@@ -148,11 +194,7 @@ class FilterDataWithCohortFilters:
|
|
|
148
194
|
if (model_task == ModelTask.CLASSIFICATION or
|
|
149
195
|
model_task == ModelTask.OBJECT_DETECTION) \
|
|
150
196
|
and filters:
|
|
151
|
-
|
|
152
|
-
if COLUMN in filter:
|
|
153
|
-
column = filter[COLUMN]
|
|
154
|
-
if column == CLASSIFICATION_OUTCOME:
|
|
155
|
-
return True
|
|
197
|
+
return self._filters_has_column(filters, CLASSIFICATION_OUTCOME)
|
|
156
198
|
return False
|
|
157
199
|
|
|
158
200
|
def _filters_has_regression_error(self, filters):
|
|
@@ -169,11 +211,17 @@ class FilterDataWithCohortFilters:
|
|
|
169
211
|
# is included in filters, and if it is then
|
|
170
212
|
# compute the necessary column data on the fly
|
|
171
213
|
if model_task == ModelTask.REGRESSION and filters:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
return self._filters_has_column(filters, REGRESSION_ERROR)
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def _filters_has_column(self, filters, column):
|
|
218
|
+
"""Find a column in flat filters or nested composite filters."""
|
|
219
|
+
for filter in filters:
|
|
220
|
+
if filter.get(COLUMN) == column:
|
|
221
|
+
return True
|
|
222
|
+
if self._filters_has_column(
|
|
223
|
+
filter.get(COMPOSITE_FILTERS, []), column):
|
|
224
|
+
return True
|
|
177
225
|
return False
|
|
178
226
|
|
|
179
227
|
def _compute_binary_classification_outcome_data(self, true_y, pred_y,
|
|
@@ -181,6 +229,10 @@ class FilterDataWithCohortFilters:
|
|
|
181
229
|
"""Creates the classification outcome data for binary
|
|
182
230
|
classification scenario.
|
|
183
231
|
|
|
232
|
+
The second class is positive. Outcome codes match the dashboard:
|
|
233
|
+
0 is true negative, 1 false positive, 2 false negative,
|
|
234
|
+
and 3 true positive.
|
|
235
|
+
|
|
184
236
|
:param true_y: The true labels.
|
|
185
237
|
:type true_y: list or numpy.ndarray
|
|
186
238
|
:param pred_y: The predicted labels.
|
|
@@ -190,29 +242,15 @@ class FilterDataWithCohortFilters:
|
|
|
190
242
|
:return: The classification outcome data.
|
|
191
243
|
:rtype: list
|
|
192
244
|
"""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if true_y[i] == classes[0]:
|
|
203
|
-
# True positive == 0
|
|
204
|
-
classification_outcome.append(0)
|
|
205
|
-
else:
|
|
206
|
-
# True negative == 3
|
|
207
|
-
classification_outcome.append(3)
|
|
208
|
-
else:
|
|
209
|
-
if true_y[i] == classes[0]:
|
|
210
|
-
# False negative == 2
|
|
211
|
-
classification_outcome.append(2)
|
|
212
|
-
else:
|
|
213
|
-
# False positive == 1
|
|
214
|
-
classification_outcome.append(1)
|
|
215
|
-
return classification_outcome
|
|
245
|
+
true_codes = encode_classification_labels(
|
|
246
|
+
classes, true_y, allow_unknown=True)[0]
|
|
247
|
+
matches = ~compute_classification_diff(pred_y, true_y)
|
|
248
|
+
true_negative_class = true_codes == 0
|
|
249
|
+
return np.where(
|
|
250
|
+
matches,
|
|
251
|
+
np.where(true_negative_class, 0, 3),
|
|
252
|
+
np.where(true_negative_class, 1, 2),
|
|
253
|
+
).tolist()
|
|
216
254
|
|
|
217
255
|
def _compute_multiclass_classification_outcome_data(
|
|
218
256
|
self, true_y, pred_y):
|
|
@@ -226,22 +264,7 @@ class FilterDataWithCohortFilters:
|
|
|
226
264
|
:return: The classification outcome data.
|
|
227
265
|
:rtype: list
|
|
228
266
|
"""
|
|
229
|
-
|
|
230
|
-
if not isinstance(pred_y, np.ndarray):
|
|
231
|
-
pred_y = np.array(pred_y)
|
|
232
|
-
|
|
233
|
-
if not isinstance(true_y, np.ndarray):
|
|
234
|
-
true_y = np.array(true_y)
|
|
235
|
-
|
|
236
|
-
for i in range(len(true_y)):
|
|
237
|
-
if true_y[i] == pred_y[i]:
|
|
238
|
-
# Correct prediction == 0
|
|
239
|
-
classification_outcome.append(0)
|
|
240
|
-
else:
|
|
241
|
-
# Incorrect prediction == 1
|
|
242
|
-
classification_outcome.append(1)
|
|
243
|
-
|
|
244
|
-
return classification_outcome
|
|
267
|
+
return compute_classification_diff(pred_y, true_y).astype(int).tolist()
|
|
245
268
|
|
|
246
269
|
def _compute_regression_error_data(self, true_y, pred_y):
|
|
247
270
|
"""Computes the per instance absolute difference
|
|
@@ -254,16 +277,7 @@ class FilterDataWithCohortFilters:
|
|
|
254
277
|
:return: The regression error data.
|
|
255
278
|
:rtype: list
|
|
256
279
|
"""
|
|
257
|
-
|
|
258
|
-
if not isinstance(pred_y, np.ndarray):
|
|
259
|
-
pred_y = np.array(pred_y)
|
|
260
|
-
|
|
261
|
-
if not isinstance(true_y, np.ndarray):
|
|
262
|
-
true_y = np.array(true_y)
|
|
263
|
-
|
|
264
|
-
for i in range(len(true_y)):
|
|
265
|
-
regression_error.append(abs(true_y[i] - pred_y[i]))
|
|
266
|
-
return regression_error
|
|
280
|
+
return np.abs(compute_regression_residuals(pred_y, true_y)).tolist()
|
|
267
281
|
|
|
268
282
|
def _add_filter_cols(self, df, filters):
|
|
269
283
|
"""Adds special columns to the dataset for filtering
|
|
@@ -277,17 +291,39 @@ class FilterDataWithCohortFilters:
|
|
|
277
291
|
has_classification_outcome = self._filters_has_classification_outcome(
|
|
278
292
|
filters)
|
|
279
293
|
has_regression_error = self._filters_has_regression_error(filters)
|
|
294
|
+
if self.model is None and self.pred_y is None and (
|
|
295
|
+
has_classification_outcome or has_regression_error):
|
|
296
|
+
raise ValueError(
|
|
297
|
+
"A model or predicted labels are required to filter by "
|
|
298
|
+
"classification outcome or regression error.")
|
|
280
299
|
|
|
281
300
|
if isinstance(self.true_y, str):
|
|
282
301
|
df.rename(columns={self.true_y: TRUE_Y})
|
|
302
|
+
elif self.model_task in (
|
|
303
|
+
ModelTask.OBJECT_DETECTION,
|
|
304
|
+
ModelTask.MULTILABEL_IMAGE_CLASSIFICATION):
|
|
305
|
+
_assign_label_column(
|
|
306
|
+
df, TRUE_Y, _get_object_detection_values(self.true_y))
|
|
283
307
|
else:
|
|
284
|
-
|
|
308
|
+
_assign_label_column(
|
|
309
|
+
df, TRUE_Y, _get_cohort_values(self.true_y))
|
|
285
310
|
|
|
286
311
|
if self.model is None:
|
|
287
|
-
|
|
312
|
+
if self.pred_y is not None and self.model_task in (
|
|
313
|
+
ModelTask.OBJECT_DETECTION,
|
|
314
|
+
ModelTask.MULTILABEL_IMAGE_CLASSIFICATION):
|
|
315
|
+
_assign_label_column(
|
|
316
|
+
df, PRED_Y,
|
|
317
|
+
_get_object_detection_values(self.pred_y))
|
|
318
|
+
elif self.pred_y is not None:
|
|
319
|
+
_assign_label_column(
|
|
320
|
+
df, PRED_Y, _get_cohort_values(self.pred_y))
|
|
288
321
|
|
|
289
322
|
if not is_spark(df):
|
|
290
|
-
|
|
323
|
+
row_count = (self.true_y.shape[0]
|
|
324
|
+
if hasattr(self.true_y, "shape")
|
|
325
|
+
else len(self.true_y))
|
|
326
|
+
df[ROW_INDEX] = np.arange(0, row_count)
|
|
291
327
|
if has_classification_outcome:
|
|
292
328
|
if PRED_Y in df:
|
|
293
329
|
pred_y = df[PRED_Y]
|
|
@@ -296,11 +332,51 @@ class FilterDataWithCohortFilters:
|
|
|
296
332
|
pred_y = self.model.predict(
|
|
297
333
|
df.drop(columns=[TRUE_Y, ROW_INDEX]))
|
|
298
334
|
|
|
335
|
+
multi_true_y = normalize_multioutput_array(self.true_y)
|
|
336
|
+
multi_pred_y = normalize_multioutput_array(pred_y)
|
|
337
|
+
if multi_true_y is not None and multi_pred_y is not None:
|
|
338
|
+
df[CLASSIFICATION_OUTCOME] = \
|
|
339
|
+
self._compute_multiclass_classification_outcome_data(
|
|
340
|
+
multi_true_y, multi_pred_y)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
model_classes = getattr(self.model, "classes_", None)
|
|
344
|
+
declared_classes = self.classes
|
|
345
|
+
if declared_classes is None and model_classes is not None:
|
|
346
|
+
model_order = get_ordered_classes(
|
|
347
|
+
model_classes, self.true_y, pred_y)
|
|
348
|
+
if model_order is model_classes and len(model_classes) == 2:
|
|
349
|
+
declared_classes = model_classes
|
|
350
|
+
true_labels = normalize_single_output_array(self.true_y).ravel()
|
|
351
|
+
nonmissing_true_labels = [
|
|
352
|
+
label for label in true_labels
|
|
353
|
+
if not (
|
|
354
|
+
np.asarray(pd.isna(label)).ndim == 0 and
|
|
355
|
+
bool(pd.isna(label)))
|
|
356
|
+
]
|
|
299
357
|
classes = get_ordered_classes(
|
|
300
|
-
|
|
358
|
+
declared_classes, nonmissing_true_labels,
|
|
359
|
+
nonmissing_true_labels)
|
|
360
|
+
if declared_classes is None or classes is not declared_classes:
|
|
361
|
+
if model_classes is not None and declared_classes is not None:
|
|
362
|
+
model_order = get_ordered_classes(
|
|
363
|
+
model_classes, self.true_y, pred_y)
|
|
364
|
+
if model_order is model_classes and \
|
|
365
|
+
len(model_classes) == 2:
|
|
366
|
+
classes = model_classes
|
|
367
|
+
else:
|
|
368
|
+
classes = get_ordered_classes(
|
|
369
|
+
None, self.true_y, pred_y)
|
|
370
|
+
else:
|
|
371
|
+
classes = get_ordered_classes(
|
|
372
|
+
None, self.true_y, pred_y)
|
|
301
373
|
|
|
302
374
|
# calculate classification outcome and add to df
|
|
303
375
|
if len(classes) == 2:
|
|
376
|
+
validate_ordered_classes(
|
|
377
|
+
classes, nonmissing_true_labels,
|
|
378
|
+
nonmissing_true_labels,
|
|
379
|
+
require_two_classes=True)
|
|
304
380
|
df[CLASSIFICATION_OUTCOME] = \
|
|
305
381
|
self._compute_binary_classification_outcome_data(
|
|
306
382
|
self.true_y, pred_y, classes)
|