tide-binning 0.6.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,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .hypothesis/
10
+ .coverage
11
+ coverage.xml
12
+ htmlcov/
13
+ site/
14
+ .ipynb_checkpoints/
15
+ plots_review/
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0 (2026-10-16)
4
+
5
+ Complete rewrite. The greedy algorithm and its rules are unchanged: on the scenarios in
6
+ `tests/golden`, the bins are identical to v0.5.0.
7
+
8
+ ### New
9
+ - scikit-learn estimator: `get_params` / `set_params` / `clone`, `Pipeline`,
10
+ `cross_validate(params={"tide__periods": ...})`, metadata routing,
11
+ `get_feature_names_out`.
12
+ - NaN / None / pd.NA / NaT are supported in features (bins key `None`).
13
+ - `cat_values` and `missing_values` turn numeric codes such as `-999` into categories or
14
+ missing values, globally or per feature.
15
+ - `trend` can be forced (`"pos"` / `"neg"`), globally or per feature.
16
+ - `max_unique` / `n_quantiles` (defaults 1000 / 200): features with more than 1000 unique values
17
+ are split at 200 quantiles instead of every unique value. This is faster, and with many
18
+ periods the exact search often finds no split at all. Pass a large `max_unique` for v0.5 behaviour.
19
+ - `user_bins`: apply your own bins (any shape), with a coverage warning.
20
+ - `summary_` table; a warning when a feature collapses to one continuous bin.
21
+ - `n_jobs` (parallel over features) and `verbose` (progress bar).
22
+ - `plot()` returns the figures and accepts `show=False`.
23
+ - Periods may be any sortable type and need not be sorted.
24
+
25
+ ### Fixed
26
+ - `min_er_delta` is now also guaranteed for the last bin (it is merged otherwise).
27
+ - `min_er_delta` no longer applies to the first bin (v0.5 required its event rate ≥ delta).
28
+ - With `worst_bin` / `best_bin`, no split is made between the continuous part and the
29
+ attached missing values.
30
+ - `cat_strategy="tide"` now guarantees all constraints: continuous bins merge only with
31
+ adjacent ones (their intervals become one), and their monotonicity is re-checked.
32
+ The missing bin takes part in the merge.
33
+ - `transform` no longer mistakes values equal to a WoE for unknown values.
34
+ - `stats_per` no longer duplicates rows on refit.
35
+ - `"col_min"` / `"col_max"` bounds now use the next representable float instead of
36
+ `± 1e-8`, which failed for large values.
37
+
38
+ ### Changed (breaking)
39
+ - WoE uses a symmetric epsilon, `ln((%nonevents + 1e-8) / (%events + 1e-8))`: a bin without
40
+ non-events no longer gets WoE = -inf and IV = inf. Other bins are unchanged up to ~1e-8.
41
+ - `fit(X, y, per)` → `fit(X, y, periods)`; `reset` and `return_bins` were removed.
42
+ - `handle_unknown` moved from `transform()` into the constructor (`set_params` changes it).
43
+ - `transform` requires the same columns as `fit`; `xnames` and ndarray input were removed.
44
+ - Attributes: `exog_bins` → `bins_`, `exog_woes` → `woes_`, `stats` → `stats_`,
45
+ `stats_per` → `stats_per_`; the stats tables have a new `bin_idx` column.
46
+ - `missing_categories` → `missing_values`; `n_prebins` → `n_trend_bins`.
47
+ - Bin labels (stats tables and plots) show integer bounds without decimals (`(18,25]`) and
48
+ other bounds with exactly 4 decimals (`(0.3400,1.1130]`). `bins_` keeps the full values.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Leonid Garin
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,194 @@
1
+ Metadata-Version: 2.5
2
+ Name: tide-binning
3
+ Version: 0.6.0
4
+ Summary: TIDE - Time Invariant Discretization Engine: time-stable binning for credit scorecards
5
+ Project-URL: Homepage, https://github.com/leonidgarin/TIDE
6
+ Project-URL: Issues, https://github.com/leonidgarin/TIDE/issues
7
+ Author-email: Leonid Garin <leonidgarin97@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: binning,credit risk,information value,psi,scorecard,weight of evidence,woe
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: joblib>=1.3
24
+ Requires-Dist: matplotlib>=3.7
25
+ Requires-Dist: numpy>=1.23
26
+ Requires-Dist: pandas>=2.0
27
+ Requires-Dist: scikit-learn>=1.4
28
+ Requires-Dist: scipy>=1.9
29
+ Requires-Dist: tqdm>=4.60
30
+ Provides-Extra: dev
31
+ Requires-Dist: build>=1.0; extra == 'dev'
32
+ Requires-Dist: hypothesis>=6.80; extra == 'dev'
33
+ Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
34
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'dev'
35
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4; extra == 'dev'
37
+ Requires-Dist: pytest>=7; extra == 'dev'
38
+ Requires-Dist: ruff>=0.6; extra == 'dev'
39
+ Requires-Dist: twine>=5; extra == 'dev'
40
+ Provides-Extra: docs
41
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
42
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
43
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
44
+ Provides-Extra: test
45
+ Requires-Dist: hypothesis>=6.80; extra == 'test'
46
+ Requires-Dist: pytest-cov>=4; extra == 'test'
47
+ Requires-Dist: pytest>=7; extra == 'test'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # TIDE — Time Invariant Discretization Engine
51
+
52
+ [![CI](https://github.com/leonidgarin/TIDE/actions/workflows/ci.yml/badge.svg)](https://github.com/leonidgarin/TIDE/actions/workflows/ci.yml)
53
+ [![PyPI](https://img.shields.io/pypi/v/tide-binning.svg)](https://pypi.org/project/tide-binning/)
54
+ [![Python](https://img.shields.io/pypi/pyversions/tide-binning.svg)](https://pypi.org/project/tide-binning/)
55
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
56
+
57
+ TIDE bins features for credit scorecards so that the bins are **stable over time**.
58
+ In every period of the training data, as hard constraints:
59
+
60
+ - the event rate is **monotonic** over the bins, in the **same direction** in all periods,
61
+ so WoE / event-rate curves of different bins never cross;
62
+ - adjacent bins differ in event rate by at least `min_er_delta`;
63
+ - every bin holds at least `min_sample_rate` of the period's rows and at least
64
+ `min_class_obs` events and non-events.
65
+
66
+ It works on **mixed-type** columns, e.g. `age = [18, 44, np.nan, "special_category", -999]`,
67
+ and encodes them with Weight of Evidence. It is a scikit-learn transformer.
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install tide-binning
73
+ ```
74
+
75
+ Requires Python ≥ 3.10. All dependencies (numpy, pandas, scipy, scikit-learn, matplotlib,
76
+ joblib, tqdm) ship prebuilt wheels for Windows, macOS and Linux.
77
+
78
+ ## Quick start
79
+
80
+ ```python
81
+ import pandas as pd
82
+ from sklearn.linear_model import LogisticRegression
83
+ from sklearn.metrics import roc_auc_score
84
+ from tide import TIDE
85
+
86
+ X = pd.DataFrame(...) # features, may be mixed-type
87
+ y = pd.Series(...) # binary target, 0/1
88
+ periods = pd.Series(...) # e.g. application month: str, datetime, Period, int, ...
89
+
90
+ binner = TIDE(cat_values=[-999]) # -999 is a category, not a number
91
+ X_woe = binner.fit_transform(X, y, periods=periods)
92
+
93
+ lr = LogisticRegression().fit(X_woe, y)
94
+ print(roc_auc_score(y, lr.predict_proba(X_woe)[:, 1]))
95
+ ```
96
+
97
+ ## Results
98
+
99
+ ```python
100
+ binner.summary_ # one row per feature: trend, n_bins, IV, max PSI, warnings
101
+ binner.stats_ # per feature and bin: n_obs, event rate, WoE, IV
102
+ binner.stats_per_ # per period, feature and bin: n_obs, event rate, PSI
103
+ binner.bins_ # fitted bins (format below)
104
+ binner.woes_ # {feature: {bin_idx: WoE}}
105
+ binner.plot(["age"]) # bin sizes and event rates, event rate over time, PSI over time
106
+ ```
107
+
108
+ ## Bins format
109
+
110
+ ```python
111
+ {"trend": "pos",
112
+ "bins": {0: {(-np.inf, 25.0): "cont", "special_category": "cat"},
113
+ 1: {(25.0, 40.0): "cont"},
114
+ 2: {(40.0, np.inf): "cont"},
115
+ 3: {None: "missing"}}} # None = NaN / None / pd.NA / NaT
116
+ ```
117
+
118
+ Intervals are `(a, b]` for a positive trend and `[a, b)` for a negative trend, so a value
119
+ that is unseen in training and lies between two borders always goes to the bin with the
120
+ **higher** event rate.
121
+
122
+ ## Handling special values
123
+
124
+ | You have | Use |
125
+ |---|---|
126
+ | NaN, None, pd.NA, NaT | missing automatically |
127
+ | strings, bools | categories automatically |
128
+ | numeric codes such as `-999` that mean "no data" | `missing_values=[-999]` |
129
+ | numeric codes that are a category of their own | `cat_values=[-999]` |
130
+ | different codes per feature | `{"age": [-1], "income": [-999]}` |
131
+
132
+ `missing_strategy` (`"separate_bin"`, `"worst_bin"`, `"best_bin"`) and `cat_strategy`
133
+ (`"separate_bin"`, `"chi-squared"`, `"tide"`) control how they are binned. With
134
+ `cat_strategy="tide"`, all bins — continuous, categorical and missing — are merged until the
135
+ stability constraints hold for all of them.
136
+
137
+ ## Your own bins
138
+
139
+ ```python
140
+ binner = TIDE(user_bins={"age": {"trend": "pos", "bins": {
141
+ 0: {(-np.inf, 25.0): "cont"},
142
+ 1: {(25.0, 60.0): "cont"},
143
+ 2: {(60.0, np.inf): "cont", None: "missing"},
144
+ }}})
145
+ ```
146
+
147
+ User bins are applied as given (U-shapes are fine); a warning lists training values that
148
+ fall in no bin.
149
+
150
+ ## In production
151
+
152
+ ```python
153
+ import pickle
154
+ binner = pickle.load(open("binner.pkl", "rb"))
155
+ binner.set_params(handle_unknown="worst") # change how unseen values are treated, no refit
156
+ X_woe = binner.transform(X_new)
157
+ ```
158
+
159
+ `handle_unknown`: `"keep"` (leave the raw value), `"worst"` / `"best"` (lowest / highest
160
+ WoE), `"missing_worst"` / `"missing_best"` (WoE of the missing bin, else lowest / highest).
161
+
162
+ ## scikit-learn
163
+
164
+ ```python
165
+ from sklearn.pipeline import Pipeline
166
+ from sklearn.model_selection import cross_validate
167
+
168
+ pipe = Pipeline([("tide", TIDE()), ("lr", LogisticRegression())])
169
+ pipe.fit(X, y, tide__periods=periods)
170
+ cross_validate(pipe, X, y, params={"tide__periods": periods}, scoring="roc_auc")
171
+ ```
172
+
173
+ `get_params` / `set_params` / `clone`, `get_feature_names_out` and metadata routing
174
+ (`TIDE().set_fit_request(periods=True)`) work as usual.
175
+
176
+ ## Performance
177
+
178
+ `fit` works on per-period counts of the sorted unique values, so after one sort per
179
+ feature its cost no longer depends on the number of rows. `n_jobs=-1` fits features in
180
+ parallel. Measured on a 2-core laptop, 5,000,000 rows and 24 periods, per feature:
181
+
182
+ | feature | fit | transform |
183
+ |---|---|---|
184
+ | integer, 100 unique values | ~1 s | ~0.5 s |
185
+ | float, 1,000 unique | ~1 s | ~0.5 s |
186
+ | float, all unique (undersampled to 200 quantiles) | ~5 s | ~0.5 s |
187
+ | float, 530k unique (exact search, `max_unique=10**6`) | ~5 s | ~0.5 s |
188
+ | mixed object column (numbers, strings, NaN) | ~3 s | ~2 s |
189
+
190
+ Run `python benchmarks/bench_fit.py` to measure on your machine.
191
+
192
+ ## License
193
+
194
+ MIT © Leonid Garin
@@ -0,0 +1,145 @@
1
+ # TIDE — Time Invariant Discretization Engine
2
+
3
+ [![CI](https://github.com/leonidgarin/TIDE/actions/workflows/ci.yml/badge.svg)](https://github.com/leonidgarin/TIDE/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/tide-binning.svg)](https://pypi.org/project/tide-binning/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/tide-binning.svg)](https://pypi.org/project/tide-binning/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ TIDE bins features for credit scorecards so that the bins are **stable over time**.
9
+ In every period of the training data, as hard constraints:
10
+
11
+ - the event rate is **monotonic** over the bins, in the **same direction** in all periods,
12
+ so WoE / event-rate curves of different bins never cross;
13
+ - adjacent bins differ in event rate by at least `min_er_delta`;
14
+ - every bin holds at least `min_sample_rate` of the period's rows and at least
15
+ `min_class_obs` events and non-events.
16
+
17
+ It works on **mixed-type** columns, e.g. `age = [18, 44, np.nan, "special_category", -999]`,
18
+ and encodes them with Weight of Evidence. It is a scikit-learn transformer.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install tide-binning
24
+ ```
25
+
26
+ Requires Python ≥ 3.10. All dependencies (numpy, pandas, scipy, scikit-learn, matplotlib,
27
+ joblib, tqdm) ship prebuilt wheels for Windows, macOS and Linux.
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ import pandas as pd
33
+ from sklearn.linear_model import LogisticRegression
34
+ from sklearn.metrics import roc_auc_score
35
+ from tide import TIDE
36
+
37
+ X = pd.DataFrame(...) # features, may be mixed-type
38
+ y = pd.Series(...) # binary target, 0/1
39
+ periods = pd.Series(...) # e.g. application month: str, datetime, Period, int, ...
40
+
41
+ binner = TIDE(cat_values=[-999]) # -999 is a category, not a number
42
+ X_woe = binner.fit_transform(X, y, periods=periods)
43
+
44
+ lr = LogisticRegression().fit(X_woe, y)
45
+ print(roc_auc_score(y, lr.predict_proba(X_woe)[:, 1]))
46
+ ```
47
+
48
+ ## Results
49
+
50
+ ```python
51
+ binner.summary_ # one row per feature: trend, n_bins, IV, max PSI, warnings
52
+ binner.stats_ # per feature and bin: n_obs, event rate, WoE, IV
53
+ binner.stats_per_ # per period, feature and bin: n_obs, event rate, PSI
54
+ binner.bins_ # fitted bins (format below)
55
+ binner.woes_ # {feature: {bin_idx: WoE}}
56
+ binner.plot(["age"]) # bin sizes and event rates, event rate over time, PSI over time
57
+ ```
58
+
59
+ ## Bins format
60
+
61
+ ```python
62
+ {"trend": "pos",
63
+ "bins": {0: {(-np.inf, 25.0): "cont", "special_category": "cat"},
64
+ 1: {(25.0, 40.0): "cont"},
65
+ 2: {(40.0, np.inf): "cont"},
66
+ 3: {None: "missing"}}} # None = NaN / None / pd.NA / NaT
67
+ ```
68
+
69
+ Intervals are `(a, b]` for a positive trend and `[a, b)` for a negative trend, so a value
70
+ that is unseen in training and lies between two borders always goes to the bin with the
71
+ **higher** event rate.
72
+
73
+ ## Handling special values
74
+
75
+ | You have | Use |
76
+ |---|---|
77
+ | NaN, None, pd.NA, NaT | missing automatically |
78
+ | strings, bools | categories automatically |
79
+ | numeric codes such as `-999` that mean "no data" | `missing_values=[-999]` |
80
+ | numeric codes that are a category of their own | `cat_values=[-999]` |
81
+ | different codes per feature | `{"age": [-1], "income": [-999]}` |
82
+
83
+ `missing_strategy` (`"separate_bin"`, `"worst_bin"`, `"best_bin"`) and `cat_strategy`
84
+ (`"separate_bin"`, `"chi-squared"`, `"tide"`) control how they are binned. With
85
+ `cat_strategy="tide"`, all bins — continuous, categorical and missing — are merged until the
86
+ stability constraints hold for all of them.
87
+
88
+ ## Your own bins
89
+
90
+ ```python
91
+ binner = TIDE(user_bins={"age": {"trend": "pos", "bins": {
92
+ 0: {(-np.inf, 25.0): "cont"},
93
+ 1: {(25.0, 60.0): "cont"},
94
+ 2: {(60.0, np.inf): "cont", None: "missing"},
95
+ }}})
96
+ ```
97
+
98
+ User bins are applied as given (U-shapes are fine); a warning lists training values that
99
+ fall in no bin.
100
+
101
+ ## In production
102
+
103
+ ```python
104
+ import pickle
105
+ binner = pickle.load(open("binner.pkl", "rb"))
106
+ binner.set_params(handle_unknown="worst") # change how unseen values are treated, no refit
107
+ X_woe = binner.transform(X_new)
108
+ ```
109
+
110
+ `handle_unknown`: `"keep"` (leave the raw value), `"worst"` / `"best"` (lowest / highest
111
+ WoE), `"missing_worst"` / `"missing_best"` (WoE of the missing bin, else lowest / highest).
112
+
113
+ ## scikit-learn
114
+
115
+ ```python
116
+ from sklearn.pipeline import Pipeline
117
+ from sklearn.model_selection import cross_validate
118
+
119
+ pipe = Pipeline([("tide", TIDE()), ("lr", LogisticRegression())])
120
+ pipe.fit(X, y, tide__periods=periods)
121
+ cross_validate(pipe, X, y, params={"tide__periods": periods}, scoring="roc_auc")
122
+ ```
123
+
124
+ `get_params` / `set_params` / `clone`, `get_feature_names_out` and metadata routing
125
+ (`TIDE().set_fit_request(periods=True)`) work as usual.
126
+
127
+ ## Performance
128
+
129
+ `fit` works on per-period counts of the sorted unique values, so after one sort per
130
+ feature its cost no longer depends on the number of rows. `n_jobs=-1` fits features in
131
+ parallel. Measured on a 2-core laptop, 5,000,000 rows and 24 periods, per feature:
132
+
133
+ | feature | fit | transform |
134
+ |---|---|---|
135
+ | integer, 100 unique values | ~1 s | ~0.5 s |
136
+ | float, 1,000 unique | ~1 s | ~0.5 s |
137
+ | float, all unique (undersampled to 200 quantiles) | ~5 s | ~0.5 s |
138
+ | float, 530k unique (exact search, `max_unique=10**6`) | ~5 s | ~0.5 s |
139
+ | mixed object column (numbers, strings, NaN) | ~3 s | ~2 s |
140
+
141
+ Run `python benchmarks/bench_fit.py` to measure on your machine.
142
+
143
+ ## License
144
+
145
+ MIT © Leonid Garin
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tide-binning"
7
+ dynamic = ["version"]
8
+ description = "TIDE - Time Invariant Discretization Engine: time-stable binning for credit scorecards"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Leonid Garin", email = "leonidgarin97@gmail.com" }]
13
+ requires-python = ">=3.10"
14
+ keywords = ["binning", "scorecard", "credit risk", "weight of evidence", "woe", "information value", "psi"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Financial and Insurance Industry",
18
+ "Intended Audience :: Science/Research",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Scientific/Engineering :: Information Analysis",
27
+ ]
28
+ dependencies = [
29
+ "numpy>=1.23",
30
+ "pandas>=2.0",
31
+ "scipy>=1.9",
32
+ "scikit-learn>=1.4",
33
+ "matplotlib>=3.7",
34
+ "joblib>=1.3",
35
+ "tqdm>=4.60",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ test = ["pytest>=7", "pytest-cov>=4", "hypothesis>=6.80"]
40
+ docs = ["mkdocs>=1.6,<2", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.25"]
41
+ dev = ["tide-binning[test,docs]", "ruff>=0.6", "build>=1.0", "twine>=5"]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/leonidgarin/TIDE"
45
+ Issues = "https://github.com/leonidgarin/TIDE/issues"
46
+
47
+ [tool.hatch.version]
48
+ path = "src/tide/__init__.py"
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/tide"]
52
+
53
+ [tool.hatch.build.targets.sdist]
54
+ include = ["src/tide", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ addopts = "-ra"
59
+ filterwarnings = ["error::FutureWarning", "error::DeprecationWarning:tide.*"]
60
+
61
+ [tool.coverage.run]
62
+ source = ["tide"]
63
+ branch = true
64
+
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py310"
68
+
69
+ [tool.ruff.lint]
70
+ select = ["E", "F", "W", "I", "B", "UP", "NPY", "PD", "RUF"]
71
+ ignore = ["RUF001", "RUF002", "RUF003", "PD011", "B905"]
72
+
73
+ [tool.ruff.lint.per-file-ignores]
74
+ "tests/*" = ["B", "PD", "RUF007", "RUF059"]
75
+ "benchmarks/*" = ["B", "PD"]
@@ -0,0 +1,10 @@
1
+ """TIDE - Time Invariant Discretization Engine.
2
+
3
+ Time-stable binning of mixed-type features for credit scorecards.
4
+ """
5
+
6
+ from ._estimator import TIDE
7
+
8
+ __version__ = "0.6.0"
9
+
10
+ __all__ = ["TIDE", "__version__"]