SuperModelingFactory 0.3.3__tar.gz → 0.3.5__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.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/Evaluation_Tool.py +11 -2
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +23 -5
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/GBM_Tool.py +5 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/LRM_Tool.py +41 -6
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/credit_model.py +146 -2
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/feature_validation.py +65 -4
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/reject_inference.py +163 -34
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/sample_analysis.py +13 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/score_comparison.py +46 -2
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Reject_Infer.py +106 -50
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Reject_Infer.pyi +5 -5
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Adapter.py +35 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +13 -4
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/PKG-INFO +2 -2
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/README.md +1 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/pyproject.toml +1 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/setup.py +1 -1
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/LICENSE +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/MANIFEST.in +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Report/__init__.py +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/requirements.txt +0 -0
- {supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/setup.cfg +0 -0
{supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Eval/Evaluation_Tool.py
RENAMED
|
@@ -597,6 +597,7 @@ class Model_Evaluation_Tool:
|
|
|
597
597
|
min_bin_prop: Optional[float] = None,
|
|
598
598
|
include_missing: Optional[bool] = None,
|
|
599
599
|
equal_freq: Optional[bool] = None,
|
|
600
|
+
sample_name: Optional[str] = None,
|
|
600
601
|
) -> pd.DataFrame:
|
|
601
602
|
"""
|
|
602
603
|
Compare model performance across different scores.
|
|
@@ -626,7 +627,12 @@ class Model_Evaluation_Tool:
|
|
|
626
627
|
Defaults to ``False`` for performance comparison.
|
|
627
628
|
equal_freq : bool, optional
|
|
628
629
|
Use equal-frequency binning. Defaults to the instance setting.
|
|
629
|
-
|
|
630
|
+
sample_name : str, optional
|
|
631
|
+
Label applied to the evaluated dataset and emitted in the result's
|
|
632
|
+
``index`` column. Defaults to ``'all'`` (the whole ``data`` treated as
|
|
633
|
+
a single sample). Callers evaluating a specific slice can pass e.g.
|
|
634
|
+
``'global'`` or a split name.
|
|
635
|
+
|
|
630
636
|
Returns
|
|
631
637
|
-------
|
|
632
638
|
pandas.DataFrame
|
|
@@ -636,7 +642,10 @@ class Model_Evaluation_Tool:
|
|
|
636
642
|
score_list = self.comp_scrlist
|
|
637
643
|
dep = self.dep
|
|
638
644
|
base_score = self.base_score
|
|
639
|
-
|
|
645
|
+
# Label for the single (unsplit) dataset being evaluated; surfaces as the
|
|
646
|
+
# 'index' column of the result. Defaults to a neutral 'all' rather than the
|
|
647
|
+
# historical (and misleading) 'oot', which implied an out-of-time sample.
|
|
648
|
+
sample_name = sample_name if sample_name is not None else 'all'
|
|
640
649
|
|
|
641
650
|
if base_score is None:
|
|
642
651
|
return pd.DataFrame()
|
|
@@ -155,17 +155,35 @@ class PSICalculator:
|
|
|
155
155
|
|
|
156
156
|
detail = {}
|
|
157
157
|
rows = []
|
|
158
|
-
|
|
158
|
+
resolved_group = group_name if group_name is not None else group_by
|
|
159
|
+
if resolved_group is None:
|
|
160
|
+
groups = [(None, current_data, {})]
|
|
161
|
+
else:
|
|
162
|
+
group_cols = [resolved_group] if isinstance(resolved_group, str) else list(resolved_group)
|
|
163
|
+
grouped_data = current_data.copy()
|
|
164
|
+
for col in group_cols:
|
|
165
|
+
if grouped_data[col].isna().sum() > 0:
|
|
166
|
+
grouped_data[col] = grouped_data[col].fillna("__NULL__")
|
|
167
|
+
group_key = group_cols[0] if len(group_cols) == 1 else group_cols
|
|
168
|
+
groups = []
|
|
169
|
+
for grp_value, grp_df in grouped_data.groupby(group_key, dropna=False):
|
|
170
|
+
if len(group_cols) == 1:
|
|
171
|
+
group_info = {group_cols[0]: grp_value}
|
|
172
|
+
else:
|
|
173
|
+
value_tuple = grp_value if isinstance(grp_value, tuple) else (grp_value,)
|
|
174
|
+
group_info = dict(zip(group_cols, value_tuple))
|
|
175
|
+
groups.append((grp_value, grp_df, group_info))
|
|
176
|
+
|
|
159
177
|
for var in varlist:
|
|
160
178
|
expected_bins = adapter.assign_bins(expected_df, var)
|
|
161
|
-
for grp_value, grp_df in groups:
|
|
179
|
+
for grp_value, grp_df, group_info in groups:
|
|
162
180
|
current_bins = adapter.assign_bins(grp_df, var)
|
|
163
181
|
row = {"var": var, "psi": round(_psi_from_bins(expected_bins, current_bins, self.content), self.precision)}
|
|
164
|
-
|
|
165
|
-
row[group_name or group_by] = grp_value
|
|
182
|
+
row.update(group_info)
|
|
166
183
|
rows.append(row)
|
|
167
184
|
if return_details:
|
|
168
|
-
|
|
185
|
+
detail_key = var if resolved_group is None else (var, grp_value)
|
|
186
|
+
detail[detail_key] = {
|
|
169
187
|
"expected_bins": expected_bins.value_counts(normalize=True, dropna=False),
|
|
170
188
|
"current_bins": current_bins.value_counts(normalize=True, dropna=False),
|
|
171
189
|
}
|
|
@@ -153,7 +153,9 @@ def _normalize_catboost_params(params_dict):
|
|
|
153
153
|
``eval_metric`` defaults to ``'AUC'`` when neither ``eval_metric`` nor
|
|
154
154
|
``metric`` is supplied. Common lower-case aliases (e.g. ``'auc'``,
|
|
155
155
|
``'logloss'``) are normalized to the case-sensitive names CatBoost expects;
|
|
156
|
-
any other string is left untouched.
|
|
156
|
+
any other string is left untouched. CatBoost writes ``catboost_info`` under
|
|
157
|
+
the current working directory by default, so SMF disables file output unless
|
|
158
|
+
the caller explicitly passes ``allow_writing_files`` or ``train_dir``.
|
|
157
159
|
|
|
158
160
|
Returns
|
|
159
161
|
-------
|
|
@@ -174,6 +176,8 @@ def _normalize_catboost_params(params_dict):
|
|
|
174
176
|
|
|
175
177
|
params.setdefault('loss_function', 'Logloss')
|
|
176
178
|
params.setdefault('verbose', False)
|
|
179
|
+
if 'allow_writing_files' not in params and 'train_dir' not in params:
|
|
180
|
+
params['allow_writing_files'] = False
|
|
177
181
|
|
|
178
182
|
# eval_metric must be passed to the CatBoostClassifier constructor, not fit().
|
|
179
183
|
if eval_metric is None:
|
|
@@ -523,6 +523,44 @@ class LRMaster:
|
|
|
523
523
|
self._data = data
|
|
524
524
|
return self
|
|
525
525
|
|
|
526
|
+
def __getstate__(self):
|
|
527
|
+
"""
|
|
528
|
+
Exclude the reference training frame from pickling.
|
|
529
|
+
|
|
530
|
+
``self._data`` holds the full training set (row-level customer records)
|
|
531
|
+
only as a convenience fallback for
|
|
532
|
+
``get_statsmodel_summary``/``get_aic``/``get_bic``. Serializing it bloats
|
|
533
|
+
saved models (tens to hundreds of MB) and leaks raw data alongside the
|
|
534
|
+
model artifact. Prediction never depends on it, so it is dropped on
|
|
535
|
+
pickle; pass ``data=`` explicitly to the diagnostic methods on a loaded
|
|
536
|
+
model.
|
|
537
|
+
"""
|
|
538
|
+
state = self.__dict__.copy()
|
|
539
|
+
state["_data"] = None
|
|
540
|
+
return state
|
|
541
|
+
|
|
542
|
+
def __setstate__(self, state):
|
|
543
|
+
self.__dict__.update(state)
|
|
544
|
+
self.__dict__.setdefault("_data", None)
|
|
545
|
+
|
|
546
|
+
def _require_data(self, data):
|
|
547
|
+
"""
|
|
548
|
+
Resolve evaluation data, falling back to the stored training frame.
|
|
549
|
+
|
|
550
|
+
Raises a clear error when neither an explicit ``data`` argument nor a
|
|
551
|
+
stored ``self._data`` is available -- e.g. on a model loaded from disk,
|
|
552
|
+
where ``_data`` is intentionally dropped during pickling.
|
|
553
|
+
"""
|
|
554
|
+
if data is None:
|
|
555
|
+
data = self._data
|
|
556
|
+
if data is None:
|
|
557
|
+
raise ValueError(
|
|
558
|
+
"No data available: this model was loaded without its training "
|
|
559
|
+
"frame (_data is dropped on save). Pass data=... to "
|
|
560
|
+
"get_statsmodel_summary/get_aic/get_bic."
|
|
561
|
+
)
|
|
562
|
+
return data
|
|
563
|
+
|
|
526
564
|
def fit(self, data, varlist, tgt_name, val_data=None, val_varlist=None, val_tgt_name=None, weight_col=None):
|
|
527
565
|
"""
|
|
528
566
|
Train the logistic regression model.
|
|
@@ -796,8 +834,7 @@ class LRMaster:
|
|
|
796
834
|
When standardization is enabled the summary is computed on the
|
|
797
835
|
standardized feature space, consistent with how the model was trained.
|
|
798
836
|
"""
|
|
799
|
-
|
|
800
|
-
data = self._data
|
|
837
|
+
data = self._require_data(data)
|
|
801
838
|
if varlist is None:
|
|
802
839
|
varlist = self.varlist
|
|
803
840
|
if tgt_name is None:
|
|
@@ -824,8 +861,7 @@ class LRMaster:
|
|
|
824
861
|
-------
|
|
825
862
|
float
|
|
826
863
|
"""
|
|
827
|
-
|
|
828
|
-
data = self._data
|
|
864
|
+
data = self._require_data(data)
|
|
829
865
|
if varlist is None:
|
|
830
866
|
varlist = self.varlist
|
|
831
867
|
if tgt_name is None:
|
|
@@ -851,8 +887,7 @@ class LRMaster:
|
|
|
851
887
|
-------
|
|
852
888
|
float
|
|
853
889
|
"""
|
|
854
|
-
|
|
855
|
-
data = self._data
|
|
890
|
+
data = self._require_data(data)
|
|
856
891
|
if varlist is None:
|
|
857
892
|
varlist = self.varlist
|
|
858
893
|
if tgt_name is None:
|
{supermodelingfactory-0.3.3 → supermodelingfactory-0.3.5}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
@@ -26,10 +26,15 @@ class CreditModelPipelineConfig:
|
|
|
26
26
|
split_col: str | None = None
|
|
27
27
|
sample_col: str = "sample_ind"
|
|
28
28
|
oot_col: str | None = "oot_flag"
|
|
29
|
+
weight_col: str | None = None
|
|
29
30
|
random_state: int = 42
|
|
30
31
|
write_outputs: bool = True
|
|
31
32
|
write_excel: bool = True
|
|
32
33
|
plot_outputs: bool = True
|
|
34
|
+
save_models: bool = False
|
|
35
|
+
model_output_dir: str | None = None
|
|
36
|
+
model_include_metadata: bool = True
|
|
37
|
+
save_woe_artifacts: bool = True
|
|
33
38
|
|
|
34
39
|
split_config: dict[str, Any] = field(default_factory=lambda: {"test_size": 0.3, "stratify": True})
|
|
35
40
|
feature_selection: dict[str, Any] = field(
|
|
@@ -103,6 +108,8 @@ class CreditModelPipelineResult:
|
|
|
103
108
|
model_feature_sets: dict[str, list[str]] = field(default_factory=dict)
|
|
104
109
|
selected_raw_features: list[str] = field(default_factory=list)
|
|
105
110
|
selected_woe_features: list[str] = field(default_factory=list)
|
|
111
|
+
model_paths: dict[str, str] = field(default_factory=dict)
|
|
112
|
+
artifact_paths: dict[str, str] = field(default_factory=dict)
|
|
106
113
|
|
|
107
114
|
|
|
108
115
|
class CreditModelPipeline:
|
|
@@ -166,9 +173,10 @@ class CreditModelPipeline:
|
|
|
166
173
|
output_dir / "figs" / "woe",
|
|
167
174
|
output_dir / "figs" / "mono_woe",
|
|
168
175
|
output_dir / "figs" / "perf",
|
|
169
|
-
output_dir / "models",
|
|
170
176
|
output_dir / "explain",
|
|
171
177
|
)
|
|
178
|
+
if cfg.save_models:
|
|
179
|
+
make_dirs(self._model_output_dir(), output_dir / "artifacts")
|
|
172
180
|
|
|
173
181
|
splits = self._split_data(data)
|
|
174
182
|
fs_summary, final_features = self._feature_selection(splits, feature_cols)
|
|
@@ -202,6 +210,19 @@ class CreditModelPipeline:
|
|
|
202
210
|
optuna_results = self._run_optuna(model_inputs)
|
|
203
211
|
perf_results = self._evaluate_models(model_inputs, models)
|
|
204
212
|
explain_outputs = self._run_explainability(model_inputs, models)
|
|
213
|
+
model_paths: dict[str, str] = {}
|
|
214
|
+
artifact_paths: dict[str, str] = {}
|
|
215
|
+
model_paths_frame = None
|
|
216
|
+
if cfg.save_models:
|
|
217
|
+
model_paths, artifact_paths = self._save_models_and_artifacts(
|
|
218
|
+
models=models,
|
|
219
|
+
woe_artifacts=woe_artifacts,
|
|
220
|
+
perf_results=perf_results,
|
|
221
|
+
model_feature_sources=model_feature_sources,
|
|
222
|
+
model_feature_sets=model_feature_sets,
|
|
223
|
+
)
|
|
224
|
+
model_paths_frame = self._paths_to_frame(model_paths, artifact_paths)
|
|
225
|
+
safe_to_csv(model_paths_frame, output_dir / "model_paths.csv", index=False)
|
|
205
226
|
|
|
206
227
|
if cfg.write_outputs:
|
|
207
228
|
self._write_outputs(
|
|
@@ -214,6 +235,7 @@ class CreditModelPipeline:
|
|
|
214
235
|
lr_search_results,
|
|
215
236
|
warm_start_summary,
|
|
216
237
|
model_feature_source_summary,
|
|
238
|
+
model_paths_frame,
|
|
217
239
|
)
|
|
218
240
|
|
|
219
241
|
report_path = None
|
|
@@ -226,6 +248,7 @@ class CreditModelPipeline:
|
|
|
226
248
|
"LR_Param_Search": lr_search_results,
|
|
227
249
|
"Warm_Start": warm_start_summary,
|
|
228
250
|
"Model_Feature_Source": model_feature_source_summary,
|
|
251
|
+
"Model_Paths": model_paths_frame,
|
|
229
252
|
}
|
|
230
253
|
for name, perf in perf_results.items():
|
|
231
254
|
sheets[f"Perf_{name.upper()}"] = perf
|
|
@@ -248,6 +271,8 @@ class CreditModelPipeline:
|
|
|
248
271
|
model_feature_sets=model_feature_sets,
|
|
249
272
|
selected_raw_features=list(selected_raw_features),
|
|
250
273
|
selected_woe_features=list(selected_woe_features),
|
|
274
|
+
model_paths=model_paths,
|
|
275
|
+
artifact_paths=artifact_paths,
|
|
251
276
|
)
|
|
252
277
|
|
|
253
278
|
def _resolve_feature_cols(self, data: pd.DataFrame) -> list[str]:
|
|
@@ -259,6 +284,8 @@ class CreditModelPipeline:
|
|
|
259
284
|
excluded.add(cfg.split_col)
|
|
260
285
|
if cfg.oot_col:
|
|
261
286
|
excluded.add(cfg.oot_col)
|
|
287
|
+
if cfg.weight_col:
|
|
288
|
+
excluded.add(cfg.weight_col)
|
|
262
289
|
numeric_cols = data.select_dtypes(include=[np.number]).columns
|
|
263
290
|
return [col for col in numeric_cols if col not in excluded]
|
|
264
291
|
|
|
@@ -268,6 +295,12 @@ class CreditModelPipeline:
|
|
|
268
295
|
missing = [col for col in missing if col not in data.columns]
|
|
269
296
|
if missing:
|
|
270
297
|
raise KeyError(f"Missing required columns: {missing}")
|
|
298
|
+
if cfg.weight_col:
|
|
299
|
+
if cfg.weight_col not in data.columns:
|
|
300
|
+
raise KeyError(f"Missing weight_col {cfg.weight_col!r}")
|
|
301
|
+
from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
|
|
302
|
+
|
|
303
|
+
resolve_sample_weight(data=data, weight_col=cfg.weight_col, expected_len=len(data))
|
|
271
304
|
if cfg.warm_start_enabled:
|
|
272
305
|
if not cfg.warm_start_score_col:
|
|
273
306
|
raise ValueError("warm_start_score_col is required when warm_start_enabled=True")
|
|
@@ -278,6 +311,21 @@ class CreditModelPipeline:
|
|
|
278
311
|
if cfg.warm_start_on_unsupported not in {"skip", "raise"}:
|
|
279
312
|
raise ValueError("warm_start_on_unsupported must be 'skip' or 'raise'")
|
|
280
313
|
|
|
314
|
+
def _gbm_weight_kwargs(self, train: pd.DataFrame, val: pd.DataFrame) -> dict[str, Any]:
|
|
315
|
+
cfg = self.config
|
|
316
|
+
if not cfg.weight_col:
|
|
317
|
+
return {}
|
|
318
|
+
from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
|
|
319
|
+
|
|
320
|
+
kwargs: dict[str, Any] = {}
|
|
321
|
+
train_sw = resolve_sample_weight(data=train, weight_col=cfg.weight_col, expected_len=len(train))
|
|
322
|
+
if train_sw is not None:
|
|
323
|
+
kwargs["sample_weight"] = train_sw
|
|
324
|
+
eval_sw = resolve_sample_weight(data=val, weight_col=cfg.weight_col, expected_len=len(val))
|
|
325
|
+
if eval_sw is not None:
|
|
326
|
+
kwargs["eval_sample_weight"] = eval_sw
|
|
327
|
+
return kwargs
|
|
328
|
+
|
|
281
329
|
def _validate_gbm_feature_source_config(self) -> None:
|
|
282
330
|
source_cfg = self.config.gbm_feature_source
|
|
283
331
|
if isinstance(source_cfg, dict):
|
|
@@ -590,6 +638,7 @@ class CreditModelPipeline:
|
|
|
590
638
|
val_data=val,
|
|
591
639
|
val_varlist=feature_cols,
|
|
592
640
|
val_tgt_name=cfg.target_col,
|
|
641
|
+
weight_col=cfg.weight_col,
|
|
593
642
|
)
|
|
594
643
|
models[name] = (lr, getattr(lr, "model", lr), list(feature_cols))
|
|
595
644
|
elif name in {"lgb", "xgb", "cat"}:
|
|
@@ -605,6 +654,7 @@ class CreditModelPipeline:
|
|
|
605
654
|
valx=val[feature_cols],
|
|
606
655
|
valy=val[cfg.target_col].astype(int),
|
|
607
656
|
init_score=init_score,
|
|
657
|
+
**self._gbm_weight_kwargs(train, val),
|
|
608
658
|
)
|
|
609
659
|
raw = gbm._model.model if hasattr(gbm, "_model") else gbm
|
|
610
660
|
models[name] = (gbm, raw, list(feature_cols))
|
|
@@ -627,6 +677,8 @@ class CreditModelPipeline:
|
|
|
627
677
|
"model_type": f"{cfg.backward_model}m" if cfg.backward_model == "lgb" else cfg.backward_model,
|
|
628
678
|
"validation_data": splits["oos"],
|
|
629
679
|
"test_data_dict": {"oot": splits["oot"]},
|
|
680
|
+
"weight_col": cfg.weight_col,
|
|
681
|
+
"validation_weight_col": cfg.weight_col,
|
|
630
682
|
},
|
|
631
683
|
cfg.backward_params.get("init", {}),
|
|
632
684
|
)
|
|
@@ -687,6 +739,8 @@ class CreditModelPipeline:
|
|
|
687
739
|
tgt_name=cfg.target_col,
|
|
688
740
|
eval_sets={"oos": splits["oos"], "oot": splits["oot"]},
|
|
689
741
|
param_grid=cfg.lr_search_param_grid,
|
|
742
|
+
weight_col=cfg.weight_col,
|
|
743
|
+
eval_weight_col=cfg.weight_col,
|
|
690
744
|
**params,
|
|
691
745
|
)
|
|
692
746
|
self._lr_best_params = dict(getattr(lr, "best_params_", {}) or {})
|
|
@@ -807,6 +861,8 @@ class CreditModelPipeline:
|
|
|
807
861
|
data=splits["ins"],
|
|
808
862
|
search_space=search_spaces[name],
|
|
809
863
|
fit_kwargs=fit_kwargs or None,
|
|
864
|
+
weight_col=cfg.weight_col,
|
|
865
|
+
eval_weight_col=cfg.weight_col,
|
|
810
866
|
**common,
|
|
811
867
|
)
|
|
812
868
|
except Exception as exc:
|
|
@@ -834,7 +890,7 @@ class CreditModelPipeline:
|
|
|
834
890
|
for ds_name, df in splits.items():
|
|
835
891
|
scored = df.copy()
|
|
836
892
|
scored[f"pred_{name}"] = self._predict_model_positive(name, wrapper, scored, feature_cols)
|
|
837
|
-
add_dataset_with_optional_weight(evaluator, ds_name, scored)
|
|
893
|
+
add_dataset_with_optional_weight(evaluator, ds_name, scored, weight_col=cfg.weight_col)
|
|
838
894
|
fig_save_path = None
|
|
839
895
|
if cfg.write_outputs and cfg.plot_outputs:
|
|
840
896
|
fig_save_path = str(Path(cfg.output_dir) / "figs" / "perf" / f"perf_{name}.png")
|
|
@@ -956,6 +1012,92 @@ class CreditModelPipeline:
|
|
|
956
1012
|
},
|
|
957
1013
|
}
|
|
958
1014
|
|
|
1015
|
+
def _model_output_dir(self) -> Path:
|
|
1016
|
+
cfg = self.config
|
|
1017
|
+
return Path(cfg.model_output_dir) if cfg.model_output_dir else Path(cfg.output_dir) / "models"
|
|
1018
|
+
|
|
1019
|
+
def _save_models_and_artifacts(
|
|
1020
|
+
self,
|
|
1021
|
+
models: dict[str, tuple[Any, Any, list[str]]],
|
|
1022
|
+
woe_artifacts: dict[str, Any],
|
|
1023
|
+
perf_results: dict[str, pd.DataFrame],
|
|
1024
|
+
model_feature_sources: dict[str, str],
|
|
1025
|
+
model_feature_sets: dict[str, list[str]],
|
|
1026
|
+
) -> tuple[dict[str, str], dict[str, str]]:
|
|
1027
|
+
from Modeling_Tool import save_model
|
|
1028
|
+
|
|
1029
|
+
cfg = self.config
|
|
1030
|
+
model_dir = self._model_output_dir()
|
|
1031
|
+
artifact_dir = Path(cfg.output_dir) / "artifacts"
|
|
1032
|
+
make_dirs(model_dir)
|
|
1033
|
+
model_paths: dict[str, str] = {}
|
|
1034
|
+
artifact_paths: dict[str, str] = {}
|
|
1035
|
+
|
|
1036
|
+
woe_table_path = None
|
|
1037
|
+
if cfg.save_woe_artifacts:
|
|
1038
|
+
make_dirs(artifact_dir)
|
|
1039
|
+
woe_table = woe_artifacts.get("woe_table")
|
|
1040
|
+
if isinstance(woe_table, pd.DataFrame):
|
|
1041
|
+
woe_table_path = artifact_dir / "woe_table.csv"
|
|
1042
|
+
safe_to_csv(woe_table, woe_table_path, index=False)
|
|
1043
|
+
artifact_paths["woe_table"] = str(woe_table_path)
|
|
1044
|
+
engine = woe_artifacts.get("engine")
|
|
1045
|
+
if engine is not None:
|
|
1046
|
+
engine_path = artifact_dir / "woe_engine.pkl"
|
|
1047
|
+
save_model(
|
|
1048
|
+
engine,
|
|
1049
|
+
engine_path,
|
|
1050
|
+
metadata={
|
|
1051
|
+
"pipeline": "CreditModelPipeline",
|
|
1052
|
+
"artifact_role": "woe_engine",
|
|
1053
|
+
"target_col": cfg.target_col,
|
|
1054
|
+
"raw_features": list(woe_artifacts.get("features") or []),
|
|
1055
|
+
"woe_features": list(woe_artifacts.get("woe_features") or []),
|
|
1056
|
+
"woe_engine": woe_artifacts.get("engine_name"),
|
|
1057
|
+
"random_state": cfg.random_state,
|
|
1058
|
+
},
|
|
1059
|
+
feature_cols=woe_artifacts.get("features"),
|
|
1060
|
+
include_metadata=cfg.model_include_metadata,
|
|
1061
|
+
)
|
|
1062
|
+
artifact_paths["woe_engine"] = str(engine_path)
|
|
1063
|
+
|
|
1064
|
+
warm_start_requested = {str(x).lower() for x in as_list(cfg.warm_start_models)}
|
|
1065
|
+
for name, (wrapper, _, feature_cols) in models.items():
|
|
1066
|
+
path = model_dir / f"model_{name}.pkl"
|
|
1067
|
+
metadata = {
|
|
1068
|
+
"pipeline": "CreditModelPipeline",
|
|
1069
|
+
"model_name": name,
|
|
1070
|
+
"target_col": cfg.target_col,
|
|
1071
|
+
"feature_cols": list(feature_cols),
|
|
1072
|
+
"feature_source": model_feature_sources.get(name),
|
|
1073
|
+
"model_feature_set": model_feature_sets.get(name, list(feature_cols)),
|
|
1074
|
+
"model_params": dict(cfg.model_params.get(name, {})),
|
|
1075
|
+
"warm_start_enabled": bool(cfg.warm_start_enabled and name in warm_start_requested),
|
|
1076
|
+
"warm_start_score_col": cfg.warm_start_score_col,
|
|
1077
|
+
"random_state": cfg.random_state,
|
|
1078
|
+
}
|
|
1079
|
+
metrics = None
|
|
1080
|
+
if isinstance(perf_results.get(name), pd.DataFrame):
|
|
1081
|
+
metrics = {"perf_results": perf_results[name].to_dict("records")}
|
|
1082
|
+
save_model(
|
|
1083
|
+
wrapper,
|
|
1084
|
+
path,
|
|
1085
|
+
metadata=metadata,
|
|
1086
|
+
feature_cols=feature_cols,
|
|
1087
|
+
woe_mapping_path=str(woe_table_path) if woe_table_path else None,
|
|
1088
|
+
metrics=metrics,
|
|
1089
|
+
model_name=name,
|
|
1090
|
+
include_metadata=cfg.model_include_metadata,
|
|
1091
|
+
)
|
|
1092
|
+
model_paths[name] = str(path)
|
|
1093
|
+
return model_paths, artifact_paths
|
|
1094
|
+
|
|
1095
|
+
@staticmethod
|
|
1096
|
+
def _paths_to_frame(model_paths: dict[str, str], artifact_paths: dict[str, str]) -> pd.DataFrame:
|
|
1097
|
+
rows = [{"name": name, "path": path, "kind": "model"} for name, path in model_paths.items()]
|
|
1098
|
+
rows.extend({"name": name, "path": path, "kind": "artifact"} for name, path in artifact_paths.items())
|
|
1099
|
+
return pd.DataFrame(rows)
|
|
1100
|
+
|
|
959
1101
|
def _write_outputs(
|
|
960
1102
|
self,
|
|
961
1103
|
output_dir: Path,
|
|
@@ -967,6 +1109,7 @@ class CreditModelPipeline:
|
|
|
967
1109
|
lr_search_results: pd.DataFrame | None,
|
|
968
1110
|
warm_start_summary: pd.DataFrame | None,
|
|
969
1111
|
model_feature_source_summary: pd.DataFrame | None,
|
|
1112
|
+
model_paths_frame: pd.DataFrame | None,
|
|
970
1113
|
) -> None:
|
|
971
1114
|
if isinstance(fs_summary.get("psi"), pd.DataFrame):
|
|
972
1115
|
safe_to_csv(fs_summary["psi"], output_dir / "psi_result.csv", index=False)
|
|
@@ -977,6 +1120,7 @@ class CreditModelPipeline:
|
|
|
977
1120
|
safe_to_csv(lr_search_results, output_dir / "lr_param_search.csv", index=False)
|
|
978
1121
|
safe_to_csv(warm_start_summary, output_dir / "warm_start_summary.csv", index=False)
|
|
979
1122
|
safe_to_csv(model_feature_source_summary, output_dir / "model_feature_sources.csv", index=False)
|
|
1123
|
+
safe_to_csv(model_paths_frame, output_dir / "model_paths.csv", index=False)
|
|
980
1124
|
for name, df in optuna_results.items():
|
|
981
1125
|
safe_to_csv(df, output_dir / f"{name}_optuna_search.csv", index=False)
|
|
982
1126
|
for name, df in perf_results.items():
|
|
@@ -648,6 +648,7 @@ class FeatureValidationPipeline:
|
|
|
648
648
|
continue
|
|
649
649
|
params = dict(cfg.corr_params or {})
|
|
650
650
|
max_iterations = int(params.pop("max_iterations", 10))
|
|
651
|
+
corr_method = params.get("method", "pearson")
|
|
651
652
|
filt = CorrelationFilter(data=observed, dep=target, **params)
|
|
652
653
|
keep = filt.remove_highly_correlated(features, max_iterations=max_iterations)
|
|
653
654
|
removed = [feature for feature in features if feature not in keep]
|
|
@@ -659,6 +660,7 @@ class FeatureValidationPipeline:
|
|
|
659
660
|
for _, corr_row in corr.iterrows():
|
|
660
661
|
key = tuple(sorted([str(corr_row.get("VAR1")), str(corr_row.get("VAR2"))]))
|
|
661
662
|
meta = pair_meta.get(key, {})
|
|
663
|
+
row_corr_method = meta.get("corr_method", corr_method)
|
|
662
664
|
for var in [corr_row.get("VAR1"), corr_row.get("VAR2")]:
|
|
663
665
|
metrics = metric_map.get(var, {})
|
|
664
666
|
rows.append(
|
|
@@ -673,9 +675,14 @@ class FeatureValidationPipeline:
|
|
|
673
675
|
"iv": metrics.get("iv"),
|
|
674
676
|
"ks_in_gains": metrics.get("ks_in_gains"),
|
|
675
677
|
"lift_in_gains": metrics.get("lift_in_gains"),
|
|
678
|
+
**self._corr_detail_fields(
|
|
679
|
+
corr_row.get("VAR1"),
|
|
680
|
+
corr_row.get("VAR2"),
|
|
681
|
+
var,
|
|
682
|
+
row_corr_method,
|
|
683
|
+
"cross_batch",
|
|
684
|
+
),
|
|
676
685
|
"pair_type": "new_new_cross_batch",
|
|
677
|
-
"detail_scope": "cross_batch",
|
|
678
|
-
"corr_method": meta.get("corr_method", params.get("method", "pearson")),
|
|
679
686
|
"batch_left": meta.get("batch_left"),
|
|
680
687
|
"batch_right": meta.get("batch_right"),
|
|
681
688
|
}
|
|
@@ -688,8 +695,14 @@ class FeatureValidationPipeline:
|
|
|
688
695
|
"corr_var1": features[0],
|
|
689
696
|
"corr_var2": features[1],
|
|
690
697
|
"recommended_action": "remove",
|
|
698
|
+
**self._corr_detail_fields(
|
|
699
|
+
features[0],
|
|
700
|
+
features[1],
|
|
701
|
+
",".join(removed),
|
|
702
|
+
pair.get("corr_method", params.get("method", "pearson")),
|
|
703
|
+
"cross_batch",
|
|
704
|
+
),
|
|
691
705
|
"pair_type": "new_new_cross_batch",
|
|
692
|
-
"detail_scope": "cross_batch",
|
|
693
706
|
"batch_left": pair.get("batch_left"),
|
|
694
707
|
"batch_right": pair.get("batch_right"),
|
|
695
708
|
}
|
|
@@ -1121,6 +1134,12 @@ class FeatureValidationPipeline:
|
|
|
1121
1134
|
result = pd.DataFrame(result)
|
|
1122
1135
|
if group_col is None and "psi" not in result.columns:
|
|
1123
1136
|
result = pd.DataFrame({"psi": result.iloc[:, 0]})
|
|
1137
|
+
if group_col is None:
|
|
1138
|
+
result["group_value"] = "global"
|
|
1139
|
+
elif group_col in result.columns:
|
|
1140
|
+
result["group_value"] = result[group_col]
|
|
1141
|
+
else:
|
|
1142
|
+
result["group_value"] = np.nan
|
|
1124
1143
|
result["target"] = target_key
|
|
1125
1144
|
result["group_col"] = group_col or "global"
|
|
1126
1145
|
rows.append(result)
|
|
@@ -1350,6 +1369,7 @@ class FeatureValidationPipeline:
|
|
|
1350
1369
|
continue
|
|
1351
1370
|
params = dict(cfg.corr_params or {})
|
|
1352
1371
|
max_iterations = int(params.pop("max_iterations", 10))
|
|
1372
|
+
corr_method = params.get("method", "pearson")
|
|
1353
1373
|
binner = woe_artifacts.get("by_target", {}).get(target, {}).get("engine") if cfg.corr_use_woe_bins else None
|
|
1354
1374
|
filt = CorrelationFilter(data=data, dep=target, woe_binner=binner, **params)
|
|
1355
1375
|
keep = filt.remove_highly_correlated(features, max_iterations=max_iterations)
|
|
@@ -1374,12 +1394,53 @@ class FeatureValidationPipeline:
|
|
|
1374
1394
|
"iv": metrics.get("iv"),
|
|
1375
1395
|
"ks_in_gains": metrics.get("ks_in_gains"),
|
|
1376
1396
|
"lift_in_gains": metrics.get("lift_in_gains"),
|
|
1397
|
+
**self._corr_detail_fields(
|
|
1398
|
+
row.get("VAR1"),
|
|
1399
|
+
row.get("VAR2"),
|
|
1400
|
+
var,
|
|
1401
|
+
corr_method,
|
|
1402
|
+
"within_batch",
|
|
1403
|
+
),
|
|
1377
1404
|
}
|
|
1378
1405
|
)
|
|
1379
1406
|
if not rows and removed:
|
|
1380
|
-
rows.append(
|
|
1407
|
+
rows.append(
|
|
1408
|
+
{
|
|
1409
|
+
"target": target,
|
|
1410
|
+
"recommended_action": "remove",
|
|
1411
|
+
"var": ",".join(removed),
|
|
1412
|
+
"metric_var": ",".join(removed),
|
|
1413
|
+
"corr_method": corr_method,
|
|
1414
|
+
"detail_scope": "within_batch",
|
|
1415
|
+
}
|
|
1416
|
+
)
|
|
1381
1417
|
return pd.DataFrame(rows)
|
|
1382
1418
|
|
|
1419
|
+
@staticmethod
|
|
1420
|
+
def _corr_detail_fields(
|
|
1421
|
+
corr_var1: Any,
|
|
1422
|
+
corr_var2: Any,
|
|
1423
|
+
metric_var: Any,
|
|
1424
|
+
corr_method: str,
|
|
1425
|
+
detail_scope: str,
|
|
1426
|
+
) -> dict[str, Any]:
|
|
1427
|
+
corr_var1_str = str(corr_var1) if pd.notna(corr_var1) else ""
|
|
1428
|
+
corr_var2_str = str(corr_var2) if pd.notna(corr_var2) else ""
|
|
1429
|
+
metric_var_str = str(metric_var) if pd.notna(metric_var) else ""
|
|
1430
|
+
if metric_var_str == corr_var1_str:
|
|
1431
|
+
metric_var_position = "corr_var1"
|
|
1432
|
+
elif metric_var_str == corr_var2_str:
|
|
1433
|
+
metric_var_position = "corr_var2"
|
|
1434
|
+
else:
|
|
1435
|
+
metric_var_position = np.nan
|
|
1436
|
+
return {
|
|
1437
|
+
"corr_pair_id": f"{corr_var1_str}||{corr_var2_str}" if corr_var1_str or corr_var2_str else np.nan,
|
|
1438
|
+
"metric_var": metric_var,
|
|
1439
|
+
"metric_var_position": metric_var_position,
|
|
1440
|
+
"corr_method": corr_method,
|
|
1441
|
+
"detail_scope": detail_scope,
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1383
1444
|
def _build_validation_summary(
|
|
1384
1445
|
self,
|
|
1385
1446
|
data: pd.DataFrame,
|