SuperModelingFactory 0.2.0__py3-none-any.whl
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.
- ExcelMaster/ExcelFormatTool.py +487 -0
- ExcelMaster/ExcelMaster.py +917 -0
- ExcelMaster/Template.py +525 -0
- ExcelMaster/Utility.py +233 -0
- ExcelMaster/__init__.py +3 -0
- Modeling_Tool/Core/Binning_Tool.py +1608 -0
- Modeling_Tool/Core/Binning_Tool.pyi +48 -0
- Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
- Modeling_Tool/Core/Json_Data_Converter.py +621 -0
- Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
- Modeling_Tool/Core/ODPS_Tool.py +284 -0
- Modeling_Tool/Core/Slope_Tool.py +356 -0
- Modeling_Tool/Core/Slope_Tool.pyi +31 -0
- Modeling_Tool/Core/XOR_Encryptor.py +207 -0
- Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
- Modeling_Tool/Core/__init__.py +99 -0
- Modeling_Tool/Core/kDataFrame.py +228 -0
- Modeling_Tool/Core/kDataFrame.pyi +43 -0
- Modeling_Tool/Core/sample_weight_utils.py +77 -0
- Modeling_Tool/Core/utils.py +2672 -0
- Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
- Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
- Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
- Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
- Modeling_Tool/Eval/__init__.py +54 -0
- Modeling_Tool/Eval/evaluate_model.py +2008 -0
- Modeling_Tool/Eval/evaluate_model.pyi +50 -0
- Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
- Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
- Modeling_Tool/Explainability/Model_Explainer.py +743 -0
- Modeling_Tool/Explainability/__init__.py +24 -0
- Modeling_Tool/Feature/Distribution_Tool.py +509 -0
- Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
- Modeling_Tool/Feature/Feature_Insights.py +762 -0
- Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
- Modeling_Tool/Feature/PSI_Tool.py +1195 -0
- Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
- Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
- Modeling_Tool/Feature/__init__.py +40 -0
- Modeling_Tool/Model/Backward_Tool.py +778 -0
- Modeling_Tool/Model/Backward_Tool.pyi +45 -0
- Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
- Modeling_Tool/Model/GBM_Tool.py +1610 -0
- Modeling_Tool/Model/GBM_Tool.pyi +90 -0
- Modeling_Tool/Model/LRM_Tool.py +1198 -0
- Modeling_Tool/Model/LRM_Tool.pyi +47 -0
- Modeling_Tool/Model/__init__.py +61 -0
- Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
- Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
- Modeling_Tool/Sample/Reject_Infer.py +413 -0
- Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
- Modeling_Tool/Sample/Sample_Split.py +520 -0
- Modeling_Tool/Sample/Sample_Split.pyi +43 -0
- Modeling_Tool/Sample/__init__.py +31 -0
- Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
- Modeling_Tool/UAT/__init__.py +19 -0
- Modeling_Tool/WOE/WOE_Adapter.py +204 -0
- Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
- Modeling_Tool/WOE/WOE_Master.py +491 -0
- Modeling_Tool/WOE/WOE_Master.pyi +40 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
- Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
- Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
- Modeling_Tool/WOE/WOE_Tool.py +1094 -0
- Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
- Modeling_Tool/WOE/__init__.py +86 -0
- Modeling_Tool/WOE/plot_woe_tool.py +290 -0
- Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
- Modeling_Tool/__init__.py +176 -0
- Report/Report_Tool.py +380 -0
- Report/__init__.py +3 -0
- supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
- supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
- supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
- supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
- supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Modeling_Tool.Sample.Reject_Infer
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2026 Kyle Sun <github.com/Kyle-J-Sun>. All rights reserved.
|
|
5
|
+
# SuperModelingFactory — Licensed under the Business Source License 1.1.
|
|
6
|
+
#
|
|
7
|
+
# This stub describes the public API of a closed-source module compiled to a
|
|
8
|
+
# native extension (.so / .pyd). The original source is not distributed.
|
|
9
|
+
# Production / commercial use requires a separate commercial license.
|
|
10
|
+
#
|
|
11
|
+
# FINGERPRINT: SMF-REJECTINFER-31339160
|
|
12
|
+
# (Unique trace marker. Do not remove or alter — used for plagiarism
|
|
13
|
+
# detection across the public internet.)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import numpy as np
|
|
18
|
+
from typing import Union, Optional, List, Dict, Any, Tuple
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
|
|
21
|
+
class RejectInferrer(ABC):
|
|
22
|
+
def __init__(self, target_col: str = 'target', score_col: str = 'score'): ...
|
|
23
|
+
def infer(self, df_approved: pd.DataFrame, df_rejected: pd.DataFrame, score_col: Optional[str] = None) -> pd.DataFrame: ...
|
|
24
|
+
|
|
25
|
+
class SimpleAugmentInferrer(RejectInferrer):
|
|
26
|
+
def __init__(self, target_col: str = 'target', score_col: str = 'score', bad_rate: Optional[float] = None): ...
|
|
27
|
+
def infer(self, df_approved: pd.DataFrame, df_rejected: pd.DataFrame, score_col: Optional[str] = None) -> pd.DataFrame: ...
|
|
28
|
+
|
|
29
|
+
class HardCutoffInferrer(RejectInferrer):
|
|
30
|
+
def __init__(self, target_col: str = 'target', score_col: str = 'score', cutoff: float = 0.5): ...
|
|
31
|
+
def infer(self, df_approved: pd.DataFrame, df_rejected: pd.DataFrame, score_col: Optional[str] = None) -> pd.DataFrame: ...
|
|
32
|
+
|
|
33
|
+
class FuzzyAugmentInferrer(RejectInferrer):
|
|
34
|
+
def __init__(self, target_col: str = 'target', score_col: str = 'score', weight_factor: float = 1.0): ...
|
|
35
|
+
def infer(self, df_approved: pd.DataFrame, df_rejected: pd.DataFrame, score_col: Optional[str] = None) -> pd.DataFrame: ...
|
|
36
|
+
|
|
37
|
+
class ParcelingInferrer(RejectInferrer):
|
|
38
|
+
def __init__(self, target_col: str = 'target', score_col: str = 'score', n_parcels: int = 10): ...
|
|
39
|
+
def infer(self, df_approved: pd.DataFrame, df_rejected: pd.DataFrame, score_col: Optional[str] = None) -> pd.DataFrame: ...
|
|
40
|
+
|
|
41
|
+
class RejectInferenceFactory:
|
|
42
|
+
def create(cls, method: str = 'parceling', **kwargs) -> RejectInferrer: ...
|
|
43
|
+
def available_methods(cls) -> List[str]: ...
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sampling classes for credit modeling.
|
|
3
|
+
|
|
4
|
+
This module provides classes for splitting samples, stratified sampling,
|
|
5
|
+
and sample balancing for credit model development.
|
|
6
|
+
|
|
7
|
+
Classes
|
|
8
|
+
-------
|
|
9
|
+
SampleSplitter : Split data into train/test samples.
|
|
10
|
+
StratifiedSampler : Stratified sampling with target balance control.
|
|
11
|
+
SampleBalancer : Balance samples using various techniques.
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
--------
|
|
15
|
+
>>> from Modeling_Tool_refactored.sample import SampleSplitter
|
|
16
|
+
>>> splitter = SampleSplitter()
|
|
17
|
+
>>> train, test = splitter.split(df, 'target', test_size=0.3)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
import numpy as np
|
|
22
|
+
from typing import Union, Optional, List, Dict, Any, Tuple
|
|
23
|
+
from sklearn.model_selection import train_test_split
|
|
24
|
+
from Modeling_Tool.Eval.Model_Eval_Tool import PerformanceEvaluator
|
|
25
|
+
from Modeling_Tool.Core.utils import get_feature_names
|
|
26
|
+
|
|
27
|
+
def select_sample_seed(master_df, oot_split_col, model, tgt_name, seed_range = (3000, 3050), ins_prop = 0.7):
|
|
28
|
+
""" Select Best Seed for Sample Splitting. """
|
|
29
|
+
|
|
30
|
+
from tqdm import tqdm
|
|
31
|
+
|
|
32
|
+
if isinstance(model, str):
|
|
33
|
+
score = model
|
|
34
|
+
|
|
35
|
+
perf_res = pd.DataFrame()
|
|
36
|
+
for seed in tqdm(range(seed_range[0], seed_range[1])):
|
|
37
|
+
|
|
38
|
+
train_df = master_df.loc[master_df[oot_split_col].isin([1])]
|
|
39
|
+
oot_df = master_df.loc[master_df[oot_split_col].isin([2])]
|
|
40
|
+
|
|
41
|
+
sampler = SampleSplitter(test_size = (1 - ins_prop), random_state=seed, stratify=True)
|
|
42
|
+
mdl_df, val_df = sampler.split_df(train_df, tgt_name)
|
|
43
|
+
|
|
44
|
+
mdl_df['sample_ind_fnl'] = "ins"
|
|
45
|
+
val_df['sample_ind_fnl'] = "oos"
|
|
46
|
+
oot_df['sample_ind_fnl'] = "oot"
|
|
47
|
+
|
|
48
|
+
drv_w_sample_ind = pd.concat([mdl_df, val_df, oot_df])
|
|
49
|
+
|
|
50
|
+
ins_df = drv_w_sample_ind.loc[drv_w_sample_ind['sample_ind_fnl'].isin(['ins'])]
|
|
51
|
+
oos_df = drv_w_sample_ind.loc[drv_w_sample_ind['sample_ind_fnl'].isin(['oos'])]
|
|
52
|
+
oot_df = drv_w_sample_ind.loc[drv_w_sample_ind['sample_ind_fnl'].isin(['oot'])]
|
|
53
|
+
|
|
54
|
+
evaluator = PerformanceEvaluator(tgt_name=tgt_name, model=model, feature_cols=get_feature_names(model)) if not isinstance(model, str) else PerformanceEvaluator(tgt_name=tgt_name, scr_name = score)
|
|
55
|
+
|
|
56
|
+
if ins_df.shape[0] > 0:
|
|
57
|
+
evaluator = evaluator.add_dataset('train', ins_df)
|
|
58
|
+
|
|
59
|
+
if oos_df.shape[0] > 0:
|
|
60
|
+
evaluator = evaluator.add_dataset('validation', oos_df)
|
|
61
|
+
|
|
62
|
+
if oot_df.shape[0] > 0:
|
|
63
|
+
evaluator = evaluator.add_dataset('oot', oot_df)
|
|
64
|
+
|
|
65
|
+
result = evaluator.evaluate(to_show=False, display = False)
|
|
66
|
+
result['seed'] = seed
|
|
67
|
+
|
|
68
|
+
perf_res = pd.concat([perf_res, result], axis = 0)
|
|
69
|
+
|
|
70
|
+
return perf_res
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SampleSplitter:
|
|
74
|
+
"""
|
|
75
|
+
Split data into training and testing samples.
|
|
76
|
+
|
|
77
|
+
This class provides flexible sample splitting with support for
|
|
78
|
+
stratification, random sampling, and custom split ratios.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
test_size : float, default 0.3
|
|
83
|
+
Proportion of data for testing (0 to 1).
|
|
84
|
+
random_state : int, optional
|
|
85
|
+
Random seed for reproducibility.
|
|
86
|
+
stratify : bool, default True
|
|
87
|
+
Whether to stratify by target variable.
|
|
88
|
+
|
|
89
|
+
Attributes
|
|
90
|
+
----------
|
|
91
|
+
train_index_ : numpy.ndarray
|
|
92
|
+
Indices for training data.
|
|
93
|
+
test_index_ : numpy.ndarray
|
|
94
|
+
Indices for testing data.
|
|
95
|
+
|
|
96
|
+
Methods
|
|
97
|
+
-------
|
|
98
|
+
split(X, y, test_size=None, stratify=None)
|
|
99
|
+
Split data into train and test sets.
|
|
100
|
+
split_df(df, target, exclude_cols=None)
|
|
101
|
+
Split DataFrame while excluding certain columns.
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
>>> splitter = SampleSplitter(test_size=0.2, random_state=42)
|
|
106
|
+
>>> train, test = splitter.split(X, y)
|
|
107
|
+
>>> print(f"Train size: {len(train)}, Test size: {len(test)}")
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, test_size: float = 0.3,
|
|
111
|
+
random_state: Optional[int] = None,
|
|
112
|
+
stratify: bool = True):
|
|
113
|
+
"""
|
|
114
|
+
Initialize SampleSplitter.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
test_size : float, default 0.3
|
|
119
|
+
Proportion for testing.
|
|
120
|
+
random_state : int, optional
|
|
121
|
+
Random seed.
|
|
122
|
+
stratify : bool, default True
|
|
123
|
+
Whether to stratify.
|
|
124
|
+
"""
|
|
125
|
+
self.test_size = test_size
|
|
126
|
+
self.random_state = random_state
|
|
127
|
+
self.stratify = stratify
|
|
128
|
+
self.train_index_ = None
|
|
129
|
+
self.test_index_ = None
|
|
130
|
+
|
|
131
|
+
def split(self, X: Union[pd.DataFrame, np.ndarray],
|
|
132
|
+
y: Union[pd.Series, np.ndarray],
|
|
133
|
+
test_size: Optional[float] = None,
|
|
134
|
+
stratify: Optional[bool] = None) -> Tuple:
|
|
135
|
+
"""
|
|
136
|
+
Split data into train and test sets.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
X : pandas.DataFrame or numpy.ndarray
|
|
141
|
+
Features.
|
|
142
|
+
y : pandas.Series or numpy.ndarray
|
|
143
|
+
Target variable.
|
|
144
|
+
test_size : float, optional
|
|
145
|
+
Override default test size.
|
|
146
|
+
stratify : bool, optional
|
|
147
|
+
Override default stratify setting.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
tuple
|
|
152
|
+
(X_train, X_test, y_train, y_test)
|
|
153
|
+
|
|
154
|
+
Examples
|
|
155
|
+
--------
|
|
156
|
+
>>> X_train, X_test, y_train, y_test = splitter.split(X, y)
|
|
157
|
+
"""
|
|
158
|
+
test_size = test_size if test_size is not None else self.test_size
|
|
159
|
+
stratify = stratify if stratify is not None else self.stratify
|
|
160
|
+
|
|
161
|
+
y_arr = np.array(y)
|
|
162
|
+
stratify_param = y_arr if stratify and len(np.unique(y_arr)) > 1 else None
|
|
163
|
+
|
|
164
|
+
return train_test_split(
|
|
165
|
+
X, y,
|
|
166
|
+
test_size=test_size,
|
|
167
|
+
random_state=self.random_state,
|
|
168
|
+
stratify=stratify_param
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def split_df(self, df: pd.DataFrame, target: str,
|
|
172
|
+
exclude_cols: Optional[List[str]] = None,
|
|
173
|
+
test_size: Optional[float] = None) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
174
|
+
"""
|
|
175
|
+
Split DataFrame while excluding certain columns from split.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
df : pandas.DataFrame
|
|
180
|
+
Input DataFrame.
|
|
181
|
+
target : str
|
|
182
|
+
Target column name.
|
|
183
|
+
exclude_cols : list of str, optional
|
|
184
|
+
Columns to exclude from split.
|
|
185
|
+
test_size : float, optional
|
|
186
|
+
Override default test size.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
tuple
|
|
191
|
+
(train_df, test_df)
|
|
192
|
+
|
|
193
|
+
Examples
|
|
194
|
+
--------
|
|
195
|
+
>>> train_df, test_df = splitter.split_df(df, 'target', exclude_cols=['id', 'date'])
|
|
196
|
+
"""
|
|
197
|
+
test_size = test_size if test_size is not None else self.test_size
|
|
198
|
+
|
|
199
|
+
exclude_cols = exclude_cols or []
|
|
200
|
+
feature_cols = [c for c in df.columns if c not in exclude_cols + [target]]
|
|
201
|
+
|
|
202
|
+
X = df[feature_cols]
|
|
203
|
+
y = df[target]
|
|
204
|
+
|
|
205
|
+
if self.stratify:
|
|
206
|
+
strat = y
|
|
207
|
+
else:
|
|
208
|
+
strat = None
|
|
209
|
+
|
|
210
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
211
|
+
X, y,
|
|
212
|
+
test_size=test_size,
|
|
213
|
+
random_state=self.random_state,
|
|
214
|
+
stratify=strat
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
train_df = X_train.copy()
|
|
218
|
+
train_df[target] = y_train
|
|
219
|
+
|
|
220
|
+
test_df = X_test.copy()
|
|
221
|
+
test_df[target] = y_test
|
|
222
|
+
|
|
223
|
+
return train_df, test_df
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class StratifiedSampler:
|
|
227
|
+
"""
|
|
228
|
+
Stratified sampling with target balance control.
|
|
229
|
+
|
|
230
|
+
This class provides stratified sampling that maintains
|
|
231
|
+
the target distribution while allowing controlled sampling.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
target_rate : float, optional
|
|
236
|
+
Target bad rate in the sample.
|
|
237
|
+
random_state : int, optional
|
|
238
|
+
Random seed.
|
|
239
|
+
|
|
240
|
+
Methods
|
|
241
|
+
-------
|
|
242
|
+
sample(df, target, n_samples=None, sample_frac=None)
|
|
243
|
+
Perform stratified sampling.
|
|
244
|
+
balance(df, target, method='undersample')
|
|
245
|
+
Balance sample by adjusting target distribution.
|
|
246
|
+
|
|
247
|
+
Examples
|
|
248
|
+
--------
|
|
249
|
+
>>> sampler = StratifiedSampler(target_rate=0.15)
|
|
250
|
+
>>> balanced = sampler.balance(df, 'target', method='undersample')
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def __init__(self, target_rate: Optional[float] = None,
|
|
254
|
+
random_state: Optional[int] = None):
|
|
255
|
+
"""
|
|
256
|
+
Initialize StratifiedSampler.
|
|
257
|
+
"""
|
|
258
|
+
self.target_rate = target_rate
|
|
259
|
+
self.random_state = random_state
|
|
260
|
+
self.original_rate_ = None
|
|
261
|
+
|
|
262
|
+
def sample(self, df: pd.DataFrame, target: str,
|
|
263
|
+
n_samples: Optional[int] = None,
|
|
264
|
+
sample_frac: Optional[float] = None) -> pd.DataFrame:
|
|
265
|
+
"""
|
|
266
|
+
Perform stratified sampling.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
df : pandas.DataFrame
|
|
271
|
+
Input data.
|
|
272
|
+
target : str
|
|
273
|
+
Target column name.
|
|
274
|
+
n_samples : int, optional
|
|
275
|
+
Number of samples to draw.
|
|
276
|
+
sample_frac : float, optional
|
|
277
|
+
Fraction of data to sample.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
pandas.DataFrame
|
|
282
|
+
Sampled DataFrame.
|
|
283
|
+
|
|
284
|
+
Examples
|
|
285
|
+
--------
|
|
286
|
+
>>> sampled = sampler.sample(df, 'target', sample_frac=0.5)
|
|
287
|
+
"""
|
|
288
|
+
if sample_frac is not None:
|
|
289
|
+
return df.sample(frac=sample_frac, random_state=self.random_state)
|
|
290
|
+
|
|
291
|
+
if n_samples is not None:
|
|
292
|
+
return df.sample(n=n_samples, random_state=self.random_state)
|
|
293
|
+
|
|
294
|
+
return df.copy()
|
|
295
|
+
|
|
296
|
+
def balance(self, df: pd.DataFrame, target: str,
|
|
297
|
+
method: str = 'undersample') -> pd.DataFrame:
|
|
298
|
+
"""
|
|
299
|
+
Balance sample by adjusting target distribution.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
df : pandas.DataFrame
|
|
304
|
+
Input data.
|
|
305
|
+
target : str
|
|
306
|
+
Target column name.
|
|
307
|
+
method : str, default 'undersample'
|
|
308
|
+
Balancing method: 'undersample', 'oversample', 'smote'.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
pandas.DataFrame
|
|
313
|
+
Balanced DataFrame.
|
|
314
|
+
|
|
315
|
+
Examples
|
|
316
|
+
--------
|
|
317
|
+
>>> balanced = sampler.balance(df, 'target', method='undersample')
|
|
318
|
+
"""
|
|
319
|
+
self.original_rate_ = df[target].mean()
|
|
320
|
+
|
|
321
|
+
if method == 'undersample':
|
|
322
|
+
return self._undersample(df, target)
|
|
323
|
+
elif method == 'oversample':
|
|
324
|
+
return self._oversample(df, target)
|
|
325
|
+
elif method == 'smote':
|
|
326
|
+
return self._smote(df, target)
|
|
327
|
+
else:
|
|
328
|
+
raise ValueError(f"Unknown method: {method}")
|
|
329
|
+
|
|
330
|
+
def _undersample(self, df: pd.DataFrame, target: str) -> pd.DataFrame:
|
|
331
|
+
"""
|
|
332
|
+
Undersample majority class.
|
|
333
|
+
"""
|
|
334
|
+
goods = df[df[target] == 0]
|
|
335
|
+
bads = df[df[target] == 1]
|
|
336
|
+
|
|
337
|
+
if self.target_rate is not None:
|
|
338
|
+
n_bads = len(bads)
|
|
339
|
+
n_goods = int(n_bads * (1 - self.target_rate) / self.target_rate)
|
|
340
|
+
goods = goods.sample(n=min(n_goods, len(goods)), random_state=self.random_state)
|
|
341
|
+
else:
|
|
342
|
+
goods = goods.sample(n=len(bads), random_state=self.random_state)
|
|
343
|
+
|
|
344
|
+
return pd.concat([goods, bads]).sample(frac=1, random_state=self.random_state)
|
|
345
|
+
|
|
346
|
+
def _oversample(self, df: pd.DataFrame, target: str) -> pd.DataFrame:
|
|
347
|
+
"""
|
|
348
|
+
Oversample minority class.
|
|
349
|
+
"""
|
|
350
|
+
goods = df[df[target] == 0]
|
|
351
|
+
bads = df[df[target] == 1]
|
|
352
|
+
|
|
353
|
+
if len(bads) < len(goods):
|
|
354
|
+
if self.target_rate is not None:
|
|
355
|
+
n_goods = len(goods)
|
|
356
|
+
n_bads = int(n_goods * self.target_rate / (1 - self.target_rate))
|
|
357
|
+
bads = bads.sample(n=n_bads, replace=True, random_state=self.random_state)
|
|
358
|
+
else:
|
|
359
|
+
bads = bads.sample(n=len(goods), replace=True, random_state=self.random_state)
|
|
360
|
+
|
|
361
|
+
return pd.concat([goods, bads]).sample(frac=1, random_state=self.random_state)
|
|
362
|
+
|
|
363
|
+
def _smote(self, df: pd.DataFrame, target: str) -> pd.DataFrame:
|
|
364
|
+
"""
|
|
365
|
+
SMOTE oversampling (requires imbalanced-learn).
|
|
366
|
+
"""
|
|
367
|
+
try:
|
|
368
|
+
from imblearn.over_sampling import SMOTE
|
|
369
|
+
except ImportError:
|
|
370
|
+
raise ImportError("imbalanced-learn required for SMOTE. Install with: pip install imbalanced-learn")
|
|
371
|
+
|
|
372
|
+
goods = df[df[target] == 0]
|
|
373
|
+
bads = df[df[target] == 1]
|
|
374
|
+
|
|
375
|
+
if len(bads) < 6:
|
|
376
|
+
return self._oversample(df, target)
|
|
377
|
+
|
|
378
|
+
X_cols = [c for c in df.columns if c != target]
|
|
379
|
+
X = df[X_cols].values
|
|
380
|
+
y = df[target].values
|
|
381
|
+
|
|
382
|
+
smote = SMOTE(random_state=self.random_state)
|
|
383
|
+
X_resampled, y_resampled = smote.fit_resample(X, y)
|
|
384
|
+
|
|
385
|
+
return pd.DataFrame(X_resampled, columns=X_cols).assign(**{target: y_resampled})
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class SampleBalancer:
|
|
389
|
+
"""
|
|
390
|
+
Advanced sample balancing with multiple methods.
|
|
391
|
+
|
|
392
|
+
This class provides various sampling techniques to handle
|
|
393
|
+
class imbalance in credit modeling.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
method : str, default 'random'
|
|
398
|
+
Balancing method.
|
|
399
|
+
target_ratio : float, optional
|
|
400
|
+
Desired minority/majority ratio.
|
|
401
|
+
random_state : int, optional
|
|
402
|
+
Random seed.
|
|
403
|
+
|
|
404
|
+
Methods
|
|
405
|
+
-------
|
|
406
|
+
fit_resample(X, y)
|
|
407
|
+
Resample features and target.
|
|
408
|
+
get_balanced_indices(y)
|
|
409
|
+
Get indices for balanced sampling.
|
|
410
|
+
|
|
411
|
+
Examples
|
|
412
|
+
--------
|
|
413
|
+
>>> balancer = SampleBalancer(method='nearmiss')
|
|
414
|
+
>>> X_bal, y_bal = balancer.fit_resample(X, y)
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
def __init__(self, method: str = 'random',
|
|
418
|
+
target_ratio: Optional[float] = None,
|
|
419
|
+
random_state: Optional[int] = None):
|
|
420
|
+
"""
|
|
421
|
+
Initialize SampleBalancer.
|
|
422
|
+
"""
|
|
423
|
+
self.method = method
|
|
424
|
+
self.target_ratio = target_ratio
|
|
425
|
+
self.random_state = random_state
|
|
426
|
+
|
|
427
|
+
def fit_resample(self, X: Union[pd.DataFrame, np.ndarray],
|
|
428
|
+
y: Union[pd.Series, np.ndarray]) -> Tuple:
|
|
429
|
+
"""
|
|
430
|
+
Resample data to balance classes.
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
----------
|
|
434
|
+
X : pandas.DataFrame or numpy.ndarray
|
|
435
|
+
Features.
|
|
436
|
+
y : pandas.Series or numpy.ndarray
|
|
437
|
+
Target.
|
|
438
|
+
|
|
439
|
+
Returns
|
|
440
|
+
-------
|
|
441
|
+
tuple
|
|
442
|
+
(X_resampled, y_resampled)
|
|
443
|
+
|
|
444
|
+
Examples
|
|
445
|
+
--------
|
|
446
|
+
>>> X_bal, y_bal = balancer.fit_resample(X, y)
|
|
447
|
+
"""
|
|
448
|
+
if self.method == 'random':
|
|
449
|
+
return self._random_undersample(X, y)
|
|
450
|
+
elif self.method == 'nearmiss':
|
|
451
|
+
return self._nearmiss(X, y)
|
|
452
|
+
elif self.method == 'tomek':
|
|
453
|
+
return self._tomek_links(X, y)
|
|
454
|
+
elif self.method == 'enn':
|
|
455
|
+
return self._edited_nn(X, y)
|
|
456
|
+
else:
|
|
457
|
+
raise ValueError(f"Unknown method: {self.method}")
|
|
458
|
+
|
|
459
|
+
def _random_undersample(self, X, y):
|
|
460
|
+
"""
|
|
461
|
+
Random undersampling.
|
|
462
|
+
"""
|
|
463
|
+
goods = y == 0
|
|
464
|
+
bads = y == 1
|
|
465
|
+
|
|
466
|
+
n_bads = goods.sum() if self.target_ratio else bads.sum()
|
|
467
|
+
|
|
468
|
+
if self.target_ratio:
|
|
469
|
+
n_goods = int(n_bads * (1 - self.target_ratio) / self.target_ratio)
|
|
470
|
+
else:
|
|
471
|
+
n_goods = n_bads
|
|
472
|
+
|
|
473
|
+
goods_indices = np.where(goods)[0]
|
|
474
|
+
bads_indices = np.where(bads)[0]
|
|
475
|
+
|
|
476
|
+
np.random.seed(self.random_state)
|
|
477
|
+
goods_sample = np.random.choice(goods_indices, size=n_goods, replace=False)
|
|
478
|
+
|
|
479
|
+
selected = np.concatenate([goods_sample, bads_indices])
|
|
480
|
+
np.random.shuffle(selected)
|
|
481
|
+
|
|
482
|
+
if isinstance(X, pd.DataFrame):
|
|
483
|
+
return X.iloc[selected].copy(), y.iloc[selected].copy()
|
|
484
|
+
return X[selected], y[selected]
|
|
485
|
+
|
|
486
|
+
def _nearmiss(self, X, y):
|
|
487
|
+
"""
|
|
488
|
+
NearMiss undersampling.
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
from imblearn.under_sampling import NearMiss
|
|
492
|
+
except ImportError:
|
|
493
|
+
raise ImportError("imbalanced-learn required. Install with: pip install imbalanced-learn")
|
|
494
|
+
|
|
495
|
+
nm = NearMiss(version=1, random_state=self.random_state)
|
|
496
|
+
return nm.fit_resample(X, y)
|
|
497
|
+
|
|
498
|
+
def _tomek_links(self, X, y):
|
|
499
|
+
"""
|
|
500
|
+
Tomek links cleaning.
|
|
501
|
+
"""
|
|
502
|
+
try:
|
|
503
|
+
from imblearn.under_sampling import TomekLinks
|
|
504
|
+
except ImportError:
|
|
505
|
+
raise ImportError("imbalanced-learn required. Install with: pip install imbalanced-learn")
|
|
506
|
+
|
|
507
|
+
tl = TomekLinks(random_state=self.random_state)
|
|
508
|
+
return tl.fit_resample(X, y)
|
|
509
|
+
|
|
510
|
+
def _edited_nn(self, X, y):
|
|
511
|
+
"""
|
|
512
|
+
Edited Nearest Neighbors cleaning.
|
|
513
|
+
"""
|
|
514
|
+
try:
|
|
515
|
+
from imblearn.under_sampling import EditedNearestNeighbours
|
|
516
|
+
except ImportError:
|
|
517
|
+
raise ImportError("imbalanced-learn required. Install with: pip install imbalanced-learn")
|
|
518
|
+
|
|
519
|
+
enn = EditedNearestNeighbours(random_state=self.random_state)
|
|
520
|
+
return enn.fit_resample(X, y)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Modeling_Tool.Sample.Sample_Split
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2026 Kyle Sun <github.com/Kyle-J-Sun>. All rights reserved.
|
|
5
|
+
# SuperModelingFactory — Licensed under the Business Source License 1.1.
|
|
6
|
+
#
|
|
7
|
+
# This stub describes the public API of a closed-source module compiled to a
|
|
8
|
+
# native extension (.so / .pyd). The original source is not distributed.
|
|
9
|
+
# Production / commercial use requires a separate commercial license.
|
|
10
|
+
#
|
|
11
|
+
# FINGERPRINT: SMF-SAMPLESPLIT-aeab7413
|
|
12
|
+
# (Unique trace marker. Do not remove or alter — used for plagiarism
|
|
13
|
+
# detection across the public internet.)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import numpy as np
|
|
18
|
+
from typing import Union, Optional, List, Dict, Any, Tuple
|
|
19
|
+
from sklearn.model_selection import train_test_split
|
|
20
|
+
from Modeling_Tool.Eval.Model_Eval_Tool import PerformanceEvaluator
|
|
21
|
+
from Modeling_Tool.Core.utils import get_feature_names
|
|
22
|
+
def select_sample_seed(master_df, oot_split_col, model, tgt_name, seed_range = (3000, 3050), ins_prop = 0.7): ...
|
|
23
|
+
|
|
24
|
+
class SampleSplitter:
|
|
25
|
+
def __init__(self, test_size: float = 0.3, random_state: Optional[int] = None, stratify: bool = True): ...
|
|
26
|
+
def split(self, X: Union[pd.DataFrame, np.ndarray], y: Union[pd.Series, np.ndarray], test_size: Optional[float] = None, stratify: Optional[bool] = None) -> Tuple: ...
|
|
27
|
+
def split_df(self, df: pd.DataFrame, target: str, exclude_cols: Optional[List[str]] = None, test_size: Optional[float] = None) -> Tuple[pd.DataFrame, pd.DataFrame]: ...
|
|
28
|
+
|
|
29
|
+
class StratifiedSampler:
|
|
30
|
+
def __init__(self, target_rate: Optional[float] = None, random_state: Optional[int] = None): ...
|
|
31
|
+
def sample(self, df: pd.DataFrame, target: str, n_samples: Optional[int] = None, sample_frac: Optional[float] = None) -> pd.DataFrame: ...
|
|
32
|
+
def balance(self, df: pd.DataFrame, target: str, method: str = 'undersample') -> pd.DataFrame: ...
|
|
33
|
+
def _undersample(self, df: pd.DataFrame, target: str) -> pd.DataFrame: ...
|
|
34
|
+
def _oversample(self, df: pd.DataFrame, target: str) -> pd.DataFrame: ...
|
|
35
|
+
def _smote(self, df: pd.DataFrame, target: str) -> pd.DataFrame: ...
|
|
36
|
+
|
|
37
|
+
class SampleBalancer:
|
|
38
|
+
def __init__(self, method: str = 'random', target_ratio: Optional[float] = None, random_state: Optional[int] = None): ...
|
|
39
|
+
def fit_resample(self, X: Union[pd.DataFrame, np.ndarray], y: Union[pd.Series, np.ndarray]) -> Tuple: ...
|
|
40
|
+
def _random_undersample(self, X, y): ...
|
|
41
|
+
def _nearmiss(self, X, y): ...
|
|
42
|
+
def _tomek_links(self, X, y): ...
|
|
43
|
+
def _edited_nn(self, X, y): ...
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .Distribution_Adaptation import DistributionAdaptation
|
|
2
|
+
|
|
3
|
+
from .Reject_Infer import (
|
|
4
|
+
RejectInferrer,
|
|
5
|
+
SimpleAugmentInferrer,
|
|
6
|
+
HardCutoffInferrer,
|
|
7
|
+
FuzzyAugmentInferrer,
|
|
8
|
+
ParcelingInferrer,
|
|
9
|
+
RejectInferenceFactory,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .Sample_Split import (
|
|
13
|
+
SampleSplitter,
|
|
14
|
+
StratifiedSampler,
|
|
15
|
+
SampleBalancer,
|
|
16
|
+
select_sample_seed,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
# Distribution_Adaptation
|
|
22
|
+
'DistributionAdaptation',
|
|
23
|
+
|
|
24
|
+
# Reject_Infer
|
|
25
|
+
'RejectInferrer', 'SimpleAugmentInferrer', 'HardCutoffInferrer',
|
|
26
|
+
'FuzzyAugmentInferrer', 'ParcelingInferrer', 'RejectInferenceFactory',
|
|
27
|
+
|
|
28
|
+
# Sample_Split
|
|
29
|
+
'SampleSplitter', 'StratifiedSampler', 'SampleBalancer',
|
|
30
|
+
'select_sample_seed',
|
|
31
|
+
]
|