SuperModelingFactory 0.2.0__tar.gz → 0.2.1__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.
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/Evaluation_Tool.py +317 -248
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/Evaluation_Tool.pyi +6 -7
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Explainability/Coalition_Structure.py +73 -9
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Explainability/Model_Explainer.py +196 -1
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/Backward_Tool.py +70 -24
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/PKG-INFO +4 -2
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/README.md +1 -1
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/PKG-INFO +4 -2
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/requires.txt +5 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/pyproject.toml +2 -1
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/setup.py +1 -1
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/LICENSE +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/MANIFEST.in +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Report/__init__.py +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/requirements.txt +0 -0
- {supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/setup.cfg +0 -0
{supermodelingfactory-0.2.0 → supermodelingfactory-0.2.1}/Modeling_Tool/Eval/Evaluation_Tool.py
RENAMED
|
@@ -12,10 +12,42 @@ import pandas as pd
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import inspect
|
|
14
14
|
from typing import List, Dict, Optional, Callable, Any, Union
|
|
15
|
-
from .Model_Eval_Tool import
|
|
15
|
+
from .Model_Eval_Tool import (
|
|
16
|
+
cross_risk,
|
|
17
|
+
get_perf_summary,
|
|
18
|
+
GainsTableCalculator,
|
|
19
|
+
PerformanceEvaluator,
|
|
20
|
+
)
|
|
16
21
|
import logging
|
|
17
22
|
logger = logging.getLogger(__name__)
|
|
18
23
|
|
|
24
|
+
_DEFAULT_GAINS_DISPLAY_METRICS = [
|
|
25
|
+
'MIN', 'MAX', 'N', 'PROP', 'AVG_SCORE', 'AVG_BAD',
|
|
26
|
+
'CUM_BAD_PCT', 'KS_PER_BIN', 'LIFT', 'RANK_ORDER_BUMP',
|
|
27
|
+
]
|
|
28
|
+
_LEGACY_EVAL_YLABELS = ['is_dpd7']
|
|
29
|
+
_LEGACY_GRP_NAMELIST = ['sample_ind_fnl', 'aprvvrsn_2', 'week_start_date']
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _legacy_subset_condition_dict():
|
|
33
|
+
return {
|
|
34
|
+
"Overall": "",
|
|
35
|
+
"AE": "aprvvrsn_2 in ('AE')",
|
|
36
|
+
"NON_AE": "aprvvrsn_2 not in ('AE')",
|
|
37
|
+
"FT": "aprvvrsn_2 in ('FT2.0')",
|
|
38
|
+
"NON_FT": "aprvvrsn_2 not in ('FT2.0')",
|
|
39
|
+
"NON_FT_AE": "aprvvrsn_2 not in ('FT2.0') and aprvvrsn_2 not in ('AE')",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _legacy_cross_agg_dict(udf):
|
|
44
|
+
return {
|
|
45
|
+
'is_dpd7': ['count', lambda x: (x.sum() / x.count()).round(4)],
|
|
46
|
+
'credit_limit': ['count', lambda x: udf.valid_average(x)],
|
|
47
|
+
'monthlyincome': ['count', lambda x: udf.valid_average(x)],
|
|
48
|
+
'education': ['count', lambda x: udf.valid_average(x)],
|
|
49
|
+
}
|
|
50
|
+
|
|
19
51
|
class EvaluationPipeline:
|
|
20
52
|
"""
|
|
21
53
|
链式调用的流水线对象,支持 .group_by() 和 .subset_by() 链式添加条件,
|
|
@@ -202,7 +234,6 @@ class Model_Evaluation_Tool:
|
|
|
202
234
|
- Base score calculation and correlation analysis
|
|
203
235
|
- Model performance comparison across multiple metrics
|
|
204
236
|
- Gains table generation and analysis
|
|
205
|
-
- Custom metric evaluation
|
|
206
237
|
- Cross-risk analysis
|
|
207
238
|
- Multi-dimensional evaluation with subsets and groupings
|
|
208
239
|
|
|
@@ -255,8 +286,6 @@ class Model_Evaluation_Tool:
|
|
|
255
286
|
Name of the base score column.
|
|
256
287
|
comp_scrlist : list
|
|
257
288
|
List of comparison score names.
|
|
258
|
-
eval_metrics : list
|
|
259
|
-
List of evaluation metrics.
|
|
260
289
|
eval_ylabels : list
|
|
261
290
|
Labels for y-axis evaluation.
|
|
262
291
|
gains_display_metric_list : list
|
|
@@ -283,8 +312,15 @@ class Model_Evaluation_Tool:
|
|
|
283
312
|
include_missing: bool = True,
|
|
284
313
|
missing_rate_ref: Union[int, float] = -99999999,
|
|
285
314
|
excel_path: Optional[str] = None,
|
|
286
|
-
fillna
|
|
287
|
-
spec_values
|
|
315
|
+
fillna=-999999,
|
|
316
|
+
spec_values=None,
|
|
317
|
+
cross_agg_dict: Optional[Dict] = None,
|
|
318
|
+
subset_condition_dict: Optional[Dict[str, str]] = None,
|
|
319
|
+
eval_ylabels: Optional[List[str]] = None,
|
|
320
|
+
grp_namelist: Optional[List[str]] = None,
|
|
321
|
+
gains_display_metric_list: Optional[List[str]] = None,
|
|
322
|
+
weight_col: Optional[str] = None,
|
|
323
|
+
positive_score_only: bool = True,
|
|
288
324
|
):
|
|
289
325
|
"""
|
|
290
326
|
Initialize the Model_Evaluation_Tool with configuration parameters.
|
|
@@ -344,39 +380,105 @@ class Model_Evaluation_Tool:
|
|
|
344
380
|
self.excel_path = excel_path
|
|
345
381
|
self.missing_rate_ref = missing_rate_ref
|
|
346
382
|
self.fillna = fillna
|
|
347
|
-
self.spec_values = spec_values
|
|
383
|
+
self.spec_values = [] if spec_values is None else spec_values
|
|
384
|
+
self.weight_col = weight_col
|
|
385
|
+
self.positive_score_only = positive_score_only
|
|
348
386
|
|
|
349
387
|
self.chi2_method = chi2_method
|
|
350
388
|
self.chi2_p = chi2_p
|
|
351
389
|
self.init_equi_bins = init_equi_bins
|
|
352
390
|
|
|
353
|
-
self.eval_metrics = [
|
|
354
|
-
'age',
|
|
355
|
-
'monthlyincome',
|
|
356
|
-
'education',
|
|
357
|
-
'credit_limit'
|
|
358
|
-
]
|
|
359
|
-
|
|
360
391
|
self.udf = Utility_Functions()
|
|
361
|
-
self.cross_agg_dict =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
392
|
+
self.cross_agg_dict = (
|
|
393
|
+
cross_agg_dict if cross_agg_dict is not None
|
|
394
|
+
else _legacy_cross_agg_dict(self.udf)
|
|
395
|
+
)
|
|
396
|
+
self.subset_condition_dict = (
|
|
397
|
+
subset_condition_dict if subset_condition_dict is not None
|
|
398
|
+
else _legacy_subset_condition_dict()
|
|
399
|
+
)
|
|
400
|
+
self.eval_ylabels = (
|
|
401
|
+
eval_ylabels if eval_ylabels is not None else list(_LEGACY_EVAL_YLABELS)
|
|
402
|
+
)
|
|
403
|
+
self.grp_namelist = (
|
|
404
|
+
grp_namelist if grp_namelist is not None else list(_LEGACY_GRP_NAMELIST)
|
|
405
|
+
)
|
|
406
|
+
self.gains_display_metric_list = (
|
|
407
|
+
gains_display_metric_list if gains_display_metric_list is not None
|
|
408
|
+
else list(_DEFAULT_GAINS_DISPLAY_METRICS)
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _score_order(self) -> List[str]:
|
|
412
|
+
scores = []
|
|
413
|
+
if self.base_score is not None:
|
|
414
|
+
scores.append(self.base_score)
|
|
415
|
+
scores.extend(self.comp_scrlist or [])
|
|
416
|
+
return scores
|
|
417
|
+
|
|
418
|
+
def _filter_positive_scores(self, data: pd.DataFrame, scores: List[str]) -> pd.DataFrame:
|
|
419
|
+
if not self.positive_score_only or not scores:
|
|
420
|
+
return data
|
|
421
|
+
mask = pd.Series(True, index=data.index)
|
|
422
|
+
for score in scores:
|
|
423
|
+
if score in data.columns:
|
|
424
|
+
mask &= data[score] > 0
|
|
425
|
+
return data.loc[mask]
|
|
426
|
+
|
|
427
|
+
def _build_performance_evaluator(
|
|
428
|
+
self,
|
|
429
|
+
score: str,
|
|
430
|
+
dist_bins: int,
|
|
431
|
+
pct_bins: int,
|
|
432
|
+
min_bin_prop: Optional[float] = None,
|
|
433
|
+
include_missing: Optional[bool] = None,
|
|
434
|
+
equal_freq: Optional[bool] = None,
|
|
435
|
+
) -> PerformanceEvaluator:
|
|
436
|
+
return PerformanceEvaluator(
|
|
437
|
+
tgt_name=self.dep,
|
|
438
|
+
scr_name=score,
|
|
439
|
+
dist_bins=dist_bins,
|
|
440
|
+
pct_bins=pct_bins,
|
|
441
|
+
precision=self.precision,
|
|
442
|
+
min_bin_prop=self.min_bin_prop if min_bin_prop is None else min_bin_prop,
|
|
443
|
+
include_missing=False if include_missing is None else include_missing,
|
|
444
|
+
equal_freq=self.equal_freq if equal_freq is None else equal_freq,
|
|
445
|
+
chi2_method=self.chi2_method,
|
|
446
|
+
init_equi_bins=self.init_equi_bins,
|
|
447
|
+
chi2_p=self.chi2_p,
|
|
448
|
+
tree_binning=self.tree_binning,
|
|
449
|
+
random_state=self.seed,
|
|
450
|
+
weight_col=self.weight_col,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
def _build_gains_calculator(
|
|
454
|
+
self,
|
|
455
|
+
data: pd.DataFrame,
|
|
456
|
+
score: str,
|
|
457
|
+
nbins: int,
|
|
458
|
+
fillna,
|
|
459
|
+
spec_values,
|
|
460
|
+
include_missing: bool,
|
|
461
|
+
ascending: bool = True,
|
|
462
|
+
) -> GainsTableCalculator:
|
|
463
|
+
return GainsTableCalculator(
|
|
464
|
+
data=data,
|
|
465
|
+
dep=self.dep,
|
|
466
|
+
score=score,
|
|
467
|
+
nbins=nbins,
|
|
468
|
+
precision=self.precision,
|
|
469
|
+
min_bin_prop=self.min_bin_prop,
|
|
470
|
+
include_missing=include_missing,
|
|
471
|
+
equal_freq=self.equal_freq,
|
|
472
|
+
chi2_method=self.chi2_method,
|
|
473
|
+
chi2_p=self.chi2_p,
|
|
474
|
+
init_equi_bins=self.init_equi_bins,
|
|
475
|
+
fillna=fillna,
|
|
476
|
+
spec_values=spec_values,
|
|
477
|
+
tree_binning=self.tree_binning,
|
|
478
|
+
random_state=self.seed,
|
|
479
|
+
ascending=ascending,
|
|
480
|
+
weight_col=self.weight_col,
|
|
481
|
+
)
|
|
380
482
|
|
|
381
483
|
def __init_excel_master(self):
|
|
382
484
|
"""
|
|
@@ -491,13 +593,17 @@ class Model_Evaluation_Tool:
|
|
|
491
593
|
dist_bins: int = 100,
|
|
492
594
|
pct_bins: int = 10,
|
|
493
595
|
min_data_size: int = 50,
|
|
494
|
-
sync_data_size: bool = True
|
|
596
|
+
sync_data_size: bool = True,
|
|
597
|
+
min_bin_prop: Optional[float] = None,
|
|
598
|
+
include_missing: Optional[bool] = None,
|
|
599
|
+
equal_freq: Optional[bool] = None,
|
|
495
600
|
) -> pd.DataFrame:
|
|
496
601
|
"""
|
|
497
602
|
Compare model performance across different scores.
|
|
498
603
|
|
|
499
604
|
This method calculates performance metrics (AUC, KS, etc.) for the base score
|
|
500
|
-
and all comparison scores
|
|
605
|
+
and all comparison scores via ``PerformanceEvaluator``, optionally filtering
|
|
606
|
+
by group.
|
|
501
607
|
|
|
502
608
|
Parameters
|
|
503
609
|
----------
|
|
@@ -513,108 +619,90 @@ class Model_Evaluation_Tool:
|
|
|
513
619
|
Minimum data size per bin. Default is 50.
|
|
514
620
|
sync_data_size : bool, optional
|
|
515
621
|
Whether to filter out zero/negative scores. Default is True.
|
|
622
|
+
min_bin_prop : float, optional
|
|
623
|
+
Minimum bin proportion. Defaults to the instance setting.
|
|
624
|
+
include_missing : bool, optional
|
|
625
|
+
Whether to include missing values in performance binning.
|
|
626
|
+
Defaults to ``False`` for performance comparison.
|
|
627
|
+
equal_freq : bool, optional
|
|
628
|
+
Use equal-frequency binning. Defaults to the instance setting.
|
|
516
629
|
|
|
517
630
|
Returns
|
|
518
631
|
-------
|
|
519
632
|
pandas.DataFrame
|
|
520
633
|
Performance comparison results sorted by score order.
|
|
521
634
|
"""
|
|
522
|
-
|
|
523
635
|
data = self.data.copy() if data is None else data.copy()
|
|
524
636
|
score_list = self.comp_scrlist
|
|
525
637
|
dep = self.dep
|
|
526
638
|
base_score = self.base_score
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
639
|
+
sample_name = 'oot'
|
|
640
|
+
|
|
641
|
+
if base_score is None:
|
|
642
|
+
return pd.DataFrame()
|
|
643
|
+
|
|
530
644
|
valid_mask = (
|
|
531
|
-
data[dep].notna()
|
|
532
|
-
data[base_score].notna()
|
|
533
|
-
np.isfinite(data[base_score])
|
|
645
|
+
data[dep].notna()
|
|
646
|
+
& data[base_score].notna()
|
|
647
|
+
& np.isfinite(data[base_score])
|
|
534
648
|
)
|
|
535
|
-
clean_data = data[valid_mask]
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
oot_df_ = clean_data.copy()
|
|
544
|
-
|
|
545
|
-
if sync_data_size:
|
|
546
|
-
if len(score_list) > 0:
|
|
547
|
-
score_query = " and ".join([s + " > 0" for s in score_list])
|
|
548
|
-
oot_df_ = oot_df.query(score_query) if oot_df is not None else None
|
|
549
|
-
|
|
649
|
+
clean_data = data.loc[valid_mask]
|
|
650
|
+
if clean_data.empty:
|
|
651
|
+
return pd.DataFrame()
|
|
652
|
+
|
|
653
|
+
shared_data = clean_data.copy()
|
|
654
|
+
if sync_data_size and score_list:
|
|
655
|
+
shared_data = self._filter_positive_scores(shared_data, score_list)
|
|
656
|
+
|
|
550
657
|
perf_comp_dict = {}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
658
|
+
score_order = self._score_order()
|
|
659
|
+
|
|
660
|
+
for score in score_order:
|
|
661
|
+
if score not in clean_data.columns:
|
|
662
|
+
continue
|
|
663
|
+
score_data = shared_data if sync_data_size and score in (score_list or []) else clean_data
|
|
664
|
+
score_data = score_data.loc[score_data[score] > 0] if self.positive_score_only else score_data
|
|
665
|
+
if score_data.empty:
|
|
666
|
+
continue
|
|
667
|
+
|
|
668
|
+
evaluator = self._build_performance_evaluator(
|
|
669
|
+
score=score,
|
|
561
670
|
dist_bins=dist_bins,
|
|
562
671
|
pct_bins=pct_bins,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
672
|
+
min_bin_prop=min_bin_prop,
|
|
673
|
+
include_missing=include_missing,
|
|
674
|
+
equal_freq=equal_freq,
|
|
675
|
+
)
|
|
676
|
+
perf = evaluator.add_dataset(sample_name, score_data).evaluate(
|
|
567
677
|
oot_grp_name=grp_name,
|
|
568
|
-
min_data_size=min_data_size
|
|
678
|
+
min_data_size=min_data_size,
|
|
679
|
+
to_show=False,
|
|
680
|
+
display=False,
|
|
569
681
|
)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
perf_comp_dict[base_score] = base_perf
|
|
575
|
-
|
|
576
|
-
if len(score_list) > 0:
|
|
577
|
-
for score in score_list:
|
|
578
|
-
"""Loop Score List."""
|
|
579
|
-
perf_comp_dict[score] = get_perf_summary(
|
|
580
|
-
train=None,
|
|
581
|
-
validation=None,
|
|
582
|
-
oot=oot_df_.query(f"{score} > 0"),
|
|
583
|
-
scr_name=score,
|
|
584
|
-
tgt_name=dep,
|
|
585
|
-
to_show=False,
|
|
586
|
-
display=False,
|
|
587
|
-
dist_bins=dist_bins,
|
|
588
|
-
pct_bins=pct_bins,
|
|
589
|
-
precision=precision,
|
|
590
|
-
min_bin_prop=0.05,
|
|
591
|
-
include_missing=False,
|
|
592
|
-
equal_freq=True,
|
|
593
|
-
oot_grp_name=grp_name,
|
|
594
|
-
min_data_size=min_data_size
|
|
595
|
-
).query(f"index == '{sample_name}'")
|
|
596
|
-
|
|
682
|
+
if isinstance(perf, pd.DataFrame) and not perf.empty and 'index' in perf.columns:
|
|
683
|
+
perf = perf.query(f"index == '{sample_name}'")
|
|
684
|
+
perf_comp_dict[score] = perf
|
|
685
|
+
|
|
597
686
|
if not perf_comp_dict:
|
|
598
687
|
return pd.DataFrame()
|
|
599
|
-
|
|
688
|
+
|
|
600
689
|
perf_comp_res = []
|
|
601
690
|
for score_name, perf in perf_comp_dict.items():
|
|
602
691
|
perf = perf.copy()
|
|
603
692
|
perf['score_name'] = score_name
|
|
604
693
|
perf_comp_res.append(perf)
|
|
605
|
-
|
|
694
|
+
|
|
606
695
|
perf_comp_res = pd.concat(perf_comp_res)
|
|
607
|
-
|
|
696
|
+
|
|
608
697
|
drop_cols = ['AUC_Shift', 'KS_Shift']
|
|
609
698
|
for col in drop_cols:
|
|
610
699
|
if col in perf_comp_res.columns:
|
|
611
700
|
perf_comp_res = perf_comp_res.drop(columns=[col])
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
order_map = {val: i for i, val in enumerate(custom_order) if val in perf_comp_dict}
|
|
701
|
+
|
|
702
|
+
order_map = {val: i for i, val in enumerate(score_order) if val in perf_comp_dict}
|
|
615
703
|
perf_comp_res['sort_key'] = perf_comp_res['score_name'].map(order_map)
|
|
616
704
|
perf_comp_res_sorted = perf_comp_res.sort_values('sort_key').drop('sort_key', axis=1)
|
|
617
|
-
|
|
705
|
+
|
|
618
706
|
return perf_comp_res_sorted
|
|
619
707
|
|
|
620
708
|
def get_gains_summary(
|
|
@@ -627,15 +715,16 @@ class Model_Evaluation_Tool:
|
|
|
627
715
|
withSummary: bool = True,
|
|
628
716
|
add_func: Optional[Callable] = None,
|
|
629
717
|
sync_range: bool = True,
|
|
630
|
-
spec_values
|
|
631
|
-
include_missing =
|
|
632
|
-
fillna
|
|
718
|
+
spec_values=None,
|
|
719
|
+
include_missing: Optional[bool] = None,
|
|
720
|
+
fillna=None,
|
|
633
721
|
) -> pd.DataFrame:
|
|
634
722
|
"""
|
|
635
723
|
Generate gains summary table for score analysis.
|
|
636
724
|
|
|
637
|
-
This method creates gains tables showing the
|
|
638
|
-
across score bins, with optional group stratification.
|
|
725
|
+
This method creates gains tables via ``GainsTableCalculator``, showing the
|
|
726
|
+
distribution of targets across score bins, with optional group stratification.
|
|
727
|
+
Custom metrics can be injected through ``add_func``.
|
|
639
728
|
|
|
640
729
|
Parameters
|
|
641
730
|
----------
|
|
@@ -652,9 +741,15 @@ class Model_Evaluation_Tool:
|
|
|
652
741
|
withSummary : bool, optional
|
|
653
742
|
Include summary row. Default is True.
|
|
654
743
|
add_func : callable, optional
|
|
655
|
-
|
|
744
|
+
Custom metric function merged into each gains table.
|
|
656
745
|
sync_range : bool, optional
|
|
657
746
|
Synchronize bin ranges. Default is True.
|
|
747
|
+
spec_values : list, optional
|
|
748
|
+
Special values to treat separately during binning.
|
|
749
|
+
include_missing : bool, optional
|
|
750
|
+
Whether to include missing values. Defaults to the instance setting.
|
|
751
|
+
fillna : any, optional
|
|
752
|
+
Missing-value fill for score binning. Defaults to the instance setting.
|
|
658
753
|
|
|
659
754
|
Returns
|
|
660
755
|
-------
|
|
@@ -663,188 +758,120 @@ class Model_Evaluation_Tool:
|
|
|
663
758
|
"""
|
|
664
759
|
if grp_disp_metric is None:
|
|
665
760
|
grp_disp_metric = ['N', 'PROP', 'AVG_BAD', 'LIFT']
|
|
666
|
-
|
|
761
|
+
|
|
667
762
|
if fillna is None:
|
|
668
763
|
fillna = self.fillna
|
|
669
|
-
|
|
764
|
+
if spec_values is None:
|
|
765
|
+
spec_values = self.spec_values
|
|
766
|
+
if include_missing is None:
|
|
767
|
+
include_missing = self.include_missing
|
|
768
|
+
|
|
670
769
|
new_oot_df = self.data.copy() if data is None else data.copy()
|
|
671
|
-
|
|
770
|
+
|
|
672
771
|
dep = self.dep
|
|
673
|
-
tree_binning = self.tree_binning
|
|
674
|
-
digit = self.precision
|
|
675
772
|
base_score = self.base_score
|
|
676
|
-
equal_freq = self.equal_freq
|
|
677
773
|
min_data_size = self.min_data_size
|
|
678
774
|
nbins = self.nbins if grp_name is None else grp_nbins
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
score_list = [base_score] + self.comp_scrlist
|
|
775
|
+
|
|
776
|
+
score_list = self._score_order()
|
|
682
777
|
display_metric_list = self.gains_display_metric_list if add_func is None else None
|
|
683
|
-
|
|
778
|
+
|
|
684
779
|
if self.data is None or len(self.data) == 0:
|
|
685
780
|
empty_df = pd.DataFrame()
|
|
686
781
|
empty_df.index = pd.MultiIndex.from_tuples([], names=['_bin_num', '_bin_range'])
|
|
687
|
-
return empty_df
|
|
688
|
-
|
|
782
|
+
return empty_df
|
|
783
|
+
|
|
689
784
|
gains_table_dict = {}
|
|
690
785
|
for score in score_list:
|
|
786
|
+
if score not in new_oot_df.columns:
|
|
787
|
+
continue
|
|
788
|
+
|
|
789
|
+
calculator = self._build_gains_calculator(
|
|
790
|
+
data=new_oot_df,
|
|
791
|
+
score=score,
|
|
792
|
+
nbins=nbins,
|
|
793
|
+
fillna=fillna,
|
|
794
|
+
spec_values=spec_values,
|
|
795
|
+
include_missing=include_missing,
|
|
796
|
+
ascending=True,
|
|
797
|
+
)
|
|
798
|
+
|
|
691
799
|
if grp_name is None:
|
|
692
|
-
|
|
693
|
-
gains_table_dict[score] = get_gains_table(
|
|
694
|
-
# data=new_oot_df.query(f"{score} > 0"),
|
|
695
|
-
data=new_oot_df,
|
|
696
|
-
dep=dep,
|
|
697
|
-
ascending=True,
|
|
698
|
-
tree_binning=tree_binning,
|
|
699
|
-
nbins=nbins,
|
|
700
|
-
precision=digit,
|
|
701
|
-
min_bin_prop=min_bin_prop,
|
|
702
|
-
include_missing=False,
|
|
703
|
-
score=score,
|
|
704
|
-
fillna = fillna,
|
|
705
|
-
equal_freq=equal_freq,
|
|
800
|
+
gains_table_dict[score] = calculator.calculate(
|
|
706
801
|
withSummary=withSummary,
|
|
707
802
|
add_func=add_func,
|
|
708
|
-
spec_values=spec_values
|
|
709
803
|
)
|
|
710
804
|
if display_metric_list is not None:
|
|
711
805
|
gains_table_dict[score] = gains_table_dict[score][display_metric_list]
|
|
712
806
|
gains_table_dict[score] = gains_table_dict[score].reset_index(drop=False)
|
|
713
|
-
|
|
714
807
|
else:
|
|
715
808
|
if new_oot_df.query(f"{score} > 0").shape[0] > 10:
|
|
716
|
-
oot_by_group =
|
|
717
|
-
|
|
718
|
-
dep=dep,
|
|
719
|
-
ascending=True,
|
|
720
|
-
tree_binning=tree_binning,
|
|
721
|
-
nbins=grp_nbins,
|
|
722
|
-
precision=digit,
|
|
723
|
-
min_bin_prop=min_bin_prop,
|
|
724
|
-
include_missing=False,
|
|
725
|
-
score=score,
|
|
726
|
-
fillna = fillna,
|
|
727
|
-
equal_freq=equal_freq,
|
|
728
|
-
sync_range=sync_range,
|
|
729
|
-
retSummary=False,
|
|
730
|
-
grp_name=grp_name,
|
|
809
|
+
oot_by_group = calculator.calculate(
|
|
810
|
+
grp_name=grp_name,
|
|
731
811
|
min_data_size=min_data_size,
|
|
812
|
+
sync_range=sync_range,
|
|
813
|
+
retSummary=False,
|
|
732
814
|
withSummary=withSummary,
|
|
733
|
-
|
|
734
|
-
add_func=add_func
|
|
815
|
+
add_func=add_func,
|
|
735
816
|
)
|
|
736
817
|
if display_metric_list is not None:
|
|
737
818
|
oot_by_group = oot_by_group[[*display_metric_list, grp_name]]
|
|
738
819
|
oot_by_group = oot_by_group.reset_index(drop=False)
|
|
739
|
-
|
|
820
|
+
|
|
740
821
|
grp_metric_dict = {}
|
|
741
822
|
for value in [x for x in grp_disp_metric if x not in ['PROP']]:
|
|
742
823
|
grp_metric_dict[value] = oot_by_group\
|
|
743
824
|
.reset_index(drop=False)\
|
|
744
|
-
.pivot_table(
|
|
745
|
-
|
|
825
|
+
.pivot_table(
|
|
826
|
+
index=['_bin_num', '_bin_range'],
|
|
827
|
+
columns=[grp_name],
|
|
828
|
+
values=value,
|
|
829
|
+
margins=True,
|
|
830
|
+
margins_name="Grand_Total",
|
|
831
|
+
)
|
|
832
|
+
|
|
746
833
|
if 'PROP' in grp_disp_metric:
|
|
747
834
|
tot_cnt = oot_by_group\
|
|
748
835
|
.reset_index(drop=False)\
|
|
749
|
-
.pivot_table(
|
|
750
|
-
|
|
836
|
+
.pivot_table(
|
|
837
|
+
index=['_bin_num', '_bin_range'],
|
|
838
|
+
columns=[grp_name],
|
|
839
|
+
values=['N'],
|
|
840
|
+
margins=True,
|
|
841
|
+
margins_name="Grand_Total",
|
|
842
|
+
aggfunc='sum',
|
|
843
|
+
)
|
|
844
|
+
|
|
751
845
|
if tot_cnt.shape[0] > 0:
|
|
752
|
-
grp_metric_dict['PROP'] = tot_cnt.iloc[0:tot_cnt.shape[0]:] / np.array(
|
|
753
|
-
|
|
754
|
-
|
|
846
|
+
grp_metric_dict['PROP'] = tot_cnt.iloc[0:tot_cnt.shape[0]:] / np.array(
|
|
847
|
+
tot_cnt.iloc[-1:].T['Grand_Total'].tolist()
|
|
848
|
+
)
|
|
849
|
+
grp_metric_dict['PROP'].columns = grp_metric_dict['PROP'].rename(
|
|
850
|
+
columns={'N': ''}
|
|
851
|
+
).columns.map("".join)
|
|
852
|
+
|
|
755
853
|
fnl_grp_gains_res = []
|
|
756
854
|
for colname, gains in grp_metric_dict.items():
|
|
757
855
|
gains = gains.copy()
|
|
758
856
|
gains['variable'] = colname
|
|
759
|
-
# gains = gains.reset_index(drop=False)
|
|
760
857
|
if disp:
|
|
761
858
|
from IPython.display import display
|
|
762
859
|
display(gains)
|
|
763
860
|
fnl_grp_gains_res.append(gains)
|
|
764
861
|
fnl_grp_gains_res = pd.concat(fnl_grp_gains_res)
|
|
765
|
-
|
|
862
|
+
|
|
766
863
|
gains_table_dict[score] = oot_by_group
|
|
767
|
-
|
|
864
|
+
|
|
768
865
|
fnl_gains_res = []
|
|
769
866
|
for score_name, gains_res in gains_table_dict.items():
|
|
770
867
|
gains_res = gains_res.copy()
|
|
771
868
|
gains_res['score_name'] = score_name
|
|
772
869
|
fnl_gains_res.append(gains_res)
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
def get_cust_metric_summary(
|
|
779
|
-
self,
|
|
780
|
-
eval_metrics: Optional[List[str]] = None,
|
|
781
|
-
metric_agg_func: Union[str, Callable] = 'mean',
|
|
782
|
-
withSummary: bool = True,
|
|
783
|
-
spec_values = []
|
|
784
|
-
) -> pd.DataFrame:
|
|
785
|
-
"""
|
|
786
|
-
Generate summary for custom evaluation metrics.
|
|
787
|
-
|
|
788
|
-
This method calculates gains tables for custom metrics defined in
|
|
789
|
-
self.eval_metrics, allowing analysis of various feature distributions
|
|
790
|
-
across score bins.
|
|
791
|
-
|
|
792
|
-
Parameters
|
|
793
|
-
----------
|
|
794
|
-
eval_metrics : list, optional
|
|
795
|
-
List of metrics to evaluate. If None, uses self.eval_metrics.
|
|
796
|
-
metric_agg_func : str or callable, optional
|
|
797
|
-
Aggregation function for metrics. Default is 'mean'.
|
|
798
|
-
withSummary : bool, optional
|
|
799
|
-
Include summary row. Default is True.
|
|
800
|
-
|
|
801
|
-
Returns
|
|
802
|
-
-------
|
|
803
|
-
pandas.DataFrame
|
|
804
|
-
Custom metric summary with score names.
|
|
805
|
-
"""
|
|
806
|
-
data = self.data.copy()
|
|
807
|
-
tgt_name = self.dep
|
|
808
|
-
nbins = self.nbins
|
|
809
|
-
precision = self.precision
|
|
810
|
-
min_bin_prop = self.min_bin_prop
|
|
811
|
-
include_missing = self.include_missing
|
|
812
|
-
equal_freq = self.equal_freq
|
|
813
|
-
|
|
814
|
-
score_list = [self.base_score] + self.comp_scrlist
|
|
815
|
-
|
|
816
|
-
if eval_metrics is None:
|
|
817
|
-
eval_metrics = self.eval_metrics
|
|
818
|
-
|
|
819
|
-
cust_metric_res = {}
|
|
820
|
-
for score in score_list:
|
|
821
|
-
cust_metric_res[score] = get_gains_table_by_cust_metrics(
|
|
822
|
-
data=data,
|
|
823
|
-
dep=tgt_name,
|
|
824
|
-
nbins=nbins,
|
|
825
|
-
precision=precision,
|
|
826
|
-
min_bin_prop=min_bin_prop,
|
|
827
|
-
include_missing=include_missing,
|
|
828
|
-
score=score,
|
|
829
|
-
tree_binning=self.tree_binning,
|
|
830
|
-
equal_freq=equal_freq,
|
|
831
|
-
fillna=-999999,
|
|
832
|
-
spec_values=spec_values,
|
|
833
|
-
ascending=True,
|
|
834
|
-
eval_metrics=eval_metrics,
|
|
835
|
-
metric_agg_func=metric_agg_func,
|
|
836
|
-
withSummary=withSummary
|
|
837
|
-
)
|
|
838
|
-
|
|
839
|
-
cust_gains_fnl_res = []
|
|
840
|
-
for score_name, res in cust_metric_res.items():
|
|
841
|
-
res = res.copy()
|
|
842
|
-
res['score_name'] = score_name
|
|
843
|
-
cust_gains_fnl_res.append(res)
|
|
844
|
-
|
|
845
|
-
cust_gains_fnl_res = pd.concat(cust_gains_fnl_res)
|
|
846
|
-
|
|
847
|
-
return cust_gains_fnl_res
|
|
870
|
+
|
|
871
|
+
if not fnl_gains_res:
|
|
872
|
+
return pd.DataFrame()
|
|
873
|
+
|
|
874
|
+
return pd.concat(fnl_gains_res)
|
|
848
875
|
|
|
849
876
|
def get_cross_risk_summary(
|
|
850
877
|
self,
|
|
@@ -852,7 +879,8 @@ class Model_Evaluation_Tool:
|
|
|
852
879
|
nbins: int = 5,
|
|
853
880
|
equal_freq: Optional[bool] = None,
|
|
854
881
|
disp: bool = True,
|
|
855
|
-
spec_values
|
|
882
|
+
spec_values=None,
|
|
883
|
+
binning_numeric=None,
|
|
856
884
|
) -> pd.DataFrame:
|
|
857
885
|
"""
|
|
858
886
|
Generate cross-risk analysis summary between base and comparison scores.
|
|
@@ -870,6 +898,13 @@ class Model_Evaluation_Tool:
|
|
|
870
898
|
Use equal frequency binning. If None, uses self.equal_freq.
|
|
871
899
|
disp : bool, optional
|
|
872
900
|
Whether to display results. Default is True.
|
|
901
|
+
spec_values : list, optional
|
|
902
|
+
Special values to keep separate during binning.
|
|
903
|
+
binning_numeric : bool or list/tuple of bool, optional
|
|
904
|
+
Whether numeric score columns should be binned before cross-risk
|
|
905
|
+
aggregation. If None, defaults to [True, True] for backward
|
|
906
|
+
compatibility. Passing a bool applies the same setting to both
|
|
907
|
+
base and comparison score.
|
|
873
908
|
|
|
874
909
|
Returns
|
|
875
910
|
-------
|
|
@@ -890,7 +925,41 @@ class Model_Evaluation_Tool:
|
|
|
890
925
|
|
|
891
926
|
if cross_agg_dict is None:
|
|
892
927
|
cross_agg_dict = self.cross_agg_dict.copy()
|
|
893
|
-
|
|
928
|
+
if spec_values is None:
|
|
929
|
+
spec_values = self.spec_values
|
|
930
|
+
if binning_numeric is None:
|
|
931
|
+
binning_numeric = [True, True]
|
|
932
|
+
elif isinstance(binning_numeric, (bool, np.bool_)):
|
|
933
|
+
binning_numeric = [bool(binning_numeric), bool(binning_numeric)]
|
|
934
|
+
elif isinstance(binning_numeric, (list, tuple)):
|
|
935
|
+
if len(binning_numeric) != 2:
|
|
936
|
+
raise ValueError(
|
|
937
|
+
"binning_numeric must be a bool or a two-element list/tuple."
|
|
938
|
+
)
|
|
939
|
+
if not all(isinstance(value, (bool, np.bool_)) for value in binning_numeric):
|
|
940
|
+
raise TypeError("binning_numeric values must be booleans.")
|
|
941
|
+
binning_numeric = [bool(binning_numeric[0]), bool(binning_numeric[1])]
|
|
942
|
+
else:
|
|
943
|
+
raise TypeError("binning_numeric must be None, a bool, or a two-element list/tuple.")
|
|
944
|
+
|
|
945
|
+
def _ensure_three_level_columns(frame: pd.DataFrame) -> pd.DataFrame:
|
|
946
|
+
frame = frame.copy()
|
|
947
|
+
if isinstance(frame.columns, pd.MultiIndex):
|
|
948
|
+
normalized_columns = []
|
|
949
|
+
for col in frame.columns:
|
|
950
|
+
col_tuple = tuple(col)
|
|
951
|
+
if len(col_tuple) < 3:
|
|
952
|
+
col_tuple = col_tuple + ("",) * (3 - len(col_tuple))
|
|
953
|
+
elif len(col_tuple) > 3:
|
|
954
|
+
col_tuple = col_tuple[:2] + ("_".join(map(str, col_tuple[2:])),)
|
|
955
|
+
normalized_columns.append(col_tuple)
|
|
956
|
+
frame.columns = pd.MultiIndex.from_tuples(normalized_columns)
|
|
957
|
+
else:
|
|
958
|
+
frame.columns = pd.MultiIndex.from_tuples(
|
|
959
|
+
[(col, "", "") for col in frame.columns]
|
|
960
|
+
)
|
|
961
|
+
return frame
|
|
962
|
+
|
|
894
963
|
multi_scr_res = {}
|
|
895
964
|
for compare_scr in compare_scrlist:
|
|
896
965
|
logger.info(compare_scr)
|
|
@@ -919,7 +988,7 @@ class Model_Evaluation_Tool:
|
|
|
919
988
|
min_bin_prop=min_bin_prop,
|
|
920
989
|
include_missing=include_missing,
|
|
921
990
|
equal_freq=equal_freq,
|
|
922
|
-
binning_numeric=
|
|
991
|
+
binning_numeric=binning_numeric,
|
|
923
992
|
tree_binning=tree_binning,
|
|
924
993
|
agg_func=aggfunc,
|
|
925
994
|
fillna=fillna,
|
|
@@ -928,7 +997,7 @@ class Model_Evaluation_Tool:
|
|
|
928
997
|
|
|
929
998
|
fnl_res = []
|
|
930
999
|
for colname, res in cross_res.items():
|
|
931
|
-
res = res
|
|
1000
|
+
res = _ensure_three_level_columns(res)
|
|
932
1001
|
res[('', 'eval_metric', '')] = colname
|
|
933
1002
|
res = res.rename(columns={"<lambda>": "", "count": "n"})
|
|
934
1003
|
if disp:
|
|
@@ -1449,4 +1518,4 @@ class Model_Evaluation_Tool:
|
|
|
1449
1518
|
-------
|
|
1450
1519
|
EvaluationPipeline
|
|
1451
1520
|
"""
|
|
1452
|
-
return EvaluationPipeline(self, data)
|
|
1521
|
+
return EvaluationPipeline(self, data)
|