iyipada 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.
iyipada-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kenny Obidele
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.
iyipada-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: iyipada
3
+ Version: 0.1.0
4
+ Summary: Drift detection with calibrated thresholds instead of folklore ones
5
+ Author: Kenny Obidele
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Kenny0bi/iyipada
8
+ Keywords: drift,monitoring,mlops,statistics,psi,data-drift
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy>=1.24
13
+ Requires-Dist: scipy>=1.10
14
+ Provides-Extra: benchmarks
15
+ Requires-Dist: pandas>=2.0; extra == "benchmarks"
16
+ Dynamic: license-file
17
+
18
+ # iyipada
19
+
20
+ Drift detection where the threshold is calculated instead of quoted.
21
+
22
+ Production drift monitoring runs on numbers that came from a blog post. The
23
+ most common rule in the industry is "PSI above 0.10 means watch it, above 0.25
24
+ means act". Those numbers are stated as though they were properties of the
25
+ statistic. They are not. On data where **nothing has changed at all**, that
26
+ first rule fires 85% of the time at 100 samples per batch, and it is
27
+ mathematically incapable of firing at 1,000.
28
+
29
+ This library computes the threshold from the null distribution, so a 1%
30
+ false-alarm rate is a 1% false-alarm rate.
31
+
32
+ ![The threshold everyone uses is not a constant](assets/ruler.svg)
33
+
34
+ *iyipada* is Yoruba for change.
35
+
36
+ ## The three ways a drift monitor lies to you
37
+
38
+ Each of these is measured in this repository, not asserted.
39
+
40
+ **One. A fixed threshold means different things at different sample sizes.**
41
+ PSI under the null is distributed as `(1/n + 1/m) * chi2_{B-1}`, so it depends
42
+ on both sample sizes and the bin count. The industry rule depends on neither.
43
+ False-alarm rate of the "watch" rule on identically distributed data:
44
+
45
+ | samples per batch | 5 bins | 10 bins | 20 bins |
46
+ |---|---|---|---|
47
+ | 50 | 64.3% | 98.2% | **100.0%** |
48
+ | 100 | 29.5% | 85.1% | 100.0% |
49
+ | 200 | 4.1% | 37.0% | 96.0% |
50
+ | 500 | 0.0% | 0.3% | 17.6% |
51
+ | 1,000 and up | 0.0% | 0.0% | 0.0% |
52
+
53
+ The calibrated 1% threshold across that same grid runs from 1.4476 down to
54
+ 0.0004, a span of about 5,400. One number cannot serve all of it.
55
+
56
+ **Two. Twenty features is twenty chances to be wrong.** Twenty clean features
57
+ tested independently at alpha = 0.05 produce at least one alarm on 73% of clean
58
+ batches. With Benjamini-Hochberg, 5%. Monitoring is a multiple-testing problem
59
+ before it is a drift problem.
60
+
61
+ **Three. Fifty-two weekly checks is not one check.** A test calibrated to 5%,
62
+ run weekly, is wrong at least once during a year with probability 93%. Getting
63
+ the single check right and then scheduling it re-creates the problem it solved.
64
+
65
+ ![A monitor that runs every week is not one test](assets/year.svg)
66
+
67
+ ## Using it
68
+
69
+ ```python
70
+ from iyipada import Detector, Monitor
71
+
72
+ d = Detector(reference_values, statistic="psi", alpha=0.01)
73
+ r = d.check(this_weeks_values)
74
+
75
+ r.drifted # bool, decided against a calibrated threshold
76
+ r.pvalue # what you actually want to look at
77
+ r.threshold # computed from your sample sizes, not quoted from anywhere
78
+ r.warning # set when the batch is too small for the test to mean much
79
+ ```
80
+
81
+ Nothing in the API accepts a raw divergence threshold, because a raw divergence
82
+ threshold cannot be interpreted without knowing the sample sizes and the bin
83
+ count.
84
+
85
+ For a table of features, with the multiple testing handled:
86
+
87
+ ```python
88
+ m = Monitor(reference_dataframe_as_dict, alpha=0.05, correction="fdr")
89
+ result = m.check(live_dataframe_as_dict)
90
+ result.drifted # the features that moved, after FDR control
91
+ ```
92
+
93
+ For a monitor that runs on a schedule, stop re-testing and accumulate:
94
+
95
+ ```python
96
+ from iyipada import sequential as sq
97
+
98
+ h, arl = sq.calibrate_run_length("cusum", target_arl=260) # weekly checks
99
+ # h = 3.75, one false alarm every 4.8 years of weekly monitoring
100
+ c = sq.CUSUM(h=h)
101
+ c.update(this_weeks_statistic) # True when the evidence has accumulated
102
+ ```
103
+
104
+ ## Which detector should you actually use
105
+
106
+ Wasserstein, in most cases. Every statistic below was first calibrated to the
107
+ same 1% false-alarm rate, so this compares power and nothing else. Detection
108
+ rate at 1,000 samples a side:
109
+
110
+ | | mean shift | variance | skew | discretisation | mixture | tail only |
111
+ |---|---|---|---|---|---|---|
112
+ | **Wasserstein** | 100% | 100% | 100% | 100% | **99.5%** | **15.7%** |
113
+ | PSI | 99.5% | 100% | 100% | 100% | 86.2% | 3.5% |
114
+ | Jensen-Shannon | 99.5% | 100% | 100% | 100% | 85.8% | 3.5% |
115
+ | chi-square | 99.5% | 100% | 100% | 100% | 85.7% | 3.5% |
116
+ | Kolmogorov-Smirnov | 100% | 99.8% | 88.7% | 100% | 83.0% | 2.3% |
117
+ | total variation | 98.3% | 100% | 99.8% | 100% | 59.7% | 2.8% |
118
+
119
+ ![Which detector actually catches which change](assets/power.svg)
120
+
121
+ Wasserstein wins or ties everywhere, and it reports in the units of the
122
+ variable, so a result reads as "the distribution moved by 0.3 mg/dL" rather
123
+ than as an abstract divergence. PSI, the industry default, is beaten on the two
124
+ hardest cases.
125
+
126
+ ### The blind spot
127
+
128
+ ![The change only one detector sees](assets/blindspot.svg)
129
+
130
+ A small fraction of values jumping to the far tail is close to invisible. At 5%
131
+ contamination Wasserstein catches it every time, PSI 29% of the time and
132
+ Kolmogorov-Smirnov 18%. At 1.5% nothing is reliable.
133
+
134
+ The binned statistics fail because quantile binning puts the reference's entire
135
+ top decile into one bin, so moving mass from +2 to +6 does not change the
136
+ histogram at all. KS fails because a few per cent in the far tail cannot move
137
+ the largest vertical gap between two cumulative curves. Wasserstein survives
138
+ because it measures horizontal distance.
139
+
140
+ If rare extreme values are the failure you fear, monitor the tail directly with
141
+ a quantile or an exceedance count. A distributional distance is the wrong
142
+ instrument, however well calibrated.
143
+
144
+ ## What is verified, and how
145
+
146
+ ![The theory, checked against simulation](assets/nullcheck.svg)
147
+
148
+ The asymptotic null is not taken on faith. Simulated 95th percentile divided by
149
+ theory, across ten sample sizes and three bin counts, sits within 2% of 1.0 from
150
+ 200 samples upward. It is 6 to 9% optimistic around n = 100 and it breaks down
151
+ at n = 50 with 20 bins, where each bin holds two or three points and the
152
+ chi-square approximation has nothing to stand on.
153
+
154
+ So the library uses the closed form above 300 samples and simulates the null
155
+ below it, rather than trusting one answer everywhere. With that switch, the
156
+ calibrated threshold holds between 0.0% and 2.2% actual false-alarm rate across
157
+ the entire grid, against 0% to 100% for the fixed rule.
158
+
159
+ ![What those rules deliver](assets/alarms.svg)
160
+
161
+ The ringed cell is 112 samples at 10 bins. That is a real case from
162
+ [loom](https://github.com/Kenny0bi/loom), an earlier project of mine, where a
163
+ fixed 0.10 rule fired a spurious retrain on identically distributed data. It
164
+ fires there 78.6% of the time. That bug is why this library exists.
165
+
166
+ ## The score, animated
167
+
168
+ ![Why a fixed threshold cannot work](assets/null.gif)
169
+
170
+ The null distribution of PSI collapsing as the sample grows, while the fixed
171
+ rule stays exactly where it is. Every number in the animation comes from
172
+ `benchmarks/false_alarms.py`. (Source:
173
+ [assets/manim_null.py](assets/manim_null.py), video in
174
+ [assets/null.mp4](assets/null.mp4).)
175
+
176
+ ## What is in here
177
+
178
+ - [iyipada/divergence.py](iyipada/divergence.py) the statistics: PSI,
179
+ Jensen-Shannon, total variation, Kolmogorov-Smirnov, Wasserstein,
180
+ chi-square. None of them decides anything.
181
+ - [iyipada/null.py](iyipada/null.py) the null distributions: analytic for PSI,
182
+ chi-square and KS, permutation for everything else, plus
183
+ `false_alarm_rate()` for measuring what a threshold you already use is
184
+ actually doing.
185
+ - [iyipada/detect.py](iyipada/detect.py) `Detector` and `Monitor`, the latter
186
+ with FDR or Bonferroni control.
187
+ - [iyipada/sequential.py](iyipada/sequential.py) CUSUM and Page-Hinkley
188
+ calibrated to an average run length, alpha spending across a known horizon,
189
+ and `detection_delay()` so a monitor that never false-alarms can be checked
190
+ for being merely deaf.
191
+ - [benchmarks/](benchmarks/) the three studies behind every number above.
192
+
193
+ ## Honest limits
194
+
195
+ - **Univariate only.** Correlation between features can shift while every
196
+ marginal stays put, and nothing here would see it. Multivariate drift needs a
197
+ different tool.
198
+ - **The permutation path is slow.** Wasserstein and total variation have no
199
+ closed-form null here, so their thresholds are simulated. That is a few
200
+ seconds per feature, cached per sample size.
201
+ - **Independence is assumed across checks.** The Sidak correction and the run
202
+ length calibration both assume successive batches are independent. Weekly
203
+ batches of the same seasonal process are not, and the true false-alarm rate
204
+ will be somewhat higher than advertised.
205
+ - **Detecting drift is not detecting harm.** A feature can move a long way
206
+ without the model caring, and can stay still while the relationship between
207
+ it and the target rots. This library measures the first thing. Watching the
208
+ second needs labels.
209
+ - **Below about 100 samples, nothing works.** The thresholds are honest there,
210
+ which mostly means they are honestly enormous. A detector that cannot fire is
211
+ correctly calibrated and useless. `Detector` says so in `r.warning`.
212
+
213
+ ## Running it
214
+
215
+ ```bash
216
+ pip install numpy scipy pandas
217
+
218
+ python tests/test_null.py # 9 contracts on the null distributions
219
+ python tests/test_detect.py # 10 on the detector and monitor APIs
220
+ python tests/test_sequential.py # 8 on run lengths and detection delay
221
+
222
+ python benchmarks/false_alarms.py # the headline table, about 7 minutes
223
+ python benchmarks/power.py # detector against drift type, about 4 minutes
224
+ python assets/make_visuals.py # the six figures
225
+ ```
226
+
227
+ 27 tests. The interesting ones assert the findings themselves, so if the
228
+ industry thresholds ever stop being miscalibrated, the suite will say so.
229
+
230
+ ## Papers
231
+
232
+ - B. Yurdakul (2018), *Statistical properties of population stability index*,
233
+ Western Michigan University dissertation. Derives the asymptotic
234
+ distribution, and states plainly that the 0.10 and 0.25 traffic-light values
235
+ have no support in the literature.
236
+ - Bracher et al. and the wider proper-scoring literature for why a decision
237
+ rule needs a stated error rate rather than a threshold.
238
+ - Page (1954), *Continuous inspection schemes*, Biometrika. The original CUSUM,
239
+ and the reason average run length is the right quantity for a monitor that
240
+ runs forever.
241
+ - Benjamini and Hochberg (1995), *Controlling the false discovery rate*, JRSS-B.
242
+ What `Monitor` uses when you hand it fifty features.
@@ -0,0 +1,225 @@
1
+ # iyipada
2
+
3
+ Drift detection where the threshold is calculated instead of quoted.
4
+
5
+ Production drift monitoring runs on numbers that came from a blog post. The
6
+ most common rule in the industry is "PSI above 0.10 means watch it, above 0.25
7
+ means act". Those numbers are stated as though they were properties of the
8
+ statistic. They are not. On data where **nothing has changed at all**, that
9
+ first rule fires 85% of the time at 100 samples per batch, and it is
10
+ mathematically incapable of firing at 1,000.
11
+
12
+ This library computes the threshold from the null distribution, so a 1%
13
+ false-alarm rate is a 1% false-alarm rate.
14
+
15
+ ![The threshold everyone uses is not a constant](assets/ruler.svg)
16
+
17
+ *iyipada* is Yoruba for change.
18
+
19
+ ## The three ways a drift monitor lies to you
20
+
21
+ Each of these is measured in this repository, not asserted.
22
+
23
+ **One. A fixed threshold means different things at different sample sizes.**
24
+ PSI under the null is distributed as `(1/n + 1/m) * chi2_{B-1}`, so it depends
25
+ on both sample sizes and the bin count. The industry rule depends on neither.
26
+ False-alarm rate of the "watch" rule on identically distributed data:
27
+
28
+ | samples per batch | 5 bins | 10 bins | 20 bins |
29
+ |---|---|---|---|
30
+ | 50 | 64.3% | 98.2% | **100.0%** |
31
+ | 100 | 29.5% | 85.1% | 100.0% |
32
+ | 200 | 4.1% | 37.0% | 96.0% |
33
+ | 500 | 0.0% | 0.3% | 17.6% |
34
+ | 1,000 and up | 0.0% | 0.0% | 0.0% |
35
+
36
+ The calibrated 1% threshold across that same grid runs from 1.4476 down to
37
+ 0.0004, a span of about 5,400. One number cannot serve all of it.
38
+
39
+ **Two. Twenty features is twenty chances to be wrong.** Twenty clean features
40
+ tested independently at alpha = 0.05 produce at least one alarm on 73% of clean
41
+ batches. With Benjamini-Hochberg, 5%. Monitoring is a multiple-testing problem
42
+ before it is a drift problem.
43
+
44
+ **Three. Fifty-two weekly checks is not one check.** A test calibrated to 5%,
45
+ run weekly, is wrong at least once during a year with probability 93%. Getting
46
+ the single check right and then scheduling it re-creates the problem it solved.
47
+
48
+ ![A monitor that runs every week is not one test](assets/year.svg)
49
+
50
+ ## Using it
51
+
52
+ ```python
53
+ from iyipada import Detector, Monitor
54
+
55
+ d = Detector(reference_values, statistic="psi", alpha=0.01)
56
+ r = d.check(this_weeks_values)
57
+
58
+ r.drifted # bool, decided against a calibrated threshold
59
+ r.pvalue # what you actually want to look at
60
+ r.threshold # computed from your sample sizes, not quoted from anywhere
61
+ r.warning # set when the batch is too small for the test to mean much
62
+ ```
63
+
64
+ Nothing in the API accepts a raw divergence threshold, because a raw divergence
65
+ threshold cannot be interpreted without knowing the sample sizes and the bin
66
+ count.
67
+
68
+ For a table of features, with the multiple testing handled:
69
+
70
+ ```python
71
+ m = Monitor(reference_dataframe_as_dict, alpha=0.05, correction="fdr")
72
+ result = m.check(live_dataframe_as_dict)
73
+ result.drifted # the features that moved, after FDR control
74
+ ```
75
+
76
+ For a monitor that runs on a schedule, stop re-testing and accumulate:
77
+
78
+ ```python
79
+ from iyipada import sequential as sq
80
+
81
+ h, arl = sq.calibrate_run_length("cusum", target_arl=260) # weekly checks
82
+ # h = 3.75, one false alarm every 4.8 years of weekly monitoring
83
+ c = sq.CUSUM(h=h)
84
+ c.update(this_weeks_statistic) # True when the evidence has accumulated
85
+ ```
86
+
87
+ ## Which detector should you actually use
88
+
89
+ Wasserstein, in most cases. Every statistic below was first calibrated to the
90
+ same 1% false-alarm rate, so this compares power and nothing else. Detection
91
+ rate at 1,000 samples a side:
92
+
93
+ | | mean shift | variance | skew | discretisation | mixture | tail only |
94
+ |---|---|---|---|---|---|---|
95
+ | **Wasserstein** | 100% | 100% | 100% | 100% | **99.5%** | **15.7%** |
96
+ | PSI | 99.5% | 100% | 100% | 100% | 86.2% | 3.5% |
97
+ | Jensen-Shannon | 99.5% | 100% | 100% | 100% | 85.8% | 3.5% |
98
+ | chi-square | 99.5% | 100% | 100% | 100% | 85.7% | 3.5% |
99
+ | Kolmogorov-Smirnov | 100% | 99.8% | 88.7% | 100% | 83.0% | 2.3% |
100
+ | total variation | 98.3% | 100% | 99.8% | 100% | 59.7% | 2.8% |
101
+
102
+ ![Which detector actually catches which change](assets/power.svg)
103
+
104
+ Wasserstein wins or ties everywhere, and it reports in the units of the
105
+ variable, so a result reads as "the distribution moved by 0.3 mg/dL" rather
106
+ than as an abstract divergence. PSI, the industry default, is beaten on the two
107
+ hardest cases.
108
+
109
+ ### The blind spot
110
+
111
+ ![The change only one detector sees](assets/blindspot.svg)
112
+
113
+ A small fraction of values jumping to the far tail is close to invisible. At 5%
114
+ contamination Wasserstein catches it every time, PSI 29% of the time and
115
+ Kolmogorov-Smirnov 18%. At 1.5% nothing is reliable.
116
+
117
+ The binned statistics fail because quantile binning puts the reference's entire
118
+ top decile into one bin, so moving mass from +2 to +6 does not change the
119
+ histogram at all. KS fails because a few per cent in the far tail cannot move
120
+ the largest vertical gap between two cumulative curves. Wasserstein survives
121
+ because it measures horizontal distance.
122
+
123
+ If rare extreme values are the failure you fear, monitor the tail directly with
124
+ a quantile or an exceedance count. A distributional distance is the wrong
125
+ instrument, however well calibrated.
126
+
127
+ ## What is verified, and how
128
+
129
+ ![The theory, checked against simulation](assets/nullcheck.svg)
130
+
131
+ The asymptotic null is not taken on faith. Simulated 95th percentile divided by
132
+ theory, across ten sample sizes and three bin counts, sits within 2% of 1.0 from
133
+ 200 samples upward. It is 6 to 9% optimistic around n = 100 and it breaks down
134
+ at n = 50 with 20 bins, where each bin holds two or three points and the
135
+ chi-square approximation has nothing to stand on.
136
+
137
+ So the library uses the closed form above 300 samples and simulates the null
138
+ below it, rather than trusting one answer everywhere. With that switch, the
139
+ calibrated threshold holds between 0.0% and 2.2% actual false-alarm rate across
140
+ the entire grid, against 0% to 100% for the fixed rule.
141
+
142
+ ![What those rules deliver](assets/alarms.svg)
143
+
144
+ The ringed cell is 112 samples at 10 bins. That is a real case from
145
+ [loom](https://github.com/Kenny0bi/loom), an earlier project of mine, where a
146
+ fixed 0.10 rule fired a spurious retrain on identically distributed data. It
147
+ fires there 78.6% of the time. That bug is why this library exists.
148
+
149
+ ## The score, animated
150
+
151
+ ![Why a fixed threshold cannot work](assets/null.gif)
152
+
153
+ The null distribution of PSI collapsing as the sample grows, while the fixed
154
+ rule stays exactly where it is. Every number in the animation comes from
155
+ `benchmarks/false_alarms.py`. (Source:
156
+ [assets/manim_null.py](assets/manim_null.py), video in
157
+ [assets/null.mp4](assets/null.mp4).)
158
+
159
+ ## What is in here
160
+
161
+ - [iyipada/divergence.py](iyipada/divergence.py) the statistics: PSI,
162
+ Jensen-Shannon, total variation, Kolmogorov-Smirnov, Wasserstein,
163
+ chi-square. None of them decides anything.
164
+ - [iyipada/null.py](iyipada/null.py) the null distributions: analytic for PSI,
165
+ chi-square and KS, permutation for everything else, plus
166
+ `false_alarm_rate()` for measuring what a threshold you already use is
167
+ actually doing.
168
+ - [iyipada/detect.py](iyipada/detect.py) `Detector` and `Monitor`, the latter
169
+ with FDR or Bonferroni control.
170
+ - [iyipada/sequential.py](iyipada/sequential.py) CUSUM and Page-Hinkley
171
+ calibrated to an average run length, alpha spending across a known horizon,
172
+ and `detection_delay()` so a monitor that never false-alarms can be checked
173
+ for being merely deaf.
174
+ - [benchmarks/](benchmarks/) the three studies behind every number above.
175
+
176
+ ## Honest limits
177
+
178
+ - **Univariate only.** Correlation between features can shift while every
179
+ marginal stays put, and nothing here would see it. Multivariate drift needs a
180
+ different tool.
181
+ - **The permutation path is slow.** Wasserstein and total variation have no
182
+ closed-form null here, so their thresholds are simulated. That is a few
183
+ seconds per feature, cached per sample size.
184
+ - **Independence is assumed across checks.** The Sidak correction and the run
185
+ length calibration both assume successive batches are independent. Weekly
186
+ batches of the same seasonal process are not, and the true false-alarm rate
187
+ will be somewhat higher than advertised.
188
+ - **Detecting drift is not detecting harm.** A feature can move a long way
189
+ without the model caring, and can stay still while the relationship between
190
+ it and the target rots. This library measures the first thing. Watching the
191
+ second needs labels.
192
+ - **Below about 100 samples, nothing works.** The thresholds are honest there,
193
+ which mostly means they are honestly enormous. A detector that cannot fire is
194
+ correctly calibrated and useless. `Detector` says so in `r.warning`.
195
+
196
+ ## Running it
197
+
198
+ ```bash
199
+ pip install numpy scipy pandas
200
+
201
+ python tests/test_null.py # 9 contracts on the null distributions
202
+ python tests/test_detect.py # 10 on the detector and monitor APIs
203
+ python tests/test_sequential.py # 8 on run lengths and detection delay
204
+
205
+ python benchmarks/false_alarms.py # the headline table, about 7 minutes
206
+ python benchmarks/power.py # detector against drift type, about 4 minutes
207
+ python assets/make_visuals.py # the six figures
208
+ ```
209
+
210
+ 27 tests. The interesting ones assert the findings themselves, so if the
211
+ industry thresholds ever stop being miscalibrated, the suite will say so.
212
+
213
+ ## Papers
214
+
215
+ - B. Yurdakul (2018), *Statistical properties of population stability index*,
216
+ Western Michigan University dissertation. Derives the asymptotic
217
+ distribution, and states plainly that the 0.10 and 0.25 traffic-light values
218
+ have no support in the literature.
219
+ - Bracher et al. and the wider proper-scoring literature for why a decision
220
+ rule needs a stated error rate rather than a threshold.
221
+ - Page (1954), *Continuous inspection schemes*, Biometrika. The original CUSUM,
222
+ and the reason average run length is the right quantity for a monitor that
223
+ runs forever.
224
+ - Benjamini and Hochberg (1995), *Controlling the false discovery rate*, JRSS-B.
225
+ What `Monitor` uses when you hand it fifty features.
@@ -0,0 +1,32 @@
1
+ """Drift detection where the threshold is calculated instead of quoted.
2
+
3
+ from iyipada import Detector
4
+ d = Detector(reference, statistic="wasserstein", alpha=0.01)
5
+ r = d.check(live)
6
+ r.drifted, r.pvalue, r.threshold
7
+
8
+ The point of the library, in one sentence: a drift threshold is not a property
9
+ of the statistic, it is a property of how much data you have, and every
10
+ published rule of thumb ignores that.
11
+ """
12
+
13
+ from .detect import Detector, Monitor, Result, TableResult
14
+ from .divergence import (chi_square, jensen_shannon, ks_statistic, psi,
15
+ quantile_edges, total_variation, wasserstein)
16
+ from .null import (analytic_threshold, false_alarm_rate, permutation_null,
17
+ psi_null, simulated_threshold, threshold)
18
+ from .sequential import (CUSUM, PageHinkley, any_alarm_probability,
19
+ calibrate_run_length, detection_delay,
20
+ family_wise_alpha, run_length)
21
+
22
+ __version__ = "0.1.0"
23
+
24
+ __all__ = [
25
+ "Detector", "Monitor", "Result", "TableResult",
26
+ "psi", "jensen_shannon", "total_variation", "ks_statistic",
27
+ "wasserstein", "chi_square", "quantile_edges",
28
+ "threshold", "analytic_threshold", "simulated_threshold",
29
+ "permutation_null", "psi_null", "false_alarm_rate",
30
+ "CUSUM", "PageHinkley", "calibrate_run_length", "run_length",
31
+ "detection_delay", "family_wise_alpha", "any_alarm_probability",
32
+ ]