SuperModelingFactory 0.3.2__tar.gz → 0.3.4__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.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/Evaluation_Tool.py +11 -2
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +23 -5
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/GBM_Tool.py +5 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/LRM_Tool.py +41 -6
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/credit_model.py +113 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/feature_validation.py +65 -4
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/reject_inference.py +163 -34
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/sample_analysis.py +13 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/score_comparison.py +46 -2
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Reject_Infer.py +106 -50
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Reject_Infer.pyi +5 -5
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Adapter.py +35 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +82 -33
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/PKG-INFO +2 -2
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/README.md +1 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/pyproject.toml +1 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/setup.py +1 -1
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/LICENSE +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/MANIFEST.in +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/Report/__init__.py +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/requirements.txt +0 -0
- {supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/setup.cfg +0 -0
{supermodelingfactory-0.3.2 → supermodelingfactory-0.3.4}/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.2 → supermodelingfactory-0.3.4}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
@@ -30,6 +30,10 @@ class CreditModelPipelineConfig:
|
|
|
30
30
|
write_outputs: bool = True
|
|
31
31
|
write_excel: bool = True
|
|
32
32
|
plot_outputs: bool = True
|
|
33
|
+
save_models: bool = False
|
|
34
|
+
model_output_dir: str | None = None
|
|
35
|
+
model_include_metadata: bool = True
|
|
36
|
+
save_woe_artifacts: bool = True
|
|
33
37
|
|
|
34
38
|
split_config: dict[str, Any] = field(default_factory=lambda: {"test_size": 0.3, "stratify": True})
|
|
35
39
|
feature_selection: dict[str, Any] = field(
|
|
@@ -103,6 +107,8 @@ class CreditModelPipelineResult:
|
|
|
103
107
|
model_feature_sets: dict[str, list[str]] = field(default_factory=dict)
|
|
104
108
|
selected_raw_features: list[str] = field(default_factory=list)
|
|
105
109
|
selected_woe_features: list[str] = field(default_factory=list)
|
|
110
|
+
model_paths: dict[str, str] = field(default_factory=dict)
|
|
111
|
+
artifact_paths: dict[str, str] = field(default_factory=dict)
|
|
106
112
|
|
|
107
113
|
|
|
108
114
|
class CreditModelPipeline:
|
|
@@ -166,9 +172,10 @@ class CreditModelPipeline:
|
|
|
166
172
|
output_dir / "figs" / "woe",
|
|
167
173
|
output_dir / "figs" / "mono_woe",
|
|
168
174
|
output_dir / "figs" / "perf",
|
|
169
|
-
output_dir / "models",
|
|
170
175
|
output_dir / "explain",
|
|
171
176
|
)
|
|
177
|
+
if cfg.save_models:
|
|
178
|
+
make_dirs(self._model_output_dir(), output_dir / "artifacts")
|
|
172
179
|
|
|
173
180
|
splits = self._split_data(data)
|
|
174
181
|
fs_summary, final_features = self._feature_selection(splits, feature_cols)
|
|
@@ -202,6 +209,19 @@ class CreditModelPipeline:
|
|
|
202
209
|
optuna_results = self._run_optuna(model_inputs)
|
|
203
210
|
perf_results = self._evaluate_models(model_inputs, models)
|
|
204
211
|
explain_outputs = self._run_explainability(model_inputs, models)
|
|
212
|
+
model_paths: dict[str, str] = {}
|
|
213
|
+
artifact_paths: dict[str, str] = {}
|
|
214
|
+
model_paths_frame = None
|
|
215
|
+
if cfg.save_models:
|
|
216
|
+
model_paths, artifact_paths = self._save_models_and_artifacts(
|
|
217
|
+
models=models,
|
|
218
|
+
woe_artifacts=woe_artifacts,
|
|
219
|
+
perf_results=perf_results,
|
|
220
|
+
model_feature_sources=model_feature_sources,
|
|
221
|
+
model_feature_sets=model_feature_sets,
|
|
222
|
+
)
|
|
223
|
+
model_paths_frame = self._paths_to_frame(model_paths, artifact_paths)
|
|
224
|
+
safe_to_csv(model_paths_frame, output_dir / "model_paths.csv", index=False)
|
|
205
225
|
|
|
206
226
|
if cfg.write_outputs:
|
|
207
227
|
self._write_outputs(
|
|
@@ -214,6 +234,7 @@ class CreditModelPipeline:
|
|
|
214
234
|
lr_search_results,
|
|
215
235
|
warm_start_summary,
|
|
216
236
|
model_feature_source_summary,
|
|
237
|
+
model_paths_frame,
|
|
217
238
|
)
|
|
218
239
|
|
|
219
240
|
report_path = None
|
|
@@ -226,6 +247,7 @@ class CreditModelPipeline:
|
|
|
226
247
|
"LR_Param_Search": lr_search_results,
|
|
227
248
|
"Warm_Start": warm_start_summary,
|
|
228
249
|
"Model_Feature_Source": model_feature_source_summary,
|
|
250
|
+
"Model_Paths": model_paths_frame,
|
|
229
251
|
}
|
|
230
252
|
for name, perf in perf_results.items():
|
|
231
253
|
sheets[f"Perf_{name.upper()}"] = perf
|
|
@@ -248,6 +270,8 @@ class CreditModelPipeline:
|
|
|
248
270
|
model_feature_sets=model_feature_sets,
|
|
249
271
|
selected_raw_features=list(selected_raw_features),
|
|
250
272
|
selected_woe_features=list(selected_woe_features),
|
|
273
|
+
model_paths=model_paths,
|
|
274
|
+
artifact_paths=artifact_paths,
|
|
251
275
|
)
|
|
252
276
|
|
|
253
277
|
def _resolve_feature_cols(self, data: pd.DataFrame) -> list[str]:
|
|
@@ -956,6 +980,92 @@ class CreditModelPipeline:
|
|
|
956
980
|
},
|
|
957
981
|
}
|
|
958
982
|
|
|
983
|
+
def _model_output_dir(self) -> Path:
|
|
984
|
+
cfg = self.config
|
|
985
|
+
return Path(cfg.model_output_dir) if cfg.model_output_dir else Path(cfg.output_dir) / "models"
|
|
986
|
+
|
|
987
|
+
def _save_models_and_artifacts(
|
|
988
|
+
self,
|
|
989
|
+
models: dict[str, tuple[Any, Any, list[str]]],
|
|
990
|
+
woe_artifacts: dict[str, Any],
|
|
991
|
+
perf_results: dict[str, pd.DataFrame],
|
|
992
|
+
model_feature_sources: dict[str, str],
|
|
993
|
+
model_feature_sets: dict[str, list[str]],
|
|
994
|
+
) -> tuple[dict[str, str], dict[str, str]]:
|
|
995
|
+
from Modeling_Tool import save_model
|
|
996
|
+
|
|
997
|
+
cfg = self.config
|
|
998
|
+
model_dir = self._model_output_dir()
|
|
999
|
+
artifact_dir = Path(cfg.output_dir) / "artifacts"
|
|
1000
|
+
make_dirs(model_dir)
|
|
1001
|
+
model_paths: dict[str, str] = {}
|
|
1002
|
+
artifact_paths: dict[str, str] = {}
|
|
1003
|
+
|
|
1004
|
+
woe_table_path = None
|
|
1005
|
+
if cfg.save_woe_artifacts:
|
|
1006
|
+
make_dirs(artifact_dir)
|
|
1007
|
+
woe_table = woe_artifacts.get("woe_table")
|
|
1008
|
+
if isinstance(woe_table, pd.DataFrame):
|
|
1009
|
+
woe_table_path = artifact_dir / "woe_table.csv"
|
|
1010
|
+
safe_to_csv(woe_table, woe_table_path, index=False)
|
|
1011
|
+
artifact_paths["woe_table"] = str(woe_table_path)
|
|
1012
|
+
engine = woe_artifacts.get("engine")
|
|
1013
|
+
if engine is not None:
|
|
1014
|
+
engine_path = artifact_dir / "woe_engine.pkl"
|
|
1015
|
+
save_model(
|
|
1016
|
+
engine,
|
|
1017
|
+
engine_path,
|
|
1018
|
+
metadata={
|
|
1019
|
+
"pipeline": "CreditModelPipeline",
|
|
1020
|
+
"artifact_role": "woe_engine",
|
|
1021
|
+
"target_col": cfg.target_col,
|
|
1022
|
+
"raw_features": list(woe_artifacts.get("features") or []),
|
|
1023
|
+
"woe_features": list(woe_artifacts.get("woe_features") or []),
|
|
1024
|
+
"woe_engine": woe_artifacts.get("engine_name"),
|
|
1025
|
+
"random_state": cfg.random_state,
|
|
1026
|
+
},
|
|
1027
|
+
feature_cols=woe_artifacts.get("features"),
|
|
1028
|
+
include_metadata=cfg.model_include_metadata,
|
|
1029
|
+
)
|
|
1030
|
+
artifact_paths["woe_engine"] = str(engine_path)
|
|
1031
|
+
|
|
1032
|
+
warm_start_requested = {str(x).lower() for x in as_list(cfg.warm_start_models)}
|
|
1033
|
+
for name, (wrapper, _, feature_cols) in models.items():
|
|
1034
|
+
path = model_dir / f"model_{name}.pkl"
|
|
1035
|
+
metadata = {
|
|
1036
|
+
"pipeline": "CreditModelPipeline",
|
|
1037
|
+
"model_name": name,
|
|
1038
|
+
"target_col": cfg.target_col,
|
|
1039
|
+
"feature_cols": list(feature_cols),
|
|
1040
|
+
"feature_source": model_feature_sources.get(name),
|
|
1041
|
+
"model_feature_set": model_feature_sets.get(name, list(feature_cols)),
|
|
1042
|
+
"model_params": dict(cfg.model_params.get(name, {})),
|
|
1043
|
+
"warm_start_enabled": bool(cfg.warm_start_enabled and name in warm_start_requested),
|
|
1044
|
+
"warm_start_score_col": cfg.warm_start_score_col,
|
|
1045
|
+
"random_state": cfg.random_state,
|
|
1046
|
+
}
|
|
1047
|
+
metrics = None
|
|
1048
|
+
if isinstance(perf_results.get(name), pd.DataFrame):
|
|
1049
|
+
metrics = {"perf_results": perf_results[name].to_dict("records")}
|
|
1050
|
+
save_model(
|
|
1051
|
+
wrapper,
|
|
1052
|
+
path,
|
|
1053
|
+
metadata=metadata,
|
|
1054
|
+
feature_cols=feature_cols,
|
|
1055
|
+
woe_mapping_path=str(woe_table_path) if woe_table_path else None,
|
|
1056
|
+
metrics=metrics,
|
|
1057
|
+
model_name=name,
|
|
1058
|
+
include_metadata=cfg.model_include_metadata,
|
|
1059
|
+
)
|
|
1060
|
+
model_paths[name] = str(path)
|
|
1061
|
+
return model_paths, artifact_paths
|
|
1062
|
+
|
|
1063
|
+
@staticmethod
|
|
1064
|
+
def _paths_to_frame(model_paths: dict[str, str], artifact_paths: dict[str, str]) -> pd.DataFrame:
|
|
1065
|
+
rows = [{"name": name, "path": path, "kind": "model"} for name, path in model_paths.items()]
|
|
1066
|
+
rows.extend({"name": name, "path": path, "kind": "artifact"} for name, path in artifact_paths.items())
|
|
1067
|
+
return pd.DataFrame(rows)
|
|
1068
|
+
|
|
959
1069
|
def _write_outputs(
|
|
960
1070
|
self,
|
|
961
1071
|
output_dir: Path,
|
|
@@ -967,6 +1077,7 @@ class CreditModelPipeline:
|
|
|
967
1077
|
lr_search_results: pd.DataFrame | None,
|
|
968
1078
|
warm_start_summary: pd.DataFrame | None,
|
|
969
1079
|
model_feature_source_summary: pd.DataFrame | None,
|
|
1080
|
+
model_paths_frame: pd.DataFrame | None,
|
|
970
1081
|
) -> None:
|
|
971
1082
|
if isinstance(fs_summary.get("psi"), pd.DataFrame):
|
|
972
1083
|
safe_to_csv(fs_summary["psi"], output_dir / "psi_result.csv", index=False)
|
|
@@ -977,6 +1088,7 @@ class CreditModelPipeline:
|
|
|
977
1088
|
safe_to_csv(lr_search_results, output_dir / "lr_param_search.csv", index=False)
|
|
978
1089
|
safe_to_csv(warm_start_summary, output_dir / "warm_start_summary.csv", index=False)
|
|
979
1090
|
safe_to_csv(model_feature_source_summary, output_dir / "model_feature_sources.csv", index=False)
|
|
1091
|
+
safe_to_csv(model_paths_frame, output_dir / "model_paths.csv", index=False)
|
|
980
1092
|
for name, df in optuna_results.items():
|
|
981
1093
|
safe_to_csv(df, output_dir / f"{name}_optuna_search.csv", index=False)
|
|
982
1094
|
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,
|