SuperModelingFactory 0.3.15__tar.gz → 0.4.2__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.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/ODPS_Tool.py +102 -9
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/PSI_Tool.py +107 -34
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Weighted_Screen.py +16 -2
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/_common.py +109 -3
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/credit_model.py +36 -8
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/feature_validation.py +64 -21
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/reject_inference.py +58 -4
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/sample_analysis.py +50 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/score_comparison.py +20 -3
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/score_consistency_uat.py +54 -6
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Reject_Infer.py +63 -7
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Sample_Split.py +33 -22
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/PKG-INFO +2 -2
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/README.md +1 -1
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/pyproject.toml +1 -1
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/setup.py +1 -1
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/LICENSE +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/MANIFEST.in +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Report/__init__.py +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/requirements.txt +0 -0
- {supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/setup.cfg +0 -0
|
@@ -247,7 +247,7 @@ class ODPSRunner(object):
|
|
|
247
247
|
|
|
248
248
|
return table_schema
|
|
249
249
|
|
|
250
|
-
def upload_df(self, df, table_name, table_schema=None, partition=None):
|
|
250
|
+
def upload_df(self, df, table_name, table_schema=None, partition=None, atomic=True):
|
|
251
251
|
"""上传数据集至mc中创建新表
|
|
252
252
|
|
|
253
253
|
Parameters
|
|
@@ -260,20 +260,113 @@ class ODPSRunner(object):
|
|
|
260
260
|
数据集
|
|
261
261
|
partition: string
|
|
262
262
|
保存分区
|
|
263
|
+
atomic: bool, default True
|
|
264
|
+
When ``True`` (recommended, default from 0.4.2), the target table is
|
|
265
|
+
replaced through a temp-table + rename swap so that a failure between
|
|
266
|
+
``delete_table`` and the completed write never leaves the caller
|
|
267
|
+
with a dropped-and-empty table. When ``False``, the pre-0.4.2
|
|
268
|
+
behaviour is used: the existing table is dropped first and then
|
|
269
|
+
recreated, so any exception during write leaves the target table
|
|
270
|
+
missing. Only set ``atomic=False`` if you deliberately want the
|
|
271
|
+
legacy behaviour, e.g. for tables whose downstream readers expect
|
|
272
|
+
a specific object identity.
|
|
263
273
|
"""
|
|
264
274
|
if table_schema is None:
|
|
265
275
|
df.loc[:, "py_inserttime"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
266
276
|
table_schema = self.cre_table_schema(df=df, partition_name=None)
|
|
267
277
|
|
|
268
|
-
|
|
269
|
-
|
|
278
|
+
if not atomic:
|
|
279
|
+
# Legacy pre-0.4.2 non-atomic path — retained under an explicit
|
|
280
|
+
# opt-out for callers that need the old identity semantics.
|
|
281
|
+
self.o.delete_table(table_name, if_exists=True)
|
|
282
|
+
t = self.o.create_table(table_name, table_schema)
|
|
283
|
+
if bool(partition):
|
|
284
|
+
with t.open_writer(partition=partition, create_partition=True) as writer:
|
|
285
|
+
writer.write(df.values.tolist())
|
|
286
|
+
else:
|
|
287
|
+
with t.open_writer() as writer:
|
|
288
|
+
writer.write(df.values.tolist())
|
|
289
|
+
logger.info(f'<<<< 完成数据入表{table_name}: shape={df.shape} >>>>')
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Atomic swap path (default from 0.4.2). If any step before the final
|
|
293
|
+
# swap fails, the original table is untouched.
|
|
294
|
+
ts = datetime.now().strftime('%Y%m%d%H%M%S%f')
|
|
295
|
+
tmp_name = f"{table_name}__tmp_{ts}"
|
|
296
|
+
old_name = f"{table_name}__old_{ts}"
|
|
297
|
+
|
|
298
|
+
# Step 1: write data into the temp table. If this fails, drop the
|
|
299
|
+
# partial temp table and re-raise; the target table is untouched.
|
|
300
|
+
self.o.delete_table(tmp_name, if_exists=True)
|
|
301
|
+
tmp_table = self.o.create_table(tmp_name, table_schema)
|
|
302
|
+
try:
|
|
303
|
+
if bool(partition):
|
|
304
|
+
with tmp_table.open_writer(partition=partition, create_partition=True) as writer:
|
|
305
|
+
writer.write(df.values.tolist())
|
|
306
|
+
else:
|
|
307
|
+
with tmp_table.open_writer() as writer:
|
|
308
|
+
writer.write(df.values.tolist())
|
|
309
|
+
except Exception:
|
|
310
|
+
try:
|
|
311
|
+
self.o.delete_table(tmp_name, if_exists=True)
|
|
312
|
+
except Exception as cleanup_exc: # noqa: BLE001
|
|
313
|
+
logger.warning(
|
|
314
|
+
f"upload_df: failed to drop temp table {tmp_name} after write error: {cleanup_exc!r}"
|
|
315
|
+
)
|
|
316
|
+
raise
|
|
317
|
+
|
|
318
|
+
# Step 2: swap. If the original table exists, rename it aside first so
|
|
319
|
+
# that the rename of tmp -> target is guaranteed to succeed. Then drop
|
|
320
|
+
# the old copy (best-effort, warn on failure).
|
|
321
|
+
target_exists = self.o.exist_table(table_name)
|
|
322
|
+
if target_exists:
|
|
323
|
+
try:
|
|
324
|
+
self.o.run_sql(f"ALTER TABLE {table_name} RENAME TO {old_name};")
|
|
325
|
+
except Exception:
|
|
326
|
+
# Rename of the live target failed — do not touch it. Drop the
|
|
327
|
+
# tmp table so we don't leak it, then re-raise.
|
|
328
|
+
try:
|
|
329
|
+
self.o.delete_table(tmp_name, if_exists=True)
|
|
330
|
+
except Exception as cleanup_exc: # noqa: BLE001
|
|
331
|
+
logger.warning(
|
|
332
|
+
f"upload_df: failed to drop temp table {tmp_name} after rename-target failure: {cleanup_exc!r}"
|
|
333
|
+
)
|
|
334
|
+
raise
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
self.o.run_sql(f"ALTER TABLE {tmp_name} RENAME TO {table_name};")
|
|
338
|
+
except Exception:
|
|
339
|
+
# tmp -> target rename failed. If we already moved the original,
|
|
340
|
+
# try to restore it so the caller is not left with a missing table.
|
|
341
|
+
if target_exists:
|
|
342
|
+
try:
|
|
343
|
+
self.o.run_sql(f"ALTER TABLE {old_name} RENAME TO {table_name};")
|
|
344
|
+
logger.warning(
|
|
345
|
+
f"upload_df: swap of {tmp_name} -> {table_name} failed; restored original from {old_name}"
|
|
346
|
+
)
|
|
347
|
+
except Exception as restore_exc: # noqa: BLE001
|
|
348
|
+
logger.error(
|
|
349
|
+
f"upload_df: swap of {tmp_name} -> {table_name} failed AND restore of "
|
|
350
|
+
f"{old_name} -> {table_name} failed: {restore_exc!r}. "
|
|
351
|
+
f"Manual recovery required — original data is in {old_name}."
|
|
352
|
+
)
|
|
353
|
+
try:
|
|
354
|
+
self.o.delete_table(tmp_name, if_exists=True)
|
|
355
|
+
except Exception: # noqa: BLE001
|
|
356
|
+
pass
|
|
357
|
+
raise
|
|
358
|
+
|
|
359
|
+
# Step 3: drop the moved-aside original — best-effort, do not fail the
|
|
360
|
+
# call if this cleanup errors, since the swap already succeeded.
|
|
361
|
+
if target_exists:
|
|
362
|
+
try:
|
|
363
|
+
self.o.delete_table(old_name, if_exists=True)
|
|
364
|
+
except Exception as cleanup_exc: # noqa: BLE001
|
|
365
|
+
logger.warning(
|
|
366
|
+
f"upload_df: swap succeeded but failed to drop old copy {old_name}: {cleanup_exc!r}. "
|
|
367
|
+
f"Safe to delete manually."
|
|
368
|
+
)
|
|
270
369
|
|
|
271
|
-
if bool(partition):
|
|
272
|
-
with t.open_writer(partition=partition, create_partition=True) as writer:
|
|
273
|
-
writer.write(df.values.tolist())
|
|
274
|
-
else:
|
|
275
|
-
with t.open_writer() as writer:
|
|
276
|
-
writer.write(df.values.tolist())
|
|
277
370
|
logger.info(f'<<<< 完成数据入表{table_name}: shape={df.shape} >>>>')
|
|
278
371
|
|
|
279
372
|
def insert_df(self, df, table_name, overwrite=True, partition=None):
|
{supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/PSI_Tool.py
RENAMED
|
@@ -7,12 +7,20 @@ to measure the distribution drift between expected and actual datasets.
|
|
|
7
7
|
Author: Matrix Agent
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
10
12
|
import numpy as np
|
|
11
13
|
import pandas as pd
|
|
12
14
|
from typing import Union, List, Dict, Optional, Tuple, Callable, Any
|
|
13
15
|
from tqdm import tqdm
|
|
14
16
|
from Modeling_Tool.Core.Binning_Tool import quick_binning
|
|
15
17
|
|
|
18
|
+
# Missing bucket label used across PSI helpers when ``missing_policy="include"``
|
|
19
|
+
# (the 0.4.2 default). Kept identical to the sentinel already used by
|
|
20
|
+
# ``Feature.Weighted_Screen`` and ``WOE_Adapter`` so downstream aggregators can
|
|
21
|
+
# recognise it without special-casing.
|
|
22
|
+
_MISSING_BIN = "__MISSING__"
|
|
23
|
+
|
|
16
24
|
# ============================================================================
|
|
17
25
|
# Classes
|
|
18
26
|
# ============================================================================
|
|
@@ -460,14 +468,29 @@ def _calculate_single_psi(
|
|
|
460
468
|
return_details: bool = False,
|
|
461
469
|
min_bin_prop: float = 0.05,
|
|
462
470
|
content: float = 1e-6,
|
|
463
|
-
precision: int = 5
|
|
471
|
+
precision: int = 5,
|
|
472
|
+
missing_policy: str = "drop",
|
|
464
473
|
) -> Union[float, Tuple[float, pd.DataFrame]]:
|
|
465
474
|
"""
|
|
466
475
|
Calculate Population Stability Index (PSI) for a single variable.
|
|
467
|
-
|
|
476
|
+
|
|
468
477
|
This function bins both expected and actual series using the same breakpoints,
|
|
469
478
|
then calculates PSI based on the distribution difference between them.
|
|
470
|
-
|
|
479
|
+
|
|
480
|
+
Missing handling (0.4.2, N29)
|
|
481
|
+
------------------------------
|
|
482
|
+
Before 0.4.2, both series were unconditionally passed through ``.dropna()``
|
|
483
|
+
before binning. Any drift in the *missing rate itself* — the single most
|
|
484
|
+
common early signal of a data-pipeline break — was silently invisible in
|
|
485
|
+
the returned PSI. 0.4.2 adds the ``missing_policy`` parameter so callers
|
|
486
|
+
can opt in to the corrected behaviour. The default remains ``"drop"`` for
|
|
487
|
+
strict backward compatibility with the pre-0.4.2 numeric output; the next
|
|
488
|
+
minor release will flip the default to ``"include"``. Pass
|
|
489
|
+
``missing_policy="include"`` to route NaN rows through a dedicated
|
|
490
|
+
``"__MISSING__"`` bin on both sides so missing-rate drift contributes to
|
|
491
|
+
the PSI. Pass ``missing_policy="warn_and_drop"`` to keep the old numbers
|
|
492
|
+
but at least surface a ``RuntimeWarning`` naming the two NaN counts.
|
|
493
|
+
|
|
471
494
|
Parameters
|
|
472
495
|
----------
|
|
473
496
|
expected_series : pandas.Series
|
|
@@ -486,62 +509,112 @@ def _calculate_single_psi(
|
|
|
486
509
|
Small value to avoid division by zero. Default is 1e-6.
|
|
487
510
|
precision : int, optional
|
|
488
511
|
Decimal precision. Default is 5.
|
|
489
|
-
|
|
512
|
+
missing_policy : {"drop", "include", "warn_and_drop"}, optional
|
|
513
|
+
How NaN rows are handled. ``"drop"`` (default in 0.4.2) reproduces the
|
|
514
|
+
pre-0.4.2 behaviour and silently excludes NaN rows from both sides
|
|
515
|
+
before binning. ``"include"`` treats NaN as its own ``"__MISSING__"``
|
|
516
|
+
bin so missing-rate drift shows up in the PSI — recommended for any
|
|
517
|
+
production drift monitor, and will become the default in the next
|
|
518
|
+
minor release. ``"warn_and_drop"`` behaves like ``"drop"`` but emits
|
|
519
|
+
a ``RuntimeWarning`` naming the two NaN counts so the caller sees what
|
|
520
|
+
was dropped.
|
|
521
|
+
|
|
490
522
|
Returns
|
|
491
523
|
-------
|
|
492
524
|
float or tuple
|
|
493
525
|
If return_details is False: Returns total PSI value.
|
|
494
526
|
If return_details is True: Returns tuple of (PSI value, details DataFrame).
|
|
495
|
-
|
|
527
|
+
|
|
496
528
|
Notes
|
|
497
529
|
-----
|
|
498
530
|
PSI Formula: Σ (Actual% - Expected%) * ln(Actual% / Expected%)
|
|
499
531
|
A PSI < 0.1 indicates stable population, 0.1-0.25 suggests some change,
|
|
500
532
|
and > 0.25 indicates significant drift.
|
|
501
533
|
"""
|
|
502
|
-
|
|
534
|
+
if missing_policy not in {"include", "drop", "warn_and_drop"}:
|
|
535
|
+
raise ValueError(
|
|
536
|
+
f"_calculate_single_psi: missing_policy must be one of "
|
|
537
|
+
f"'include', 'drop', 'warn_and_drop'; got {missing_policy!r}."
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Record NaN counts before splitting so we can (a) re-attach the missing
|
|
541
|
+
# bin under "include" and (b) warn under "warn_and_drop".
|
|
542
|
+
n_expected = int(len(expected_series))
|
|
543
|
+
n_actual = int(len(actual_series))
|
|
544
|
+
n_expected_na = int(expected_series.isna().sum())
|
|
545
|
+
n_actual_na = int(actual_series.isna().sum())
|
|
546
|
+
|
|
547
|
+
if missing_policy == "warn_and_drop" and (n_expected_na or n_actual_na):
|
|
548
|
+
warnings.warn(
|
|
549
|
+
f"_calculate_single_psi: dropping NaN rows before binning. "
|
|
550
|
+
f"expected: {n_expected_na}/{n_expected} NaN, "
|
|
551
|
+
f"actual: {n_actual_na}/{n_actual} NaN. "
|
|
552
|
+
f"Pass missing_policy='include' to treat NaN as its own bin so "
|
|
553
|
+
f"missing-rate drift is captured.",
|
|
554
|
+
RuntimeWarning,
|
|
555
|
+
stacklevel=2,
|
|
556
|
+
)
|
|
557
|
+
|
|
503
558
|
expected_clean = expected_series.dropna()
|
|
504
559
|
actual_clean = actual_series.dropna()
|
|
505
|
-
|
|
560
|
+
|
|
506
561
|
# Bin the expected data to get breakpoints
|
|
507
562
|
expected_bins, breakpoints = quick_binning(
|
|
508
|
-
pd.DataFrame(expected_clean),
|
|
509
|
-
expected_clean.name,
|
|
510
|
-
labels=None,
|
|
511
|
-
nbins=buckets,
|
|
512
|
-
precision=precision,
|
|
513
|
-
equal_freq=equal_freq,
|
|
514
|
-
right=True,
|
|
563
|
+
pd.DataFrame(expected_clean),
|
|
564
|
+
expected_clean.name,
|
|
565
|
+
labels=None,
|
|
566
|
+
nbins=buckets,
|
|
567
|
+
precision=precision,
|
|
568
|
+
equal_freq=equal_freq,
|
|
569
|
+
right=True,
|
|
515
570
|
include_lowest=False,
|
|
516
|
-
min_bin_prop=min_bin_prop,
|
|
517
|
-
tree_binning=False,
|
|
518
|
-
target=None,
|
|
571
|
+
min_bin_prop=min_bin_prop,
|
|
572
|
+
tree_binning=False,
|
|
573
|
+
target=None,
|
|
519
574
|
random_state=42
|
|
520
575
|
)
|
|
521
|
-
|
|
576
|
+
|
|
522
577
|
# Bin the actual data using the same breakpoints
|
|
523
578
|
actual_bins, _ = quick_binning(
|
|
524
|
-
pd.DataFrame(actual_clean),
|
|
525
|
-
actual_clean.name,
|
|
526
|
-
labels=None,
|
|
527
|
-
nbins=list(breakpoints),
|
|
528
|
-
precision=precision,
|
|
529
|
-
equal_freq=equal_freq,
|
|
530
|
-
right=True,
|
|
579
|
+
pd.DataFrame(actual_clean),
|
|
580
|
+
actual_clean.name,
|
|
581
|
+
labels=None,
|
|
582
|
+
nbins=list(breakpoints),
|
|
583
|
+
precision=precision,
|
|
584
|
+
equal_freq=equal_freq,
|
|
585
|
+
right=True,
|
|
531
586
|
include_lowest=False,
|
|
532
|
-
min_bin_prop=min_bin_prop,
|
|
533
|
-
tree_binning=False,
|
|
534
|
-
target=None,
|
|
587
|
+
min_bin_prop=min_bin_prop,
|
|
588
|
+
tree_binning=False,
|
|
589
|
+
target=None,
|
|
535
590
|
random_state=42
|
|
536
591
|
)
|
|
537
|
-
|
|
538
|
-
# Get bin counts
|
|
592
|
+
|
|
593
|
+
# Get bin counts (finite-value rows only, from quick_binning)
|
|
539
594
|
expected_count = expected_bins.value_counts(normalize=False, sort=False)
|
|
540
595
|
actual_count = actual_bins.value_counts(normalize=False, sort=False)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
596
|
+
|
|
597
|
+
if missing_policy == "include":
|
|
598
|
+
# Re-attach the __MISSING__ bin so that missing-rate drift contributes
|
|
599
|
+
# to the PSI on both sides. The denominators use the *original* row
|
|
600
|
+
# counts (before dropna) so the fractions are directly comparable.
|
|
601
|
+
if n_expected_na > 0:
|
|
602
|
+
expected_count = pd.concat(
|
|
603
|
+
[expected_count, pd.Series({_MISSING_BIN: n_expected_na})]
|
|
604
|
+
)
|
|
605
|
+
if n_actual_na > 0:
|
|
606
|
+
actual_count = pd.concat(
|
|
607
|
+
[actual_count, pd.Series({_MISSING_BIN: n_actual_na})]
|
|
608
|
+
)
|
|
609
|
+
expected_denom = float(n_expected) if n_expected > 0 else 1.0
|
|
610
|
+
actual_denom = float(n_actual) if n_actual > 0 else 1.0
|
|
611
|
+
expected_percents = expected_count / expected_denom
|
|
612
|
+
actual_percents = actual_count / actual_denom
|
|
613
|
+
else:
|
|
614
|
+
# "drop" / "warn_and_drop": legacy behaviour, denominators are the
|
|
615
|
+
# NaN-dropped row counts, so missing-rate drift is invisible.
|
|
616
|
+
expected_percents = expected_bins.value_counts(normalize=True, sort=False)
|
|
617
|
+
actual_percents = actual_bins.value_counts(normalize=True, sort=False)
|
|
545
618
|
|
|
546
619
|
# Ensure both series have the same bin indices
|
|
547
620
|
all_bins = expected_percents.index.union(actual_percents.index)
|
{supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Feature/Weighted_Screen.py
RENAMED
|
@@ -113,11 +113,25 @@ def _weighted_bin_distribution(
|
|
|
113
113
|
w: np.ndarray,
|
|
114
114
|
content: float,
|
|
115
115
|
) -> pd.Series:
|
|
116
|
+
"""Return the weighted bin distribution as an observed-only frequency
|
|
117
|
+
Series.
|
|
118
|
+
|
|
119
|
+
Fix (0.4.2, N23): the pre-0.4.2 version unioned ``_MISSING_BIN`` into the
|
|
120
|
+
returned index and clipped every entry to ``content`` (a small positive
|
|
121
|
+
floor such as ``1e-5``). When only one side of a PSI comparison had missing
|
|
122
|
+
values, that forced a large synthetic mass on the missing bin of the side
|
|
123
|
+
that actually observed zero missings — which then propagated through
|
|
124
|
+
``_psi_from_distributions`` as a large spurious PSI component. The fix is
|
|
125
|
+
to return only the bins actually observed in ``bins``; the alignment and
|
|
126
|
+
zero-safety are done exactly once, in ``_psi_from_distributions``, so both
|
|
127
|
+
sides are treated symmetrically. The ``content`` parameter is retained for
|
|
128
|
+
signature stability but is now unused in this helper.
|
|
129
|
+
"""
|
|
130
|
+
del content # retained for signature stability; alignment is elsewhere.
|
|
116
131
|
df = pd.DataFrame({"bin": bins, "w": w})
|
|
117
132
|
dist = df.groupby("bin", dropna=False)["w"].sum()
|
|
118
133
|
total = float(dist.sum()) or 1.0
|
|
119
|
-
|
|
120
|
-
return dist.reindex(dist.index.union([_MISSING_BIN]), fill_value=0.0).clip(lower=content)
|
|
134
|
+
return dist / total
|
|
121
135
|
|
|
122
136
|
|
|
123
137
|
def _psi_from_distributions(expected: pd.Series, actual: pd.Series, content: float) -> float:
|
{supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/_common.py
RENAMED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import warnings
|
|
5
6
|
from dataclasses import fields, is_dataclass
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Iterable, Mapping
|
|
@@ -307,9 +308,45 @@ def predict_positive(model: Any, data: pd.DataFrame, feature_cols: list[str]) ->
|
|
|
307
308
|
raise TypeError(f"Model {type(model)!r} does not expose predict/predict_proba")
|
|
308
309
|
|
|
309
310
|
pred_arr = np.asarray(pred)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
n_rows = len(data)
|
|
312
|
+
|
|
313
|
+
if pred_arr.ndim == 2:
|
|
314
|
+
if pred_arr.shape[1] == 2:
|
|
315
|
+
# sklearn convention: column 1 is positive class
|
|
316
|
+
result = pred_arr[:, 1]
|
|
317
|
+
elif pred_arr.shape[1] == 1:
|
|
318
|
+
result = pred_arr[:, 0]
|
|
319
|
+
else:
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"predict_proba returned unexpected shape {pred_arr.shape}; "
|
|
322
|
+
f"expected 1-D or 2-D with 1-2 columns."
|
|
323
|
+
)
|
|
324
|
+
elif pred_arr.ndim == 1:
|
|
325
|
+
result = pred_arr
|
|
326
|
+
else:
|
|
327
|
+
raise ValueError(
|
|
328
|
+
f"predict_proba returned unexpected ndim={pred_arr.ndim} "
|
|
329
|
+
f"(shape={pred_arr.shape}); expected 1-D or 2-D."
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
result = result.reshape(-1)
|
|
333
|
+
if len(result) != n_rows:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
f"predict_positive length mismatch: got {len(result)} predictions "
|
|
336
|
+
f"for {n_rows} input rows. Model {type(model).__name__} may have "
|
|
337
|
+
f"dropped rows silently."
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
finite_mask = np.isfinite(result)
|
|
341
|
+
if not finite_mask.all():
|
|
342
|
+
n_bad = int((~finite_mask).sum())
|
|
343
|
+
warnings.warn(
|
|
344
|
+
f"predict_positive: {n_bad}/{n_rows} predictions are NaN/Inf "
|
|
345
|
+
f"from model {type(model).__name__}. Downstream scoring may be affected.",
|
|
346
|
+
RuntimeWarning,
|
|
347
|
+
stacklevel=2,
|
|
348
|
+
)
|
|
349
|
+
return result
|
|
313
350
|
|
|
314
351
|
|
|
315
352
|
def add_dataset_with_optional_weight(
|
|
@@ -364,3 +401,72 @@ def write_basic_excel(
|
|
|
364
401
|
|
|
365
402
|
em.close_workbook()
|
|
366
403
|
return report_path
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def copy_column_length_checked(
|
|
407
|
+
dst: pd.DataFrame,
|
|
408
|
+
src: pd.DataFrame,
|
|
409
|
+
col: str,
|
|
410
|
+
*,
|
|
411
|
+
dst_name: str,
|
|
412
|
+
src_name: str,
|
|
413
|
+
) -> None:
|
|
414
|
+
"""Positionally copy ``src[col]`` into ``dst[col]`` with a length guard.
|
|
415
|
+
|
|
416
|
+
``warm_start_score_col`` in credit_model relies on WOE-transformed splits
|
|
417
|
+
lining up row-for-row with the raw splits they were transformed from. That
|
|
418
|
+
assumption held in practice, but the historical implementation used a bare
|
|
419
|
+
``dst[col] = src[col].to_numpy()`` which would corrupt the join whenever the
|
|
420
|
+
two frames drifted in length (fit-query filtering, prior ``.dropna``, etc.)
|
|
421
|
+
with no error. This helper enforces the invariant explicitly.
|
|
422
|
+
|
|
423
|
+
Values are copied by position (``.to_numpy()``) — not by index alignment —
|
|
424
|
+
which matches the way ``adapter.transform`` returns rows in the same order
|
|
425
|
+
they entered. Lengths must match exactly; otherwise raise ``ValueError``
|
|
426
|
+
with the frame names so the caller can debug.
|
|
427
|
+
"""
|
|
428
|
+
if len(dst) != len(src):
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"Cannot copy column {col!r} from {src_name!r} (len={len(src)}) to "
|
|
431
|
+
f"{dst_name!r} (len={len(dst)}): row counts must match. This usually "
|
|
432
|
+
f"means WOE transformation dropped or reordered rows relative to the "
|
|
433
|
+
f"source frame — check woe_fit_query / dropna behavior."
|
|
434
|
+
)
|
|
435
|
+
dst[col] = src[col].to_numpy()
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def split_oot_by_flag(
|
|
439
|
+
data: pd.DataFrame,
|
|
440
|
+
oot_col: str | None,
|
|
441
|
+
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
442
|
+
"""Split a frame into (ins_oos, oot) based on an OOT flag column.
|
|
443
|
+
|
|
444
|
+
Rows where ``oot_col`` equals 0 (after numeric coercion) are treated as
|
|
445
|
+
in-sample/out-of-sample, everything else is out-of-time.
|
|
446
|
+
|
|
447
|
+
The column is coerced with ``pd.to_numeric(errors="raise")`` so string /
|
|
448
|
+
boolean / mixed-dtype flags raise a clear error instead of silently routing
|
|
449
|
+
every row to ``oot`` (previous behavior: string ``"0"`` != integer ``0``,
|
|
450
|
+
so every row ended up in ``oot`` and ``ins_oos`` came out empty).
|
|
451
|
+
|
|
452
|
+
If ``oot_col`` is falsy or absent from ``data``, the full frame is returned
|
|
453
|
+
as ``ins_oos`` and an empty frame with matching columns as ``oot`` — same
|
|
454
|
+
as the historical fallback.
|
|
455
|
+
"""
|
|
456
|
+
if not oot_col or oot_col not in data.columns:
|
|
457
|
+
empty = pd.DataFrame(columns=data.columns)
|
|
458
|
+
return data, empty
|
|
459
|
+
|
|
460
|
+
raw = data[oot_col]
|
|
461
|
+
try:
|
|
462
|
+
numeric = pd.to_numeric(raw, errors="raise")
|
|
463
|
+
except (ValueError, TypeError) as exc:
|
|
464
|
+
raise TypeError(
|
|
465
|
+
f"oot_col {oot_col!r} must be numeric (0 = ins_oos, non-zero = oot); "
|
|
466
|
+
f"got dtype={raw.dtype} with non-numeric values. Original error: {exc}"
|
|
467
|
+
) from exc
|
|
468
|
+
|
|
469
|
+
mask_oot = numeric.fillna(0).astype(float) != 0.0
|
|
470
|
+
ins_oos = data.loc[~mask_oot].copy()
|
|
471
|
+
oot = data.loc[mask_oot].copy()
|
|
472
|
+
return ins_oos, oot
|
{supermodelingfactory-0.3.15 → supermodelingfactory-0.4.2}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
@@ -11,11 +11,13 @@ from ._common import (
|
|
|
11
11
|
add_dataset_with_optional_weight,
|
|
12
12
|
apply_woe_fit_query,
|
|
13
13
|
as_list,
|
|
14
|
+
copy_column_length_checked,
|
|
14
15
|
make_dirs,
|
|
15
16
|
merge_dict,
|
|
16
17
|
persist_explain_outputs,
|
|
17
18
|
predict_positive,
|
|
18
19
|
safe_to_csv,
|
|
20
|
+
split_oot_by_flag,
|
|
19
21
|
validate_woe_fit_query_columns,
|
|
20
22
|
validate_woe_fit_query_syntax,
|
|
21
23
|
write_basic_excel,
|
|
@@ -417,8 +419,7 @@ class CreditModelPipeline:
|
|
|
417
419
|
raise ValueError(f"split_col {cfg.split_col!r} must contain non-empty ins and oos samples")
|
|
418
420
|
|
|
419
421
|
if cfg.oot_col and cfg.oot_col in work.columns:
|
|
420
|
-
ins_oos = work
|
|
421
|
-
oot = work[work[cfg.oot_col] != 0].copy()
|
|
422
|
+
ins_oos, oot = split_oot_by_flag(work, cfg.oot_col)
|
|
422
423
|
else:
|
|
423
424
|
ins_oos = work
|
|
424
425
|
oot = pd.DataFrame(columns=work.columns)
|
|
@@ -600,10 +601,22 @@ class CreditModelPipeline:
|
|
|
600
601
|
if cfg.warm_start_enabled and cfg.warm_start_score_col:
|
|
601
602
|
for name, df in woe_splits.items():
|
|
602
603
|
if cfg.warm_start_score_col not in df.columns and cfg.warm_start_score_col in splits[name].columns:
|
|
603
|
-
|
|
604
|
+
copy_column_length_checked(
|
|
605
|
+
df,
|
|
606
|
+
splits[name],
|
|
607
|
+
cfg.warm_start_score_col,
|
|
608
|
+
dst_name=f"woe_splits[{name!r}]",
|
|
609
|
+
src_name=f"splits[{name!r}]",
|
|
610
|
+
)
|
|
604
611
|
for name, df in extra_eval.items():
|
|
605
612
|
if cfg.warm_start_score_col not in df.columns and cfg.warm_start_score_col in cfg.extra_eval_datasets[name].columns:
|
|
606
|
-
|
|
613
|
+
copy_column_length_checked(
|
|
614
|
+
df,
|
|
615
|
+
cfg.extra_eval_datasets[name],
|
|
616
|
+
cfg.warm_start_score_col,
|
|
617
|
+
dst_name=f"extra_eval[{name!r}]",
|
|
618
|
+
src_name=f"extra_eval_datasets[{name!r}]",
|
|
619
|
+
)
|
|
607
620
|
|
|
608
621
|
return {
|
|
609
622
|
"engine": adapter,
|
|
@@ -697,11 +710,23 @@ class CreditModelPipeline:
|
|
|
697
710
|
if cfg.warm_start_enabled and cfg.warm_start_score_col:
|
|
698
711
|
for name, df in woe_splits.items():
|
|
699
712
|
if cfg.warm_start_score_col not in df.columns:
|
|
700
|
-
|
|
713
|
+
copy_column_length_checked(
|
|
714
|
+
df,
|
|
715
|
+
splits[name],
|
|
716
|
+
cfg.warm_start_score_col,
|
|
717
|
+
dst_name=f"woe_splits[{name!r}]",
|
|
718
|
+
src_name=f"splits[{name!r}]",
|
|
719
|
+
)
|
|
701
720
|
for name, df in extra_eval.items():
|
|
702
721
|
if cfg.warm_start_score_col not in df.columns:
|
|
703
722
|
source = cfg.extra_eval_datasets[name]
|
|
704
|
-
|
|
723
|
+
copy_column_length_checked(
|
|
724
|
+
df,
|
|
725
|
+
source,
|
|
726
|
+
cfg.warm_start_score_col,
|
|
727
|
+
dst_name=f"extra_eval[{name!r}]",
|
|
728
|
+
src_name=f"extra_eval_datasets[{name!r}]",
|
|
729
|
+
)
|
|
705
730
|
|
|
706
731
|
return {
|
|
707
732
|
"engine": engine,
|
|
@@ -808,7 +833,9 @@ class CreditModelPipeline:
|
|
|
808
833
|
if name == "lr" and cfg.use_lr_search_params and hasattr(self, "_lr_best_params"):
|
|
809
834
|
params = merge_dict(params, getattr(self, "_lr_best_params", {}))
|
|
810
835
|
if name == "lr":
|
|
811
|
-
|
|
836
|
+
lr_params = dict(params) if params else {}
|
|
837
|
+
standardize = bool(lr_params.pop("standardize", False))
|
|
838
|
+
lr = LRMaster(params=lr_params or None, standardize=standardize)
|
|
812
839
|
lr.fit(
|
|
813
840
|
data=train,
|
|
814
841
|
varlist=feature_cols,
|
|
@@ -999,7 +1026,8 @@ class CreditModelPipeline:
|
|
|
999
1026
|
results = {}
|
|
1000
1027
|
if not as_list(cfg.optuna_models):
|
|
1001
1028
|
return results
|
|
1002
|
-
|
|
1029
|
+
user_search_spaces = cfg.optuna_params.get("search_spaces")
|
|
1030
|
+
search_spaces = self._default_search_spaces() if user_search_spaces is None else user_search_spaces
|
|
1003
1031
|
for raw_name in as_list(cfg.optuna_models):
|
|
1004
1032
|
name = str(raw_name).lower()
|
|
1005
1033
|
if name not in {"lgb", "xgb", "cat"} or name not in search_spaces:
|