dt-feature-importance 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vrukshya Org
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,341 @@
1
+ Metadata-Version: 2.4
2
+ Name: dt_feature_importance
3
+ Version: 0.1.0
4
+ Summary: High-performance feature importance, IV, PSI, and monotonicity tools for decision tree models like CHAID
5
+ Author-email: Vrukshya <vrukshyaai@gmail.com>
6
+ Maintainer-email: Vrukshya <vrukshyaai@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/vrukshya/dt_feature_importance
9
+ Project-URL: Documentation, https://github.com/vrukshya/dt_feature_importance#readme
10
+ Project-URL: Repository, https://github.com/vrukshya/dt_feature_importance
11
+ Project-URL: Bug Tracker, https://github.com/vrukshya/dt_feature_importance/issues
12
+ Keywords: decision-tree,chaid,cart,feature-importance,feature-ranking,information-value,iv,psi,population-stability-index,monotonicity,credit-risk,scorecard,binning,pandas,numpy,fintech,machine-learning
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Office/Business :: Financial
25
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE.txt
30
+ Requires-Dist: numpy>=1.20.0
31
+ Requires-Dist: pandas>=1.3.0
32
+ Provides-Extra: optbinning
33
+ Requires-Dist: optbinning>=0.17.0; extra == "optbinning"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
36
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
37
+ Requires-Dist: black>=23.0.0; extra == "dev"
38
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
40
+ Requires-Dist: build; extra == "dev"
41
+ Requires-Dist: twine; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # dt_feature_importance
45
+
46
+ [![PyPI version](https://img.shields.io/pypi/v/dt_feature_importance.svg)](https://pypi.org/project/dt-feature-importance/)
47
+ [![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://pypi.org/project/dt-feature-importance/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
49
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
50
+
51
+ **High-performance feature importance, Information Value (IV), Population Stability Index (PSI), and monotonicity tools for decision tree models like CHAID and credit risk scorecards.**
52
+
53
+ ---
54
+
55
+ ## Overview
56
+
57
+ When developing decision tree models (such as **CHAID**, **CART**, and shallow gradient boosted trees) or regulatory credit scoring scorecards (Basel II/III, IFRS 9), practitioners need fast, reliable diagnostic tools to:
58
+ - Screen and rank candidate features prior to tree splitting.
59
+ - Measure predictive strength using **Information Value (IV)** / Chi-Square.
60
+ - Track distribution drift across development and out-of-time samples using **Population Stability Index (PSI)**.
61
+ - Enforce business and regulatory logic by ensuring **monotonic event rate trends** across bin intervals.
62
+
63
+ `dt_feature_importance` delivers high-performance NumPy/Pandas implementations of these essential utilities with **zero heavy machine-learning dependencies**.
64
+
65
+ ---
66
+
67
+ ## Key Features
68
+
69
+ - ⚡ **Zero-Dependency Single-Split Tree Importance (`lgbm_top_features`)**: Pure NumPy gradient/hessian split-gain ranking. Evaluates numeric and categorical splits, missing value directions, and min-sample/min-event constraints without installing or compiling LightGBM.
70
+ - 📊 **Vectorized Information Value (`iv_from_ids`)**: Ultra-fast IV computation from pre-binned indices using `np.bincount` and Laplace smoothing.
71
+ - 📈 **Population Stability Index (`psi_from_ids`)**: Quantify population distribution shift between reference/baseline and validation datasets.
72
+ - 🔄 **Monotonicity Trend Detection (`monotonic_direction`, `monotonic_direction_from_ids`)**: Identifies whether target event rates across bins are `'increasing'`, `'decreasing'`, or `'non_monotonic'`, supporting both raw arrays and `optbinning` objects.
73
+ - 🏷️ **Automated Feature Categorization (`infer_feature_types`)**: Automatically splits candidate columns into numeric and categorical features for downstream tree binning.
74
+
75
+ ---
76
+
77
+ ## Installation
78
+
79
+ Install the stable release from PyPI:
80
+
81
+ ```bash
82
+ pip install dt_feature_importance
83
+ ```
84
+
85
+ For optional integration with `optbinning` (to extract monotonicity directly from `OptimalBinning` objects):
86
+
87
+ ```bash
88
+ pip install "dt_feature_importance[optbinning]"
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Quickstart Guide
94
+
95
+ ### 1. Feature Importance for Decision Trees (`lgbm_top_features`)
96
+
97
+ Quickly rank hundreds of candidate variables by single-split gain to select optimal features for CHAID or decision tree nodes:
98
+
99
+ ```python
100
+ import numpy as np
101
+ import pandas as pd
102
+ from dt_feature_importance import lgbm_top_features
103
+
104
+ # Sample dataset
105
+ df = pd.DataFrame({
106
+ "bureau_score": [650, 720, 580, 790, 610, 800, 540, 690, 710, 600] * 100,
107
+ "income": [45000, 80000, 32000, 120000, 40000, 110000, 28000, 75000, 85000, 39000] * 100,
108
+ "employment_type": ["Salaried", "Self-Employed", "Salaried", "Salaried", "Other"] * 200,
109
+ "default_flag": [1, 0, 1, 0, 1, 0, 1, 0, 0, 1] * 100
110
+ })
111
+
112
+ importance_df, top_features = lgbm_top_features(
113
+ df=df,
114
+ target="default_flag",
115
+ features=["bureau_score", "income", "employment_type"],
116
+ top_n=10,
117
+ min_bin_size_node_g=20, # Minimum samples per child leaf
118
+ min_bin_n_event=5 # Minimum target events per child leaf
119
+ )
120
+
121
+ print(importance_df)
122
+ # feature importance
123
+ # 0 bureau_score 82.415024
124
+ # 1 income 64.309118
125
+ # 2 employment_type 12.184910
126
+
127
+ print("Selected top features:", top_features)
128
+ ```
129
+
130
+ ---
131
+
132
+ ### 2. Information Value (IV) from Bin IDs (`iv_from_ids`)
133
+
134
+ Calculate Information Value directly from pre-binned categories or quantile bins:
135
+
136
+ ```python
137
+ import numpy as np
138
+ from dt_feature_importance import iv_from_ids
139
+
140
+ # Pre-assigned bin IDs (e.g. from quantile cuts, CHAID leaf nodes, or optbinning)
141
+ bin_ids = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
142
+ y = np.array([1, 1, 0, 1, 0, 0, 0, 0, 0, 1])
143
+
144
+ iv = iv_from_ids(bin_ids=bin_ids, y=y)
145
+ print(f"Information Value: {iv:.4f}")
146
+ ```
147
+
148
+ #### Rule of Thumb for IV Interpretation:
149
+ | Information Value (IV) | Predictive Power |
150
+ | :--- | :--- |
151
+ | `< 0.02` | Unpredictable / Not useful |
152
+ | `0.02 to 0.1` | Weak predictive power |
153
+ | `0.1 to 0.3` | Medium predictive power |
154
+ | `0.3 to 0.5` | Strong predictive power |
155
+ | `> 0.5` | Suspicious / Overfitting check needed |
156
+
157
+ ---
158
+
159
+ ### 3. Population Stability Index (`psi_from_ids`)
160
+
161
+ Detect sample drift between your development (baseline) population and recent/validation data:
162
+
163
+ ```python
164
+ import numpy as np
165
+ from dt_feature_importance import psi_from_ids
166
+
167
+ # Bin allocations for development vs. validation populations
168
+ dev_bins = np.array([0, 0, 1, 1, 2, 2, 2, 3, 3, 3])
169
+ rec_bins = np.array([0, 1, 1, 1, 2, 2, 3, 3, 3, 3])
170
+
171
+ psi = psi_from_ids(dev_ids=dev_bins, rec_ids=rec_bins)
172
+ print(f"Population Stability Index: {psi:.4f}")
173
+ ```
174
+
175
+ #### Rule of Thumb for PSI Interpretation:
176
+ | Population Stability Index (PSI) | Action / Stability |
177
+ | :--- | :--- |
178
+ | `< 0.10` | **Stable**: No significant shift in population distribution |
179
+ | `0.10 to 0.25` | **Moderate Drift**: Slight shift; investigate bins |
180
+ | `> 0.25` | **Significant Drift**: Major population change; recalibration required |
181
+
182
+ ---
183
+
184
+ ### 4. Monotonicity Direction Extraction
185
+
186
+ Validate that the relationship between bin intervals and event rates strictly ascends or descends:
187
+
188
+ #### From Raw Bin IDs:
189
+ ```python
190
+ import numpy as np
191
+ from dt_feature_importance import monotonic_direction_from_ids
192
+
193
+ bin_ids = np.array([0, 0, 1, 1, 2, 2])
194
+ y = np.array([0, 0, 0, 1, 1, 1])
195
+
196
+ direction = monotonic_direction_from_ids(bin_ids=bin_ids, y=y)
197
+ print(direction) # Output: 'increasing'
198
+ ```
199
+
200
+ #### From an `OptimalBinning` Model:
201
+ ```python
202
+ from optbinning import OptimalBinning
203
+ from dt_feature_importance import monotonic_direction
204
+
205
+ optb = OptimalBinning(name="bureau_score", dtype="numerical")
206
+ optb.fit(df["bureau_score"], df["default_flag"])
207
+
208
+ direction = monotonic_direction(optb)
209
+ print("Monotonicity trend:", direction) # Output: 'decreasing'
210
+ ```
211
+
212
+ ---
213
+
214
+ ### 5. Feature Type Categorization (`infer_feature_types`)
215
+
216
+ Separate numeric columns from categorical variables automatically:
217
+
218
+ ```python
219
+ from dt_feature_importance import infer_feature_types
220
+
221
+ numeric_cols, categorical_cols = infer_feature_types(
222
+ df=df,
223
+ features=["bureau_score", "income", "employment_type"]
224
+ )
225
+
226
+ print("Numeric:", numeric_cols) # ['bureau_score', 'income']
227
+ print("Categorical:", categorical_cols) # ['employment_type']
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Decision Tree / CHAID Workflow Integration
233
+
234
+ Here is how `dt_feature_importance` fits into a standard decision tree or scorecard model lifecycle:
235
+
236
+ ```
237
+ Raw Candidate Feature Space (100+ Features)
238
+ │
239
+ ▼
240
+ infer_feature_types(df, features)
241
+ │
242
+ ▼
243
+ lgbm_top_features(df, target, features)
244
+ (Rank features by single-split gain quickly)
245
+ │
246
+ ▼
247
+ Top Candidate Variables Selected
248
+ │
249
+ ▼
250
+ Binning / Tree Discretization
251
+ (e.g., CHAID / Binning)
252
+ │
253
+ ┌──────────────────────┴──────────────────────┐
254
+ ▼ ▼
255
+ iv_from_ids(bin_ids, y) monotonic_direction_from_ids(bin_ids, y)
256
+ (Verify predictive strength) (Verify monotonic risk trend)
257
+ │ │
258
+ └──────────────────────┬──────────────────────┘
259
+ ▼
260
+ psi_from_ids(dev_ids, rec_ids)
261
+ (Confirm out-of-time stability)
262
+ │
263
+ ▼
264
+ Final Regulatory Decision Tree / Scorecard Nodes
265
+ ```
266
+
267
+ ---
268
+
269
+ ## API Reference
270
+
271
+ ### `lgbm_top_features(df, target, features, top_n=20, min_bin_size_node_g=None, min_bin_n_event=None, **kwargs)`
272
+ - **Parameters**:
273
+ - `df` (*pd.DataFrame*): Input dataset.
274
+ - `target` (*str*): Binary target column name (0/1).
275
+ - `features` (*List[str]*): Candidate feature names to evaluate.
276
+ - `top_n` (*int*, default `20`): Number of top features to return.
277
+ - `min_bin_size_node_g` (*float | int*, optional): Minimum samples per child leaf. If float in `(0, 1)`, interpreted as proportion of samples.
278
+ - `min_bin_n_event` (*int*, optional): Minimum target events (1s) required in each child leaf.
279
+ - **Returns**: `(pd.DataFrame, List[str])` - Sorted importance table (`['feature', 'importance']`) and list of top feature names.
280
+
281
+ ### `iv_from_ids(bin_ids, y, eps=1e-9)`
282
+ - **Parameters**:
283
+ - `bin_ids` (*np.ndarray*): 1D array of bin identifiers.
284
+ - `y` (*np.ndarray*): Binary target values (0 or 1).
285
+ - `eps` (*float*, default `1e-9`): Smoothing parameter.
286
+ - **Returns**: `float` - Information Value (IV), or `np.nan` if invalid.
287
+ - *Alias*: `chi_square_from_ids` is available as a backward-compatible alias.
288
+
289
+ ### `psi_from_ids(dev_ids, rec_ids, eps=1e-8)`
290
+ - **Parameters**:
291
+ - `dev_ids` (*np.ndarray*): Bin indices from development/reference population.
292
+ - `rec_ids` (*np.ndarray*): Bin indices from validation/recent population.
293
+ - `eps` (*float*, default `1e-8`): Constant to avoid division/log of zero.
294
+ - **Returns**: `float` - Population Stability Index.
295
+
296
+ ### `monotonic_direction_from_ids(bin_ids, y, missing_id=-1, special_id=-2)`
297
+ - **Parameters**:
298
+ - `bin_ids` (*np.ndarray*): Array of bin indices.
299
+ - `y` (*np.ndarray*): Binary target array.
300
+ - `missing_id` (*int*, default `-1`): Bin index for missing values (excluded from monotonicity check).
301
+ - `special_id` (*int*, default `-2`): Bin index for special values (excluded from monotonicity check).
302
+ - **Returns**: `Optional[str]` - `'increasing'`, `'decreasing'`, `'non_monotonic'`, or `None`.
303
+
304
+ ### `monotonic_direction(optb)`
305
+ - **Parameters**:
306
+ - `optb` (*Any*): Fitted `OptimalBinning` instance with a `binning_table` attribute.
307
+ - **Returns**: `Optional[str]` - `'increasing'`, `'decreasing'`, or `None`.
308
+
309
+ ### `infer_feature_types(df, features)`
310
+ - **Parameters**:
311
+ - `df` (*pd.DataFrame*): Input DataFrame.
312
+ - `features` (*List[str]*): List of column names.
313
+ - **Returns**: `Tuple[List[str], List[str]]` - `(numeric_features, categorical_features)`.
314
+
315
+ ---
316
+
317
+ ## Contributing & Development
318
+
319
+ To contribute or run tests locally:
320
+
321
+ 1. Clone the repository:
322
+ ```bash
323
+ git clone https://github.com/vrukshya/dt_feature_importance.git
324
+ cd dt_feature_importance
325
+ ```
326
+
327
+ 2. Install in editable mode with development dependencies:
328
+ ```bash
329
+ pip install -e ".[dev]"
330
+ ```
331
+
332
+ 3. Run the test suite:
333
+ ```bash
334
+ pytest tests/
335
+ ```
336
+
337
+ ---
338
+
339
+ ## License
340
+
341
+ This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
@@ -0,0 +1,298 @@
1
+ # dt_feature_importance
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/dt_feature_importance.svg)](https://pypi.org/project/dt-feature-importance/)
4
+ [![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://pypi.org/project/dt-feature-importance/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
7
+
8
+ **High-performance feature importance, Information Value (IV), Population Stability Index (PSI), and monotonicity tools for decision tree models like CHAID and credit risk scorecards.**
9
+
10
+ ---
11
+
12
+ ## Overview
13
+
14
+ When developing decision tree models (such as **CHAID**, **CART**, and shallow gradient boosted trees) or regulatory credit scoring scorecards (Basel II/III, IFRS 9), practitioners need fast, reliable diagnostic tools to:
15
+ - Screen and rank candidate features prior to tree splitting.
16
+ - Measure predictive strength using **Information Value (IV)** / Chi-Square.
17
+ - Track distribution drift across development and out-of-time samples using **Population Stability Index (PSI)**.
18
+ - Enforce business and regulatory logic by ensuring **monotonic event rate trends** across bin intervals.
19
+
20
+ `dt_feature_importance` delivers high-performance NumPy/Pandas implementations of these essential utilities with **zero heavy machine-learning dependencies**.
21
+
22
+ ---
23
+
24
+ ## Key Features
25
+
26
+ - ⚡ **Zero-Dependency Single-Split Tree Importance (`lgbm_top_features`)**: Pure NumPy gradient/hessian split-gain ranking. Evaluates numeric and categorical splits, missing value directions, and min-sample/min-event constraints without installing or compiling LightGBM.
27
+ - 📊 **Vectorized Information Value (`iv_from_ids`)**: Ultra-fast IV computation from pre-binned indices using `np.bincount` and Laplace smoothing.
28
+ - 📈 **Population Stability Index (`psi_from_ids`)**: Quantify population distribution shift between reference/baseline and validation datasets.
29
+ - 🔄 **Monotonicity Trend Detection (`monotonic_direction`, `monotonic_direction_from_ids`)**: Identifies whether target event rates across bins are `'increasing'`, `'decreasing'`, or `'non_monotonic'`, supporting both raw arrays and `optbinning` objects.
30
+ - 🏷️ **Automated Feature Categorization (`infer_feature_types`)**: Automatically splits candidate columns into numeric and categorical features for downstream tree binning.
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ Install the stable release from PyPI:
37
+
38
+ ```bash
39
+ pip install dt_feature_importance
40
+ ```
41
+
42
+ For optional integration with `optbinning` (to extract monotonicity directly from `OptimalBinning` objects):
43
+
44
+ ```bash
45
+ pip install "dt_feature_importance[optbinning]"
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quickstart Guide
51
+
52
+ ### 1. Feature Importance for Decision Trees (`lgbm_top_features`)
53
+
54
+ Quickly rank hundreds of candidate variables by single-split gain to select optimal features for CHAID or decision tree nodes:
55
+
56
+ ```python
57
+ import numpy as np
58
+ import pandas as pd
59
+ from dt_feature_importance import lgbm_top_features
60
+
61
+ # Sample dataset
62
+ df = pd.DataFrame({
63
+ "bureau_score": [650, 720, 580, 790, 610, 800, 540, 690, 710, 600] * 100,
64
+ "income": [45000, 80000, 32000, 120000, 40000, 110000, 28000, 75000, 85000, 39000] * 100,
65
+ "employment_type": ["Salaried", "Self-Employed", "Salaried", "Salaried", "Other"] * 200,
66
+ "default_flag": [1, 0, 1, 0, 1, 0, 1, 0, 0, 1] * 100
67
+ })
68
+
69
+ importance_df, top_features = lgbm_top_features(
70
+ df=df,
71
+ target="default_flag",
72
+ features=["bureau_score", "income", "employment_type"],
73
+ top_n=10,
74
+ min_bin_size_node_g=20, # Minimum samples per child leaf
75
+ min_bin_n_event=5 # Minimum target events per child leaf
76
+ )
77
+
78
+ print(importance_df)
79
+ # feature importance
80
+ # 0 bureau_score 82.415024
81
+ # 1 income 64.309118
82
+ # 2 employment_type 12.184910
83
+
84
+ print("Selected top features:", top_features)
85
+ ```
86
+
87
+ ---
88
+
89
+ ### 2. Information Value (IV) from Bin IDs (`iv_from_ids`)
90
+
91
+ Calculate Information Value directly from pre-binned categories or quantile bins:
92
+
93
+ ```python
94
+ import numpy as np
95
+ from dt_feature_importance import iv_from_ids
96
+
97
+ # Pre-assigned bin IDs (e.g. from quantile cuts, CHAID leaf nodes, or optbinning)
98
+ bin_ids = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
99
+ y = np.array([1, 1, 0, 1, 0, 0, 0, 0, 0, 1])
100
+
101
+ iv = iv_from_ids(bin_ids=bin_ids, y=y)
102
+ print(f"Information Value: {iv:.4f}")
103
+ ```
104
+
105
+ #### Rule of Thumb for IV Interpretation:
106
+ | Information Value (IV) | Predictive Power |
107
+ | :--- | :--- |
108
+ | `< 0.02` | Unpredictable / Not useful |
109
+ | `0.02 to 0.1` | Weak predictive power |
110
+ | `0.1 to 0.3` | Medium predictive power |
111
+ | `0.3 to 0.5` | Strong predictive power |
112
+ | `> 0.5` | Suspicious / Overfitting check needed |
113
+
114
+ ---
115
+
116
+ ### 3. Population Stability Index (`psi_from_ids`)
117
+
118
+ Detect sample drift between your development (baseline) population and recent/validation data:
119
+
120
+ ```python
121
+ import numpy as np
122
+ from dt_feature_importance import psi_from_ids
123
+
124
+ # Bin allocations for development vs. validation populations
125
+ dev_bins = np.array([0, 0, 1, 1, 2, 2, 2, 3, 3, 3])
126
+ rec_bins = np.array([0, 1, 1, 1, 2, 2, 3, 3, 3, 3])
127
+
128
+ psi = psi_from_ids(dev_ids=dev_bins, rec_ids=rec_bins)
129
+ print(f"Population Stability Index: {psi:.4f}")
130
+ ```
131
+
132
+ #### Rule of Thumb for PSI Interpretation:
133
+ | Population Stability Index (PSI) | Action / Stability |
134
+ | :--- | :--- |
135
+ | `< 0.10` | **Stable**: No significant shift in population distribution |
136
+ | `0.10 to 0.25` | **Moderate Drift**: Slight shift; investigate bins |
137
+ | `> 0.25` | **Significant Drift**: Major population change; recalibration required |
138
+
139
+ ---
140
+
141
+ ### 4. Monotonicity Direction Extraction
142
+
143
+ Validate that the relationship between bin intervals and event rates strictly ascends or descends:
144
+
145
+ #### From Raw Bin IDs:
146
+ ```python
147
+ import numpy as np
148
+ from dt_feature_importance import monotonic_direction_from_ids
149
+
150
+ bin_ids = np.array([0, 0, 1, 1, 2, 2])
151
+ y = np.array([0, 0, 0, 1, 1, 1])
152
+
153
+ direction = monotonic_direction_from_ids(bin_ids=bin_ids, y=y)
154
+ print(direction) # Output: 'increasing'
155
+ ```
156
+
157
+ #### From an `OptimalBinning` Model:
158
+ ```python
159
+ from optbinning import OptimalBinning
160
+ from dt_feature_importance import monotonic_direction
161
+
162
+ optb = OptimalBinning(name="bureau_score", dtype="numerical")
163
+ optb.fit(df["bureau_score"], df["default_flag"])
164
+
165
+ direction = monotonic_direction(optb)
166
+ print("Monotonicity trend:", direction) # Output: 'decreasing'
167
+ ```
168
+
169
+ ---
170
+
171
+ ### 5. Feature Type Categorization (`infer_feature_types`)
172
+
173
+ Separate numeric columns from categorical variables automatically:
174
+
175
+ ```python
176
+ from dt_feature_importance import infer_feature_types
177
+
178
+ numeric_cols, categorical_cols = infer_feature_types(
179
+ df=df,
180
+ features=["bureau_score", "income", "employment_type"]
181
+ )
182
+
183
+ print("Numeric:", numeric_cols) # ['bureau_score', 'income']
184
+ print("Categorical:", categorical_cols) # ['employment_type']
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Decision Tree / CHAID Workflow Integration
190
+
191
+ Here is how `dt_feature_importance` fits into a standard decision tree or scorecard model lifecycle:
192
+
193
+ ```
194
+ Raw Candidate Feature Space (100+ Features)
195
+ │
196
+ ▼
197
+ infer_feature_types(df, features)
198
+ │
199
+ ▼
200
+ lgbm_top_features(df, target, features)
201
+ (Rank features by single-split gain quickly)
202
+ │
203
+ ▼
204
+ Top Candidate Variables Selected
205
+ │
206
+ ▼
207
+ Binning / Tree Discretization
208
+ (e.g., CHAID / Binning)
209
+ │
210
+ ┌──────────────────────┴──────────────────────┐
211
+ ▼ ▼
212
+ iv_from_ids(bin_ids, y) monotonic_direction_from_ids(bin_ids, y)
213
+ (Verify predictive strength) (Verify monotonic risk trend)
214
+ │ │
215
+ └──────────────────────┬──────────────────────┘
216
+ ▼
217
+ psi_from_ids(dev_ids, rec_ids)
218
+ (Confirm out-of-time stability)
219
+ │
220
+ ▼
221
+ Final Regulatory Decision Tree / Scorecard Nodes
222
+ ```
223
+
224
+ ---
225
+
226
+ ## API Reference
227
+
228
+ ### `lgbm_top_features(df, target, features, top_n=20, min_bin_size_node_g=None, min_bin_n_event=None, **kwargs)`
229
+ - **Parameters**:
230
+ - `df` (*pd.DataFrame*): Input dataset.
231
+ - `target` (*str*): Binary target column name (0/1).
232
+ - `features` (*List[str]*): Candidate feature names to evaluate.
233
+ - `top_n` (*int*, default `20`): Number of top features to return.
234
+ - `min_bin_size_node_g` (*float | int*, optional): Minimum samples per child leaf. If float in `(0, 1)`, interpreted as proportion of samples.
235
+ - `min_bin_n_event` (*int*, optional): Minimum target events (1s) required in each child leaf.
236
+ - **Returns**: `(pd.DataFrame, List[str])` - Sorted importance table (`['feature', 'importance']`) and list of top feature names.
237
+
238
+ ### `iv_from_ids(bin_ids, y, eps=1e-9)`
239
+ - **Parameters**:
240
+ - `bin_ids` (*np.ndarray*): 1D array of bin identifiers.
241
+ - `y` (*np.ndarray*): Binary target values (0 or 1).
242
+ - `eps` (*float*, default `1e-9`): Smoothing parameter.
243
+ - **Returns**: `float` - Information Value (IV), or `np.nan` if invalid.
244
+ - *Alias*: `chi_square_from_ids` is available as a backward-compatible alias.
245
+
246
+ ### `psi_from_ids(dev_ids, rec_ids, eps=1e-8)`
247
+ - **Parameters**:
248
+ - `dev_ids` (*np.ndarray*): Bin indices from development/reference population.
249
+ - `rec_ids` (*np.ndarray*): Bin indices from validation/recent population.
250
+ - `eps` (*float*, default `1e-8`): Constant to avoid division/log of zero.
251
+ - **Returns**: `float` - Population Stability Index.
252
+
253
+ ### `monotonic_direction_from_ids(bin_ids, y, missing_id=-1, special_id=-2)`
254
+ - **Parameters**:
255
+ - `bin_ids` (*np.ndarray*): Array of bin indices.
256
+ - `y` (*np.ndarray*): Binary target array.
257
+ - `missing_id` (*int*, default `-1`): Bin index for missing values (excluded from monotonicity check).
258
+ - `special_id` (*int*, default `-2`): Bin index for special values (excluded from monotonicity check).
259
+ - **Returns**: `Optional[str]` - `'increasing'`, `'decreasing'`, `'non_monotonic'`, or `None`.
260
+
261
+ ### `monotonic_direction(optb)`
262
+ - **Parameters**:
263
+ - `optb` (*Any*): Fitted `OptimalBinning` instance with a `binning_table` attribute.
264
+ - **Returns**: `Optional[str]` - `'increasing'`, `'decreasing'`, or `None`.
265
+
266
+ ### `infer_feature_types(df, features)`
267
+ - **Parameters**:
268
+ - `df` (*pd.DataFrame*): Input DataFrame.
269
+ - `features` (*List[str]*): List of column names.
270
+ - **Returns**: `Tuple[List[str], List[str]]` - `(numeric_features, categorical_features)`.
271
+
272
+ ---
273
+
274
+ ## Contributing & Development
275
+
276
+ To contribute or run tests locally:
277
+
278
+ 1. Clone the repository:
279
+ ```bash
280
+ git clone https://github.com/vrukshya/dt_feature_importance.git
281
+ cd dt_feature_importance
282
+ ```
283
+
284
+ 2. Install in editable mode with development dependencies:
285
+ ```bash
286
+ pip install -e ".[dev]"
287
+ ```
288
+
289
+ 3. Run the test suite:
290
+ ```bash
291
+ pytest tests/
292
+ ```
293
+
294
+ ---
295
+
296
+ ## License
297
+
298
+ This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.