SuperModelingFactory 0.4.2__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Feature_Insights.py +24 -5
  2. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Feature_Screen.py +6 -5
  3. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/PSI_Tool.py +204 -39
  4. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/PSI_Tool.pyi +7 -7
  5. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +101 -14
  6. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Weighted_Screen.py +80 -42
  7. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/reject_inference.py +31 -0
  8. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Master.py +39 -1
  9. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Master.pyi +1 -1
  10. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +76 -0
  11. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/__init__.py +4 -1
  12. supermodelingfactory-0.5.1/Modeling_Tool/_utils/__init__.py +6 -0
  13. supermodelingfactory-0.5.1/Modeling_Tool/_utils/robust.py +55 -0
  14. supermodelingfactory-0.5.1/Modeling_Tool/_utils/sentinels.py +10 -0
  15. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/PKG-INFO +1 -1
  16. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/PKG-INFO +1 -1
  17. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/SOURCES.txt +3 -0
  18. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/pyproject.toml +1 -1
  19. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/setup.py +1 -1
  20. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/ExcelMaster/ExcelFormatTool.py +0 -0
  21. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/ExcelMaster/ExcelMaster.py +0 -0
  22. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/ExcelMaster/Template.py +0 -0
  23. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/ExcelMaster/Utility.py +0 -0
  24. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/ExcelMaster/__init__.py +0 -0
  25. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/LICENSE +0 -0
  26. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/MANIFEST.in +0 -0
  27. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  28. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  29. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  30. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  31. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  32. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  33. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  34. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  35. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  36. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  37. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  38. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  39. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  40. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/__init__.py +0 -0
  41. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/kDataFrame.py +0 -0
  42. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  43. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  44. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Core/utils.py +0 -0
  45. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  46. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  47. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  48. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  49. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/__init__.py +0 -0
  50. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  51. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  52. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  53. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  54. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  55. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Explainability/__init__.py +0 -0
  56. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  57. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  58. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  59. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Feature/__init__.py +0 -0
  60. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  61. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  62. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  63. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  64. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  65. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  66. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  67. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Model/__init__.py +0 -0
  68. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/__init__.py +0 -0
  69. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/_common.py +0 -0
  70. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/credit_model.py +0 -0
  71. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/feature_validation.py +0 -0
  72. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  73. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
  74. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  75. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  76. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  77. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
  78. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  79. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  80. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  81. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  82. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  83. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  84. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/Sample/__init__.py +0 -0
  85. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  86. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/UAT/__init__.py +0 -0
  87. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  88. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  89. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  90. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  91. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  92. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  93. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  94. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  95. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  96. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/__init__.py +0 -0
  97. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  98. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  99. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  100. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  101. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/ref_font/__init__.py +0 -0
  102. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  103. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/README.md +0 -0
  104. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Report/Report_Tool.py +0 -0
  105. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/Report/__init__.py +0 -0
  106. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  107. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  108. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/requires.txt +0 -0
  109. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  110. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/requirements.txt +0 -0
  111. {supermodelingfactory-0.4.2 → supermodelingfactory-0.5.1}/setup.cfg +0 -0
@@ -4,9 +4,11 @@
4
4
  """
5
5
 
6
6
  import pandas as pd
7
+ import warnings
7
8
  from tqdm import tqdm
8
9
 
9
10
  from .Distribution_Tool import proc_means_by_grp, proc_means_for_screening
11
+ from Modeling_Tool._utils.robust import smf_logger
10
12
  import logging
11
13
  logger = logging.getLogger(__name__)
12
14
 
@@ -108,6 +110,7 @@ class VarExtractionInsights:
108
110
  self.seed = seed
109
111
  self.missing_rate_ref = missing_rate_ref
110
112
  self.spec_values = spec_values if spec_values is not None else []
113
+ self.failed_variables = []
111
114
 
112
115
  @staticmethod
113
116
  def remove_folder(file_path):
@@ -170,6 +173,7 @@ class VarExtractionInsights:
170
173
  """
171
174
  if dep is None:
172
175
  dep = self.dep
176
+ self.failed_variables = []
173
177
 
174
178
  from Modeling_Tool.Eval.Model_Eval_Tool import get_gains_table
175
179
 
@@ -200,9 +204,27 @@ class VarExtractionInsights:
200
204
  attr_iv['var'] = var
201
205
  iv_info_res.append(attr_iv)
202
206
 
203
- except TypeError:
207
+ except (TypeError, ValueError, KeyError, ZeroDivisionError) as exc:
208
+ row = smf_logger.record_and_continue(var, exc, stage="feature_insights")
209
+ self.failed_variables.append((row["feature"], row["exception_type"]))
204
210
  continue
205
211
 
212
+ if self.failed_variables:
213
+ failed = [name for name, _ in self.failed_variables]
214
+ warnings.warn(
215
+ f"{len(failed)}/{len(varlist)} variables failed insight computation: "
216
+ f"{failed[:10]}{'...' if len(failed) > 10 else ''} — see smf_logger for details",
217
+ UserWarning,
218
+ stacklevel=2,
219
+ )
220
+
221
+ output_cols = [
222
+ 'var', 'n_all', 'n', 'ks_in_gains', 'lift_in_gains', 'iv',
223
+ 'n_bump', 'missing_rate', 'min', 'mean', 'max', 'n_bins'
224
+ ]
225
+ if not iv_info_res:
226
+ return pd.DataFrame(columns=output_cols)
227
+
206
228
  iv_info_res = pd.concat(iv_info_res).sort_values("IV", ascending=False)
207
229
 
208
230
  high_iv_summary = iv_info_res.query(f"IV >= {iv_cut}").round(4)
@@ -222,10 +244,7 @@ class VarExtractionInsights:
222
244
  how='left'
223
245
  )
224
246
  fnl_summary.columns = [x.lower() for x in fnl_summary.columns]
225
- fnl_summary = fnl_summary[[
226
- 'var', 'n_all', 'n', 'ks_in_gains', 'lift_in_gains', 'iv',
227
- 'n_bump', 'missing_rate', 'min', 'mean', 'max', 'n_bins'
228
- ]]
247
+ fnl_summary = fnl_summary[output_cols]
229
248
 
230
249
  return fnl_summary
231
250
 
@@ -10,6 +10,7 @@ import numpy as np
10
10
  import pandas as pd
11
11
 
12
12
  from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
13
+ from Modeling_Tool._utils.sentinels import SMF_MISSING_BIN
13
14
 
14
15
  from .Weighted_Screen import (
15
16
  WeightedScreenResult,
@@ -163,7 +164,7 @@ def fit_screening_woe_engine(
163
164
 
164
165
  master_params = dict(woe_params or {})
165
166
  woe_suffix = master_params.pop("woe_suffix", "_woe")
166
- missing_ref_value = master_params.pop("missing_ref_value", -999999)
167
+ missing_ref_value = master_params.pop("missing_ref_value", SMF_MISSING_BIN)
167
168
  master = WOE_Master(
168
169
  train_data=fit_ins,
169
170
  varlist=features,
@@ -224,7 +225,7 @@ def _woe_bins_unweighted_screen(
224
225
  missing_rate_ref=config.missing_rate_ref,
225
226
  on_empty_stage=config.on_empty_stage,
226
227
  )
227
- binner = _resolve_screening_binner(splits, feature_cols, target_col, config, prefit_woe_engine)
228
+ binner = _resolve_screening_binner(splits, current, target_col, config, prefit_woe_engine)
228
229
 
229
230
  psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
230
231
  if config.psi_enabled:
@@ -357,7 +358,7 @@ def _weighted_woe_bins_screen(
357
358
  if binner is None:
358
359
  binner = fit_screening_woe_engine(
359
360
  ins,
360
- feature_cols,
361
+ current,
361
362
  target_col,
362
363
  woe_engine=config.woe_engine,
363
364
  woe_fit_query=config.woe_fit_query,
@@ -369,7 +370,7 @@ def _weighted_woe_bins_screen(
369
370
 
370
371
  psi_records: list[dict] = []
371
372
  if config.psi_enabled:
372
- for var in feature_cols:
373
+ for var in current:
373
374
  if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
374
375
  continue
375
376
  bins_ins = _bins_to_numpy(adapter.assign_bins(ins, var))
@@ -583,4 +584,4 @@ __all__ = [
583
584
  "feature_screen_from_dataframe",
584
585
  "fit_screening_woe_engine",
585
586
  "screen_config_from_mapping",
586
- ]
587
+ ]
@@ -20,6 +20,58 @@ from Modeling_Tool.Core.Binning_Tool import quick_binning
20
20
  # ``Feature.Weighted_Screen`` and ``WOE_Adapter`` so downstream aggregators can
21
21
  # recognise it without special-casing.
22
22
  _MISSING_BIN = "__MISSING__"
23
+ _PSI_BUCKET_POLICIES = {"floor_1e6", "smooth_laplace", "exclude"}
24
+
25
+
26
+ def _validate_psi_bucket_policy(policy: str, caller: str) -> None:
27
+ if policy not in _PSI_BUCKET_POLICIES:
28
+ raise ValueError(
29
+ f"{caller}: psi_missing_bucket_policy must be one of "
30
+ f"{sorted(_PSI_BUCKET_POLICIES)}; got {policy!r}."
31
+ )
32
+
33
+
34
+ def _psi_distributions_from_counts(
35
+ expected_count: pd.Series,
36
+ actual_count: pd.Series,
37
+ expected_total: float,
38
+ actual_total: float,
39
+ *,
40
+ content: float,
41
+ policy: str,
42
+ ) -> tuple[pd.Series, pd.Series, pd.Series, pd.Series]:
43
+ _validate_psi_bucket_policy(policy, "_psi_distributions_from_counts")
44
+ all_bins = expected_count.index.union(actual_count.index)
45
+ expected_aligned = expected_count.reindex(all_bins, fill_value=0).astype(float)
46
+ actual_aligned = actual_count.reindex(all_bins, fill_value=0).astype(float)
47
+ one_sided = (expected_aligned == 0) | (actual_aligned == 0)
48
+
49
+ expected_total = float(expected_total) if expected_total > 0 else 1.0
50
+ actual_total = float(actual_total) if actual_total > 0 else 1.0
51
+
52
+ if policy == "exclude":
53
+ keep = ~one_sided
54
+ expected_aligned = expected_aligned[keep]
55
+ actual_aligned = actual_aligned[keep]
56
+ if expected_aligned.empty:
57
+ empty = pd.Series(dtype=float)
58
+ return empty, empty, empty, empty
59
+ expected_pct = expected_aligned / expected_total
60
+ actual_pct = actual_aligned / actual_total
61
+ elif policy == "smooth_laplace" and bool(one_sided.any()):
62
+ n_buckets = max(len(all_bins), 1)
63
+ expected_pct = (expected_aligned + 1.0) / (expected_total + n_buckets)
64
+ actual_pct = (actual_aligned + 1.0) / (actual_total + n_buckets)
65
+ else:
66
+ expected_pct = (expected_aligned / expected_total).clip(lower=content)
67
+ actual_pct = (actual_aligned / actual_total).clip(lower=content)
68
+
69
+ psi_values = (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
70
+
71
+ floor_expected = (expected_aligned / expected_total).clip(lower=content)
72
+ floor_actual = (actual_aligned / actual_total).clip(lower=content)
73
+ floor_values = (floor_actual - floor_expected) * np.log(floor_actual / floor_expected)
74
+ return expected_pct, actual_pct, psi_values, floor_values
23
75
 
24
76
  # ============================================================================
25
77
  # Classes
@@ -58,7 +110,9 @@ class PSICalculator:
58
110
  equal_freq: bool = True,
59
111
  min_bin_prop: float = 0.05,
60
112
  content: float = 1e-6,
61
- precision: int = 5
113
+ precision: int = 5,
114
+ missing_policy: str = "include",
115
+ psi_missing_bucket_policy: str = "smooth_laplace",
62
116
  ):
63
117
  """
64
118
  Initialize PSICalculator with configuration parameters.
@@ -75,12 +129,28 @@ class PSICalculator:
75
129
  Small value to prevent division by zero. Default is 1e-6.
76
130
  precision : int, optional
77
131
  Decimal precision for rounding. Default is 5.
132
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
133
+ How NaN rows are handled. Default in 0.5.0 is "include", which routes
134
+ NaN rows through a dedicated "__MISSING__" bin so missing-rate drift
135
+ contributes to the PSI. This is the recommended production behaviour
136
+ and became the default in 0.5.0 (previously "drop" in 0.4.2 for
137
+ numeric backward-compat). Pass "drop" to reproduce pre-0.5.0
138
+ numbers, or "warn_and_drop" for legacy numbers with a RuntimeWarning
139
+ naming the NaN counts.
78
140
  """
141
+ if missing_policy not in {"include", "drop", "warn_and_drop"}:
142
+ raise ValueError(
143
+ f"PSICalculator.__init__: missing_policy must be one of "
144
+ f"'include', 'drop', 'warn_and_drop'; got {missing_policy!r}."
145
+ )
146
+ _validate_psi_bucket_policy(psi_missing_bucket_policy, "PSICalculator.__init__")
79
147
  self.buckets = buckets
80
148
  self.equal_freq = equal_freq
81
149
  self.min_bin_prop = min_bin_prop
82
150
  self.content = content
83
151
  self.precision = precision
152
+ self.missing_policy = missing_policy
153
+ self.psi_missing_bucket_policy = psi_missing_bucket_policy
84
154
 
85
155
  # def _calculate_single_psi(
86
156
  # self,
@@ -418,7 +488,9 @@ class PSICalculator:
418
488
  varlist: List[str],
419
489
  group_by: Optional[str] = None,
420
490
  group_name: Optional[str] = None,
421
- return_details = False
491
+ return_details = False,
492
+ missing_policy: Optional[str] = None,
493
+ psi_missing_bucket_policy: Optional[str] = None,
422
494
  ) -> pd.DataFrame:
423
495
  """
424
496
  Calculate grouped PSI comparing two datasets, using expected as benchmark.
@@ -435,12 +507,23 @@ class PSICalculator:
435
507
  Column to group by in both datasets.
436
508
  group_name : str, optional
437
509
  Specific group column name for multi-group calculation.
510
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
511
+ How NaN rows are handled. If None (default), falls back to the value
512
+ configured on ``self.missing_policy``. Pass an explicit value to
513
+ override the class-level default for this call only. See
514
+ ``PSICalculator.__init__`` for the semantics of each mode.
438
515
 
439
516
  Returns
440
517
  -------
441
518
  pandas.DataFrame
442
519
  Grouped PSI results.
443
520
  """
521
+ effective_policy = missing_policy if missing_policy is not None else self.missing_policy
522
+ effective_bucket_policy = (
523
+ psi_missing_bucket_policy
524
+ if psi_missing_bucket_policy is not None
525
+ else self.psi_missing_bucket_policy
526
+ )
444
527
  return calculate_multigroup_psi_two_sets(
445
528
  expected_df = expected_df,
446
529
  actual_df = current_data,
@@ -452,7 +535,9 @@ class PSICalculator:
452
535
  content = self.content,
453
536
  precision = self.precision,
454
537
  group_name = group_name,
455
- return_details = return_details
538
+ return_details = return_details,
539
+ missing_policy = effective_policy,
540
+ psi_missing_bucket_policy = effective_bucket_policy,
456
541
  )
457
542
 
458
543
 
@@ -469,7 +554,8 @@ def _calculate_single_psi(
469
554
  min_bin_prop: float = 0.05,
470
555
  content: float = 1e-6,
471
556
  precision: int = 5,
472
- missing_policy: str = "drop",
557
+ missing_policy: str = "include",
558
+ psi_missing_bucket_policy: str = "smooth_laplace",
473
559
  ) -> Union[float, Tuple[float, pd.DataFrame]]:
474
560
  """
475
561
  Calculate Population Stability Index (PSI) for a single variable.
@@ -536,6 +622,7 @@ def _calculate_single_psi(
536
622
  f"_calculate_single_psi: missing_policy must be one of "
537
623
  f"'include', 'drop', 'warn_and_drop'; got {missing_policy!r}."
538
624
  )
625
+ _validate_psi_bucket_policy(psi_missing_bucket_policy, "_calculate_single_psi")
539
626
 
540
627
  # Record NaN counts before splitting so we can (a) re-attach the missing
541
628
  # bin under "include" and (b) warn under "warn_and_drop".
@@ -608,25 +695,24 @@ def _calculate_single_psi(
608
695
  )
609
696
  expected_denom = float(n_expected) if n_expected > 0 else 1.0
610
697
  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
698
  else:
614
699
  # "drop" / "warn_and_drop": legacy behaviour, denominators are the
615
700
  # 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)
618
-
619
- # Ensure both series have the same bin indices
620
- all_bins = expected_percents.index.union(actual_percents.index)
621
- expected_percents = expected_percents.reindex(all_bins, fill_value=content)
622
- actual_percents = actual_percents.reindex(all_bins, fill_value=content)
623
-
624
- # Clip to avoid division by zero
625
- expected_percents = expected_percents.clip(lower=content)
626
- actual_percents = actual_percents.clip(lower=content)
627
-
628
- # Calculate PSI
629
- psi_values = (actual_percents - expected_percents) * np.log(actual_percents / expected_percents)
701
+ expected_denom = float(len(expected_bins)) if len(expected_bins) > 0 else 1.0
702
+ actual_denom = float(len(actual_bins)) if len(actual_bins) > 0 else 1.0
703
+
704
+ all_bins = expected_count.index.union(actual_count.index)
705
+ expected_count = expected_count.reindex(all_bins, fill_value=0).astype(float)
706
+ actual_count = actual_count.reindex(all_bins, fill_value=0).astype(float)
707
+
708
+ expected_percents, actual_percents, psi_values, psi_floor_values = _psi_distributions_from_counts(
709
+ expected_count,
710
+ actual_count,
711
+ expected_denom,
712
+ actual_denom,
713
+ content=content,
714
+ policy=psi_missing_bucket_policy,
715
+ )
630
716
  psi_total = psi_values.sum()
631
717
 
632
718
  if return_details:
@@ -635,8 +721,13 @@ def _calculate_single_psi(
635
721
  'actual_count': actual_count,
636
722
  'expected_percent': expected_percents,
637
723
  'actual_percent': actual_percents,
638
- 'psi_component': psi_values
724
+ 'psi_component': psi_values,
725
+ 'psi_component_floor_1e6': psi_floor_values,
639
726
  })
727
+ details["bucket_status"] = "common"
728
+ details.loc[(details["expected_count"] == 0) & (details["actual_count"] > 0), "bucket_status"] = "actual_only"
729
+ details.loc[(details["expected_count"] > 0) & (details["actual_count"] == 0), "bucket_status"] = "expected_only"
730
+ details["psi_missing_bucket_policy"] = psi_missing_bucket_policy
640
731
  return psi_total, details
641
732
  else:
642
733
  return psi_total
@@ -652,7 +743,9 @@ def calculate_psi(
652
743
  return_details: bool = False,
653
744
  min_bin_prop: float = 0.05,
654
745
  content: float = 1e-6,
655
- precision: int = 5
746
+ precision: int = 5,
747
+ missing_policy: str = "include",
748
+ psi_missing_bucket_policy: str = "smooth_laplace",
656
749
  ) -> Union[float, pd.DataFrame, Tuple[Dict, Dict]]:
657
750
  """
658
751
  Calculate Population Stability Index (PSI) for a variable, optionally by groups.
@@ -682,6 +775,14 @@ def calculate_psi(
682
775
  Small value to avoid division by zero. Default is 1e-6.
683
776
  precision : int, optional
684
777
  Decimal precision. Default is 5.
778
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
779
+ How NaN rows are handled. Default in 0.5.0 is "include", which routes
780
+ NaN rows through a dedicated "__MISSING__" bin so missing-rate drift
781
+ contributes to the PSI. This is the recommended production behaviour
782
+ and became the default in 0.5.0 (previously "drop" in 0.4.2 for
783
+ numeric backward-compat). Pass "drop" to reproduce pre-0.5.0
784
+ numbers, or "warn_and_drop" for legacy numbers with a RuntimeWarning
785
+ naming the NaN counts.
685
786
 
686
787
  Returns
687
788
  -------
@@ -734,7 +835,9 @@ def calculate_psi(
734
835
  True,
735
836
  min_bin_prop,
736
837
  content,
737
- precision
838
+ precision,
839
+ missing_policy,
840
+ psi_missing_bucket_policy,
738
841
  )
739
842
  results[group] = psi_value
740
843
  details_dict[group] = detail
@@ -747,7 +850,9 @@ def calculate_psi(
747
850
  False,
748
851
  min_bin_prop,
749
852
  content,
750
- precision
853
+ precision,
854
+ missing_policy,
855
+ psi_missing_bucket_policy,
751
856
  )
752
857
 
753
858
  if return_details:
@@ -765,7 +870,9 @@ def calculate_psi(
765
870
  True,
766
871
  min_bin_prop,
767
872
  content,
768
- precision
873
+ precision,
874
+ missing_policy,
875
+ psi_missing_bucket_policy,
769
876
  )
770
877
  else:
771
878
  return _calculate_single_psi(
@@ -776,7 +883,9 @@ def calculate_psi(
776
883
  False,
777
884
  min_bin_prop,
778
885
  content,
779
- precision
886
+ precision,
887
+ missing_policy,
888
+ psi_missing_bucket_policy,
780
889
  )
781
890
 
782
891
 
@@ -791,7 +900,9 @@ def calculate_within_psi(
791
900
  min_bin_prop: float = 0.05,
792
901
  content: float = 1e-6,
793
902
  precision: int = 5,
794
- benchmark_display_name: Optional[str] = None
903
+ benchmark_display_name: Optional[str] = None,
904
+ missing_policy: str = "include",
905
+ psi_missing_bucket_policy: str = "smooth_laplace",
795
906
  ) -> Union[pd.DataFrame, Dict]:
796
907
  """
797
908
  Calculate PSI values within a single dataset, comparing groups to a benchmark.
@@ -823,6 +934,14 @@ def calculate_within_psi(
823
934
  Decimal precision. Default is 5.
824
935
  benchmark_display_name : str, optional
825
936
  Custom name for benchmark in results.
937
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
938
+ How NaN rows are handled. Default in 0.5.0 is "include", which routes
939
+ NaN rows through a dedicated "__MISSING__" bin so missing-rate drift
940
+ contributes to the PSI. This is the recommended production behaviour
941
+ and became the default in 0.5.0 (previously "drop" in 0.4.2 for
942
+ numeric backward-compat). Pass "drop" to reproduce pre-0.5.0
943
+ numbers, or "warn_and_drop" for legacy numbers with a RuntimeWarning
944
+ naming the NaN counts.
826
945
 
827
946
  Returns
828
947
  -------
@@ -858,7 +977,9 @@ def calculate_within_psi(
858
977
  return_details=True,
859
978
  min_bin_prop=min_bin_prop,
860
979
  content=content,
861
- precision=precision
980
+ precision=precision,
981
+ missing_policy=missing_policy,
982
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
862
983
  )
863
984
  res_dict[obs_value] = psi
864
985
  detail_dict[obs_value] = details
@@ -872,7 +993,9 @@ def calculate_within_psi(
872
993
  return_details=False,
873
994
  min_bin_prop=min_bin_prop,
874
995
  content=content,
875
- precision=precision
996
+ precision=precision,
997
+ missing_policy=missing_policy,
998
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
876
999
  )
877
1000
  res_dict[obs_value] = psi
878
1001
 
@@ -895,7 +1018,9 @@ def calculate_psi_within_dataset(
895
1018
  buckets: int = 10,
896
1019
  min_bin_prop: float = 0.05,
897
1020
  content: float = 1e-6,
898
- precision: int = 5
1021
+ precision: int = 5,
1022
+ missing_policy: str = "include",
1023
+ psi_missing_bucket_policy: str = "smooth_laplace",
899
1024
  ) -> pd.DataFrame:
900
1025
  """
901
1026
  Calculate PSI for multiple variables within a dataset, comparing groups to a benchmark.
@@ -923,6 +1048,14 @@ def calculate_psi_within_dataset(
923
1048
  Small value to avoid division by zero. Default is 1e-6.
924
1049
  precision : int, optional
925
1050
  Decimal precision. Default is 5.
1051
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
1052
+ How NaN rows are handled. Default in 0.5.0 is "include", which routes
1053
+ NaN rows through a dedicated "__MISSING__" bin so missing-rate drift
1054
+ contributes to the PSI. This is the recommended production behaviour
1055
+ and became the default in 0.5.0 (previously "drop" in 0.4.2 for
1056
+ numeric backward-compat). Pass "drop" to reproduce pre-0.5.0
1057
+ numbers, or "warn_and_drop" for legacy numbers with a RuntimeWarning
1058
+ naming the NaN counts.
926
1059
 
927
1060
  Returns
928
1061
  -------
@@ -946,7 +1079,9 @@ def calculate_psi_within_dataset(
946
1079
  return_details=False,
947
1080
  min_bin_prop=min_bin_prop,
948
1081
  content=content,
949
- precision=precision
1082
+ precision=precision,
1083
+ missing_policy=missing_policy,
1084
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
950
1085
  ).sort_values([grp_name]).reset_index(drop=True)
951
1086
 
952
1087
  single_psi['var'] = var
@@ -964,7 +1099,9 @@ def calculate_multivar_psi_two_sets(
964
1099
  equal_freq: bool = True,
965
1100
  min_bin_prop: float = 0.05,
966
1101
  content: float = 1e-6,
967
- precision: int = 5
1102
+ precision: int = 5,
1103
+ missing_policy: str = "include",
1104
+ psi_missing_bucket_policy: str = "smooth_laplace",
968
1105
  ) -> pd.DataFrame:
969
1106
  """
970
1107
  Calculate PSI for multiple variables by comparing two different datasets.
@@ -992,6 +1129,14 @@ def calculate_multivar_psi_two_sets(
992
1129
  Small value to avoid division by zero. Default is 1e-6.
993
1130
  precision : int, optional
994
1131
  Decimal precision. Default is 5.
1132
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
1133
+ How NaN rows are handled. Default in 0.5.0 is "include", which routes
1134
+ NaN rows through a dedicated "__MISSING__" bin so missing-rate drift
1135
+ contributes to the PSI. This is the recommended production behaviour
1136
+ and became the default in 0.5.0 (previously "drop" in 0.4.2 for
1137
+ numeric backward-compat). Pass "drop" to reproduce pre-0.5.0
1138
+ numbers, or "warn_and_drop" for legacy numbers with a RuntimeWarning
1139
+ naming the NaN counts.
995
1140
 
996
1141
  Returns
997
1142
  -------
@@ -1011,7 +1156,13 @@ def calculate_multivar_psi_two_sets(
1011
1156
  target_col=var,
1012
1157
  group_by=group_by,
1013
1158
  buckets=buckets,
1014
- return_details=False
1159
+ equal_freq=equal_freq,
1160
+ min_bin_prop=min_bin_prop,
1161
+ content=content,
1162
+ precision=precision,
1163
+ return_details=False,
1164
+ missing_policy=missing_policy,
1165
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
1015
1166
  )
1016
1167
  if group_by is None:
1017
1168
  single_psi = pd.DataFrame([single_psi], columns=['psi'])
@@ -1151,7 +1302,9 @@ def calculate_multigroup_psi_two_sets(
1151
1302
  content: float = 1e-6,
1152
1303
  precision: int = 5,
1153
1304
  group_name: Optional[str] = None,
1154
- return_details: bool = False
1305
+ return_details: bool = False,
1306
+ missing_policy: str = "include",
1307
+ psi_missing_bucket_policy: str = "smooth_laplace",
1155
1308
  ) -> Union[pd.DataFrame, Dict[str, pd.DataFrame]]:
1156
1309
  """
1157
1310
  Calculate grouped PSI using expected DataFrame as benchmark, applied to actual DataFrame groups.
@@ -1181,6 +1334,12 @@ def calculate_multigroup_psi_two_sets(
1181
1334
  return_details : bool, optional
1182
1335
  是否返回详细分箱信息。若为 True,返回字典 {'psi': psi_df, 'details': details_df},
1183
1336
  details_df 包含列:['bin', 'expected_percent', 'actual_percent', 'psi_component', group_name, 'var']
1337
+ missing_policy : {"drop", "include", "warn_and_drop"}, optional
1338
+ NaN 行处理策略。0.5.0 起默认为 "include",将 NaN 行汇入独立的
1339
+ "__MISSING__" 桶,使缺失率漂移进入 PSI 计算;这是推荐的生产行为,
1340
+ 并在 0.5.0 成为默认值(0.4.2 为 "drop" 以保持数值向后兼容)。
1341
+ 传入 "drop" 可复现 0.5.0 之前的数值,或 "warn_and_drop" 保留旧数值
1342
+ 同时发出 RuntimeWarning 报告两侧 NaN 数量。
1184
1343
  """
1185
1344
  if group_name is not None:
1186
1345
  if actual_df[group_name].isna().sum() > 0:
@@ -1203,7 +1362,9 @@ def calculate_multigroup_psi_two_sets(
1203
1362
  return_details=True,
1204
1363
  min_bin_prop=min_bin_prop,
1205
1364
  content=content,
1206
- precision=precision
1365
+ precision=precision,
1366
+ missing_policy=missing_policy,
1367
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
1207
1368
  )
1208
1369
 
1209
1370
  psi_records.append({group_name: group, 'var': var, 'psi': psi_val})
@@ -1221,7 +1382,7 @@ def calculate_multigroup_psi_two_sets(
1221
1382
  detail_df[group_name] = group
1222
1383
  detail_df['var'] = var
1223
1384
 
1224
- required_cols = ['bin', 'expected_count', 'actual_count', 'expected_percent', 'actual_percent', 'psi_component', group_name, 'var']
1385
+ required_cols = ['bin', 'expected_count', 'actual_count', 'expected_percent', 'actual_percent', 'psi_component', 'bucket_status', 'psi_missing_bucket_policy', group_name, 'var']
1225
1386
  for col in required_cols:
1226
1387
  if col not in detail_df.columns:
1227
1388
  detail_df[col] = np.nan
@@ -1250,7 +1411,9 @@ def calculate_multigroup_psi_two_sets(
1250
1411
  return_details=True,
1251
1412
  min_bin_prop=min_bin_prop,
1252
1413
  content=content,
1253
- precision=precision
1414
+ precision=precision,
1415
+ missing_policy=missing_policy,
1416
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
1254
1417
  )
1255
1418
  psi_records.append({'var': var, 'psi': psi_val})
1256
1419
 
@@ -1262,7 +1425,7 @@ def calculate_multigroup_psi_two_sets(
1262
1425
  index_col = detail_df.columns[0]
1263
1426
  detail_df = detail_df.rename(columns={index_col: 'bin'})
1264
1427
  detail_df['var'] = var
1265
- required_cols = ['bin', 'expected_count', 'actual_count', 'expected_percent', 'actual_percent', 'psi_component', 'var']
1428
+ required_cols = ['bin', 'expected_count', 'actual_count', 'expected_percent', 'actual_percent', 'psi_component', 'bucket_status', 'psi_missing_bucket_policy', 'var']
1266
1429
  for col in required_cols:
1267
1430
  if col not in detail_df.columns:
1268
1431
  detail_df[col] = np.nan
@@ -1285,7 +1448,9 @@ def calculate_multigroup_psi_two_sets(
1285
1448
  equal_freq=equal_freq,
1286
1449
  min_bin_prop=min_bin_prop,
1287
1450
  content=content,
1288
- precision=precision
1451
+ precision=precision,
1452
+ missing_policy=missing_policy,
1453
+ psi_missing_bucket_policy=psi_missing_bucket_policy,
1289
1454
  )
1290
1455
 
1291
1456
  return group_psi
@@ -20,10 +20,10 @@ from tqdm import tqdm
20
20
  from Modeling_Tool.Core.Binning_Tool import quick_binning
21
21
 
22
22
  class PSICalculator:
23
- def __init__(self, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, binning_engine: Any = None): ...
24
- def calculate(self, expected_df: pd.DataFrame, current_data: pd.DataFrame, varlist: List[str], group_by: Optional[str] = None, group_name: Optional[str] = None, return_details = False) -> pd.DataFrame: ...
25
- def calculate_psi(expected: Union[pd.DataFrame, pd.Series], actual: Union[pd.DataFrame, pd.Series], target_col: str, buckets: int = 10, equal_freq: bool = True, group_by: Optional[Union[str, List[str]]] = None, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> Union[float, pd.DataFrame, Tuple[Dict, Dict]]: ...
26
- def calculate_within_psi(data: pd.DataFrame, grp_name: str, target_col: str, benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, benchmark_display_name: Optional[str] = None) -> Union[pd.DataFrame, Dict]: ...
27
- def calculate_psi_within_dataset(data: pd.DataFrame, grp_name: str, varlist: List[str], benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> pd.DataFrame: ...
28
- def calculate_multivar_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5) -> pd.DataFrame: ...
29
- def calculate_multigroup_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, group_name: Optional[str] = None, return_details: bool = False) -> Union[pd.DataFrame, Dict[str, pd.DataFrame]]: ...
23
+ def __init__(self, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace", binning_engine: Any = None): ...
24
+ def calculate(self, expected_df: pd.DataFrame, current_data: pd.DataFrame, varlist: List[str], group_by: Optional[str] = None, group_name: Optional[str] = None, return_details = False, missing_policy: Optional[str] = None, psi_missing_bucket_policy: Optional[str] = None) -> pd.DataFrame: ...
25
+ def calculate_psi(expected: Union[pd.DataFrame, pd.Series], actual: Union[pd.DataFrame, pd.Series], target_col: str, buckets: int = 10, equal_freq: bool = True, group_by: Optional[Union[str, List[str]]] = None, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace") -> Union[float, pd.DataFrame, Tuple[Dict, Dict]]: ...
26
+ def calculate_within_psi(data: pd.DataFrame, grp_name: str, target_col: str, benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, return_details: bool = False, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, benchmark_display_name: Optional[str] = None, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace") -> Union[pd.DataFrame, Dict]: ...
27
+ def calculate_psi_within_dataset(data: pd.DataFrame, grp_name: str, varlist: List[str], benchmark: Optional[Any] = None, equal_freq: bool = True, buckets: int = 10, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace") -> pd.DataFrame: ...
28
+ def calculate_multivar_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace") -> pd.DataFrame: ...
29
+ def calculate_multigroup_psi_two_sets(expected_df: pd.DataFrame, actual_df: pd.DataFrame, varlist: List[str], group_by: Optional[Union[str, List[str]]] = None, buckets: int = 10, equal_freq: bool = True, min_bin_prop: float = 0.05, content: float = 1e-06, precision: int = 5, group_name: Optional[str] = None, return_details: bool = False, missing_policy: str = "include", psi_missing_bucket_policy: str = "smooth_laplace") -> Union[pd.DataFrame, Dict[str, pd.DataFrame]]: ...