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.
- dt_feature_importance-0.1.0/LICENSE.txt +21 -0
- dt_feature_importance-0.1.0/PKG-INFO +341 -0
- dt_feature_importance-0.1.0/README.md +298 -0
- dt_feature_importance-0.1.0/dt_feature_importance/__init__.py +61 -0
- dt_feature_importance-0.1.0/dt_feature_importance/core.py +465 -0
- dt_feature_importance-0.1.0/dt_feature_importance.egg-info/PKG-INFO +341 -0
- dt_feature_importance-0.1.0/dt_feature_importance.egg-info/SOURCES.txt +11 -0
- dt_feature_importance-0.1.0/dt_feature_importance.egg-info/dependency_links.txt +1 -0
- dt_feature_importance-0.1.0/dt_feature_importance.egg-info/requires.txt +14 -0
- dt_feature_importance-0.1.0/dt_feature_importance.egg-info/top_level.txt +1 -0
- dt_feature_importance-0.1.0/pyproject.toml +91 -0
- dt_feature_importance-0.1.0/setup.cfg +4 -0
- dt_feature_importance-0.1.0/tests/test_core.py +150 -0
|
@@ -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
|
+
[](https://pypi.org/project/dt-feature-importance/)
|
|
47
|
+
[](https://pypi.org/project/dt-feature-importance/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
[](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
|
+
[](https://pypi.org/project/dt-feature-importance/)
|
|
4
|
+
[](https://pypi.org/project/dt-feature-importance/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](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.
|