driftdep 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.
- driftdep-0.1.0/.claude/settings.local.json +15 -0
- driftdep-0.1.0/.gitattributes +2 -0
- driftdep-0.1.0/.github/workflows/publish.yml +88 -0
- driftdep-0.1.0/.gitignore +56 -0
- driftdep-0.1.0/LICENSE +21 -0
- driftdep-0.1.0/Makefile +67 -0
- driftdep-0.1.0/PKG-INFO +155 -0
- driftdep-0.1.0/README.md +129 -0
- driftdep-0.1.0/manifest.yaml +99 -0
- driftdep-0.1.0/pyproject.toml +68 -0
- driftdep-0.1.0/research/__init__.py +17 -0
- driftdep-0.1.0/research/analysis/__init__.py +16 -0
- driftdep-0.1.0/research/analysis/fig1_false_alarm.py +134 -0
- driftdep-0.1.0/research/analysis/fig2_ks_null.py +123 -0
- driftdep-0.1.0/research/analysis/fig3_ess_indicator.py +95 -0
- driftdep-0.1.0/research/analysis/fig4_size_after_correction.py +129 -0
- driftdep-0.1.0/research/analysis/fig5_power.py +124 -0
- driftdep-0.1.0/research/analysis/fig6_arl.py +88 -0
- driftdep-0.1.0/research/analysis/table1_size.py +135 -0
- driftdep-0.1.0/research/analysis/table2_realdata.py +77 -0
- driftdep-0.1.0/research/corrections/__init__.py +39 -0
- driftdep-0.1.0/research/corrections/block_perm.py +105 -0
- driftdep-0.1.0/research/corrections/bootstrap_corrections.py +226 -0
- driftdep-0.1.0/research/corrections/dep_wild.py +155 -0
- driftdep-0.1.0/research/corrections/ess_adjust.py +159 -0
- driftdep-0.1.0/research/corrections/naive.py +77 -0
- driftdep-0.1.0/research/corrections/prewhiten.py +147 -0
- driftdep-0.1.0/research/corrections/thinning.py +96 -0
- driftdep-0.1.0/research/data/__init__.py +14 -0
- driftdep-0.1.0/research/data/download_fred.py +64 -0
- driftdep-0.1.0/research/data/download_m4.py +88 -0
- driftdep-0.1.0/research/data/download_uci.py +122 -0
- driftdep-0.1.0/research/dgp/__init__.py +7 -0
- driftdep-0.1.0/research/dgp/alternatives.py +98 -0
- driftdep-0.1.0/research/dgp/ar1.py +61 -0
- driftdep-0.1.0/research/dgp/arfima.py +78 -0
- driftdep-0.1.0/research/dgp/arma.py +45 -0
- driftdep-0.1.0/research/dgp/garch.py +89 -0
- driftdep-0.1.0/research/dgp/heavy_tail.py +38 -0
- driftdep-0.1.0/research/dgp/seasonal.py +63 -0
- driftdep-0.1.0/research/experiments/__init__.py +13 -0
- driftdep-0.1.0/research/experiments/compute_cost.py +87 -0
- driftdep-0.1.0/research/experiments/power.py +244 -0
- driftdep-0.1.0/research/experiments/realdata.py +241 -0
- driftdep-0.1.0/research/experiments/size.py +299 -0
- driftdep-0.1.0/research/experiments/streaming.py +338 -0
- driftdep-0.1.0/research/harness/__init__.py +17 -0
- driftdep-0.1.0/research/harness/interfaces.py +44 -0
- driftdep-0.1.0/research/harness/registry.py +27 -0
- driftdep-0.1.0/research/harness/runner.py +155 -0
- driftdep-0.1.0/research/results/.gitkeep +0 -0
- driftdep-0.1.0/research/tests_battery/__init__.py +29 -0
- driftdep-0.1.0/research/tests_battery/stats.py +211 -0
- driftdep-0.1.0/src/driftdep/__init__.py +470 -0
- driftdep-0.1.0/src/driftdep/_blocklen.py +42 -0
- driftdep-0.1.0/src/driftdep/_blockperm.py +69 -0
- driftdep-0.1.0/src/driftdep/_ess.py +120 -0
- driftdep-0.1.0/src/driftdep/_stats.py +124 -0
- driftdep-0.1.0/src/driftdep/py.typed +0 -0
- driftdep-0.1.0/tests/conftest.py +7 -0
- driftdep-0.1.0/tests/test_m1.py +204 -0
- driftdep-0.1.0/tests/test_m2.py +209 -0
- driftdep-0.1.0/tests/test_m3.py +354 -0
- driftdep-0.1.0/tests/test_m4.py +135 -0
- driftdep-0.1.0/tests/test_package.py +17 -0
- driftdep-0.1.0/uv.lock +2179 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(pip3 --version)",
|
|
5
|
+
"Read(//Users/vivekchaudhary/.local/bin/**)",
|
|
6
|
+
"Read(//opt/homebrew/bin/**)",
|
|
7
|
+
"Read(//Users/vivekchaudhary/.cargo/bin/**)",
|
|
8
|
+
"Read(//usr/local/bin/**)",
|
|
9
|
+
"Read(//opt/homebrew/**)",
|
|
10
|
+
"Bash(curl -LsSf https://astral.sh/uv/install.sh)",
|
|
11
|
+
"Bash(sh)",
|
|
12
|
+
"Bash(~/.local/bin/uv *)"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
with:
|
|
18
|
+
version: "latest"
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
run: uv python install 3.11
|
|
22
|
+
|
|
23
|
+
- name: Build sdist and wheel
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
- name: Verify wheel contents (research/ must be absent)
|
|
27
|
+
run: |
|
|
28
|
+
pip install twine
|
|
29
|
+
twine check dist/*
|
|
30
|
+
python - <<'EOF'
|
|
31
|
+
import zipfile, sys, pathlib
|
|
32
|
+
wheels = list(pathlib.Path("dist").glob("*.whl"))
|
|
33
|
+
assert wheels, "No wheel found in dist/"
|
|
34
|
+
for whl in wheels:
|
|
35
|
+
with zipfile.ZipFile(whl) as z:
|
|
36
|
+
names = z.namelist()
|
|
37
|
+
bad = [n for n in names if n.startswith("research/")]
|
|
38
|
+
if bad:
|
|
39
|
+
print(f"ERROR: research/ found in wheel: {bad}", file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
print("Wheel contents OK — research/ is absent.")
|
|
42
|
+
EOF
|
|
43
|
+
|
|
44
|
+
- name: Upload distribution artifacts
|
|
45
|
+
uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
publish-testpypi:
|
|
51
|
+
name: Publish to TestPyPI
|
|
52
|
+
needs: build
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
environment:
|
|
55
|
+
name: testpypi
|
|
56
|
+
url: https://test.pypi.org/p/driftdep
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write # OIDC trusted publishing
|
|
59
|
+
steps:
|
|
60
|
+
- name: Download artifacts
|
|
61
|
+
uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
|
|
66
|
+
- name: Publish to TestPyPI
|
|
67
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
68
|
+
with:
|
|
69
|
+
repository-url: https://test.pypi.org/legacy/
|
|
70
|
+
|
|
71
|
+
publish-pypi:
|
|
72
|
+
name: Publish to PyPI
|
|
73
|
+
needs: publish-testpypi
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
environment:
|
|
76
|
+
name: pypi
|
|
77
|
+
url: https://pypi.org/p/driftdep
|
|
78
|
+
permissions:
|
|
79
|
+
id-token: write # OIDC trusted publishing
|
|
80
|
+
steps:
|
|
81
|
+
- name: Download artifacts
|
|
82
|
+
uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
|
|
87
|
+
- name: Publish to PyPI
|
|
88
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
*.egg
|
|
9
|
+
.venv/
|
|
10
|
+
env/
|
|
11
|
+
venv/
|
|
12
|
+
.uv/
|
|
13
|
+
|
|
14
|
+
# Jupyter
|
|
15
|
+
.ipynb_checkpoints/
|
|
16
|
+
*.ipynb
|
|
17
|
+
|
|
18
|
+
# Research outputs (large, reproducible from manifest.yaml seeds)
|
|
19
|
+
research/results/*
|
|
20
|
+
!research/results/.gitkeep
|
|
21
|
+
research/data/raw/
|
|
22
|
+
research/data/external/
|
|
23
|
+
research/data/cache/
|
|
24
|
+
|
|
25
|
+
# Simulation artefacts
|
|
26
|
+
*.pkl
|
|
27
|
+
*.parquet
|
|
28
|
+
*.feather
|
|
29
|
+
*.h5
|
|
30
|
+
*.hdf5
|
|
31
|
+
|
|
32
|
+
# Figures (generated by make figures / make reproduce)
|
|
33
|
+
*.pdf
|
|
34
|
+
*.png
|
|
35
|
+
*.svg
|
|
36
|
+
|
|
37
|
+
# Raw data (download scripts in research/data/ provided instead)
|
|
38
|
+
*.csv
|
|
39
|
+
|
|
40
|
+
# Package / distribution
|
|
41
|
+
*.whl
|
|
42
|
+
*.tar.gz
|
|
43
|
+
MANIFEST
|
|
44
|
+
|
|
45
|
+
# Mount point (local only)
|
|
46
|
+
mnt/
|
|
47
|
+
|
|
48
|
+
# OS
|
|
49
|
+
.DS_Store
|
|
50
|
+
Thumbs.db
|
|
51
|
+
|
|
52
|
+
# IDE
|
|
53
|
+
.vscode/
|
|
54
|
+
.idea/
|
|
55
|
+
*.swp
|
|
56
|
+
CLAUDE.md
|
driftdep-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vivek Chaudhary
|
|
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.
|
driftdep-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.PHONY: test test-fast lint typecheck reproduce figures data clean build
|
|
2
|
+
|
|
3
|
+
# ── development ────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
test:
|
|
6
|
+
uv run pytest
|
|
7
|
+
|
|
8
|
+
test-fast:
|
|
9
|
+
uv run pytest -m "not slow"
|
|
10
|
+
|
|
11
|
+
lint:
|
|
12
|
+
uv run ruff check . && uv run ruff format --check .
|
|
13
|
+
|
|
14
|
+
typecheck:
|
|
15
|
+
uv run mypy src/driftdep
|
|
16
|
+
|
|
17
|
+
build:
|
|
18
|
+
uv build
|
|
19
|
+
|
|
20
|
+
# ── paper reproduction ─────────────────────────────────────────────────────────
|
|
21
|
+
# Regenerates every figure and table from seeds; outputs land in research/results/.
|
|
22
|
+
# Figures are deterministic (no timestamps) so diffs are clean.
|
|
23
|
+
|
|
24
|
+
reproduce:
|
|
25
|
+
@echo "=== Logging library versions ==="
|
|
26
|
+
uv run python -c "\
|
|
27
|
+
import sys, importlib, csv, pathlib; \
|
|
28
|
+
libs = ['numpy','scipy','statsmodels','arch','dcor','pandas','matplotlib','joblib']; \
|
|
29
|
+
rows = []; \
|
|
30
|
+
[rows.append({'package': l, 'version': getattr(importlib.import_module(l), '__version__', '?')}) for l in libs]; \
|
|
31
|
+
path = pathlib.Path('research/results/environment.txt'); \
|
|
32
|
+
path.write_text('\n'.join(f\"{r['package']}=={r['version']}\" for r in rows) + '\n'); \
|
|
33
|
+
print('Versions logged to', path)"
|
|
34
|
+
@echo "=== Experiments ==="
|
|
35
|
+
uv run python -m research.experiments.size screen
|
|
36
|
+
uv run python -m research.experiments.size full
|
|
37
|
+
uv run python -m research.experiments.power screen
|
|
38
|
+
uv run python -m research.experiments.power multi
|
|
39
|
+
uv run python -m research.experiments.streaming screen
|
|
40
|
+
uv run python -m research.experiments.compute_cost
|
|
41
|
+
uv run python -m research.experiments.realdata
|
|
42
|
+
@echo "=== Figures and tables ==="
|
|
43
|
+
$(MAKE) figures
|
|
44
|
+
|
|
45
|
+
figures:
|
|
46
|
+
uv run python -m research.analysis.fig1_false_alarm
|
|
47
|
+
uv run python -m research.analysis.fig2_ks_null
|
|
48
|
+
uv run python -m research.analysis.fig3_ess_indicator
|
|
49
|
+
uv run python -m research.analysis.fig4_size_after_correction
|
|
50
|
+
uv run python -m research.analysis.fig5_power
|
|
51
|
+
uv run python -m research.analysis.fig6_arl
|
|
52
|
+
uv run python -m research.analysis.table1_size
|
|
53
|
+
uv run python -m research.analysis.table2_realdata
|
|
54
|
+
|
|
55
|
+
# ── data download (separate from reproduce; requires internet) ─────────────────
|
|
56
|
+
|
|
57
|
+
data:
|
|
58
|
+
uv run python -m research.data.download_fred
|
|
59
|
+
uv run python -m research.data.download_uci
|
|
60
|
+
uv run python -m research.data.download_m4
|
|
61
|
+
|
|
62
|
+
# ── housekeeping ───────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
clean:
|
|
65
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
66
|
+
find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
|
67
|
+
rm -rf dist/ build/ src/*.egg-info/
|
driftdep-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: driftdep
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dependence-robust two-sample drift tests for serially correlated feature streams
|
|
5
|
+
Author-email: Vivek Chaudhary <vivekch2018@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: MLOps,drift detection,monitoring,serial dependence,two-sample test
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: numpy>=1.24
|
|
11
|
+
Requires-Dist: scipy>=1.10
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: build; extra == 'dev'
|
|
14
|
+
Requires-Dist: mypy>=1.5; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
17
|
+
Requires-Dist: twine; extra == 'dev'
|
|
18
|
+
Provides-Extra: research
|
|
19
|
+
Requires-Dist: arch>=6.0; extra == 'research'
|
|
20
|
+
Requires-Dist: dcor>=0.6; extra == 'research'
|
|
21
|
+
Requires-Dist: joblib>=1.3; extra == 'research'
|
|
22
|
+
Requires-Dist: matplotlib>=3.7; extra == 'research'
|
|
23
|
+
Requires-Dist: pandas>=2.0; extra == 'research'
|
|
24
|
+
Requires-Dist: statsmodels>=0.14; extra == 'research'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# driftdep
|
|
28
|
+
|
|
29
|
+
**Your drift monitor is lying to you.**
|
|
30
|
+
If your feature windows are autocorrelated — and they almost always are —
|
|
31
|
+
standard two-sample tests (KS, CvM, AD, MMD) over-reject by 2–10× at realistic
|
|
32
|
+
dependencies. A single AR(1) coefficient of ρ=0.7 inflates the KS false-alarm
|
|
33
|
+
rate from 5% to 35%. Long-memory processes (ARFIMA) push it even higher.
|
|
34
|
+
|
|
35
|
+
`driftdep` is a drop-in, dependence-robust replacement that corrects for serial
|
|
36
|
+
dependence via ESS-adjusted block permutation by default.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Before — unreliable under serial dependence
|
|
40
|
+
from scipy.stats import ks_2samp
|
|
41
|
+
stat, p = ks_2samp(x_ref, x_new) # p-value too small when data is autocorrelated
|
|
42
|
+
|
|
43
|
+
# After — calibrated by default
|
|
44
|
+
from driftdep import ks_2samp
|
|
45
|
+
stat, p = ks_2samp(x_ref, x_new) # same call, same unpacking, correct p-value
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install driftdep
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Requires Python ≥ 3.10, numpy ≥ 1.24, scipy ≥ 1.10 only.
|
|
55
|
+
Heavy research dependencies (arch, statsmodels, dcor) are optional:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install "driftdep[research]" # for all corrections and download scripts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import numpy as np
|
|
65
|
+
import driftdep
|
|
66
|
+
|
|
67
|
+
rng = np.random.default_rng(0)
|
|
68
|
+
x_ref = rng.standard_normal(500)
|
|
69
|
+
x_new = rng.standard_normal(500) # same distribution; should not alarm
|
|
70
|
+
|
|
71
|
+
stat, p = driftdep.ks_2samp(x_ref, x_new)
|
|
72
|
+
# p ≈ 0.6 — no false alarm (correct even if the series is autocorrelated)
|
|
73
|
+
|
|
74
|
+
# Full result with diagnostics
|
|
75
|
+
result = driftdep.drift_test(x_ref, x_new, statistic="ks")
|
|
76
|
+
print(result.pvalue) # dependence-corrected p-value
|
|
77
|
+
print(result.n_eff) # effective sample size estimate
|
|
78
|
+
print(result.block_length) # block length used by block permutation
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
driftdep.drift_test(
|
|
85
|
+
x, y, *,
|
|
86
|
+
statistic="ks", # "ks", "cvm", "ad", "energy", "mmd",
|
|
87
|
+
# "wasserstein", "psi", "js"
|
|
88
|
+
correction="block_perm", # default engine; see table below
|
|
89
|
+
block_length=None, # None → auto (Politis–White 2004)
|
|
90
|
+
ess_adjust=True, # compute and report indicator-transform ESS
|
|
91
|
+
n_resamples=999, # permutation resamples
|
|
92
|
+
alpha=0.05,
|
|
93
|
+
rng=None, # numpy Generator, int seed, or None
|
|
94
|
+
) -> DriftResult
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`DriftResult` unpacks as `(statistic, pvalue)` for drop-in compatibility and
|
|
98
|
+
also exposes `.pvalue`, `.n_eff`, `.block_length`, `.correction`.
|
|
99
|
+
|
|
100
|
+
### Available corrections
|
|
101
|
+
|
|
102
|
+
| `correction=` | Description | Extra deps |
|
|
103
|
+
|-----------------|-----------------------------------------------------|----------------|
|
|
104
|
+
| `block_perm` | Block permutation, Politis–White block length | — |
|
|
105
|
+
| `ess_adjust` | ESS-adjusted analytic null (cheapest; KS/CvM/AD) | — |
|
|
106
|
+
| `naive` | i.i.d. null — baseline / reference only | — |
|
|
107
|
+
| `thinning` | Keep every k-th obs to approximate independence | — |
|
|
108
|
+
| `dep_wild` | Dependent wild bootstrap (Shao 2010) | — |
|
|
109
|
+
| `mbb` | Moving block bootstrap (Künsch 1989) | `[research]` |
|
|
110
|
+
| `cbb` | Circular block bootstrap | `[research]` |
|
|
111
|
+
| `stationary` | Stationary bootstrap (Politis–Romano 1994) | `[research]` |
|
|
112
|
+
| `prewhiten` | **Cautionary only** — changes the hypothesis | `[research]` |
|
|
113
|
+
|
|
114
|
+
## Method
|
|
115
|
+
|
|
116
|
+
`driftdep` benchmarks and operationalizes dependence corrections for two-sample
|
|
117
|
+
distributional drift tests. The core insight: when observations within a
|
|
118
|
+
monitoring window are autocorrelated, the i.i.d. null distribution of KS, CvM,
|
|
119
|
+
and related statistics is stochastically dominated, causing severe over-rejection.
|
|
120
|
+
|
|
121
|
+
The recommended default — ESS-adjusted block permutation — permutes contiguous
|
|
122
|
+
blocks of length *b* (preserving within-block dependence) and selects *b*
|
|
123
|
+
automatically via Politis–White (2004). Empirical size recovers to ≈ α across
|
|
124
|
+
AR(1), ARFIMA, and GARCH dependence structures. Size-adjusted power is within
|
|
125
|
+
5–7 percentage points of the uncalibrated naive test at moderate effect sizes.
|
|
126
|
+
|
|
127
|
+
All corrections already exist in the literature; this package operationalizes
|
|
128
|
+
them in a unified interface with a validated default.
|
|
129
|
+
|
|
130
|
+
## Reproduce paper results
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/vivekch2018/driftdep
|
|
134
|
+
cd driftdep
|
|
135
|
+
pip install -e ".[research]"
|
|
136
|
+
make data # download public datasets (requires internet)
|
|
137
|
+
make reproduce # regenerate all figures and tables from seeds
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Citation
|
|
141
|
+
|
|
142
|
+
```bibtex
|
|
143
|
+
@misc{chaudhary2025driftdep,
|
|
144
|
+
author = {Chaudhary, Vivek},
|
|
145
|
+
title = {Two-Sample Drift Tests Break Under Serial Dependence:
|
|
146
|
+
A Benchmark and Deployable Correction},
|
|
147
|
+
year = {2025},
|
|
148
|
+
note = {Preprint},
|
|
149
|
+
url = {https://github.com/vivekch2018/driftdep},
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
driftdep-0.1.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# driftdep
|
|
2
|
+
|
|
3
|
+
**Your drift monitor is lying to you.**
|
|
4
|
+
If your feature windows are autocorrelated — and they almost always are —
|
|
5
|
+
standard two-sample tests (KS, CvM, AD, MMD) over-reject by 2–10× at realistic
|
|
6
|
+
dependencies. A single AR(1) coefficient of ρ=0.7 inflates the KS false-alarm
|
|
7
|
+
rate from 5% to 35%. Long-memory processes (ARFIMA) push it even higher.
|
|
8
|
+
|
|
9
|
+
`driftdep` is a drop-in, dependence-robust replacement that corrects for serial
|
|
10
|
+
dependence via ESS-adjusted block permutation by default.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# Before — unreliable under serial dependence
|
|
14
|
+
from scipy.stats import ks_2samp
|
|
15
|
+
stat, p = ks_2samp(x_ref, x_new) # p-value too small when data is autocorrelated
|
|
16
|
+
|
|
17
|
+
# After — calibrated by default
|
|
18
|
+
from driftdep import ks_2samp
|
|
19
|
+
stat, p = ks_2samp(x_ref, x_new) # same call, same unpacking, correct p-value
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install driftdep
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python ≥ 3.10, numpy ≥ 1.24, scipy ≥ 1.10 only.
|
|
29
|
+
Heavy research dependencies (arch, statsmodels, dcor) are optional:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install "driftdep[research]" # for all corrections and download scripts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import numpy as np
|
|
39
|
+
import driftdep
|
|
40
|
+
|
|
41
|
+
rng = np.random.default_rng(0)
|
|
42
|
+
x_ref = rng.standard_normal(500)
|
|
43
|
+
x_new = rng.standard_normal(500) # same distribution; should not alarm
|
|
44
|
+
|
|
45
|
+
stat, p = driftdep.ks_2samp(x_ref, x_new)
|
|
46
|
+
# p ≈ 0.6 — no false alarm (correct even if the series is autocorrelated)
|
|
47
|
+
|
|
48
|
+
# Full result with diagnostics
|
|
49
|
+
result = driftdep.drift_test(x_ref, x_new, statistic="ks")
|
|
50
|
+
print(result.pvalue) # dependence-corrected p-value
|
|
51
|
+
print(result.n_eff) # effective sample size estimate
|
|
52
|
+
print(result.block_length) # block length used by block permutation
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
driftdep.drift_test(
|
|
59
|
+
x, y, *,
|
|
60
|
+
statistic="ks", # "ks", "cvm", "ad", "energy", "mmd",
|
|
61
|
+
# "wasserstein", "psi", "js"
|
|
62
|
+
correction="block_perm", # default engine; see table below
|
|
63
|
+
block_length=None, # None → auto (Politis–White 2004)
|
|
64
|
+
ess_adjust=True, # compute and report indicator-transform ESS
|
|
65
|
+
n_resamples=999, # permutation resamples
|
|
66
|
+
alpha=0.05,
|
|
67
|
+
rng=None, # numpy Generator, int seed, or None
|
|
68
|
+
) -> DriftResult
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`DriftResult` unpacks as `(statistic, pvalue)` for drop-in compatibility and
|
|
72
|
+
also exposes `.pvalue`, `.n_eff`, `.block_length`, `.correction`.
|
|
73
|
+
|
|
74
|
+
### Available corrections
|
|
75
|
+
|
|
76
|
+
| `correction=` | Description | Extra deps |
|
|
77
|
+
|-----------------|-----------------------------------------------------|----------------|
|
|
78
|
+
| `block_perm` | Block permutation, Politis–White block length | — |
|
|
79
|
+
| `ess_adjust` | ESS-adjusted analytic null (cheapest; KS/CvM/AD) | — |
|
|
80
|
+
| `naive` | i.i.d. null — baseline / reference only | — |
|
|
81
|
+
| `thinning` | Keep every k-th obs to approximate independence | — |
|
|
82
|
+
| `dep_wild` | Dependent wild bootstrap (Shao 2010) | — |
|
|
83
|
+
| `mbb` | Moving block bootstrap (Künsch 1989) | `[research]` |
|
|
84
|
+
| `cbb` | Circular block bootstrap | `[research]` |
|
|
85
|
+
| `stationary` | Stationary bootstrap (Politis–Romano 1994) | `[research]` |
|
|
86
|
+
| `prewhiten` | **Cautionary only** — changes the hypothesis | `[research]` |
|
|
87
|
+
|
|
88
|
+
## Method
|
|
89
|
+
|
|
90
|
+
`driftdep` benchmarks and operationalizes dependence corrections for two-sample
|
|
91
|
+
distributional drift tests. The core insight: when observations within a
|
|
92
|
+
monitoring window are autocorrelated, the i.i.d. null distribution of KS, CvM,
|
|
93
|
+
and related statistics is stochastically dominated, causing severe over-rejection.
|
|
94
|
+
|
|
95
|
+
The recommended default — ESS-adjusted block permutation — permutes contiguous
|
|
96
|
+
blocks of length *b* (preserving within-block dependence) and selects *b*
|
|
97
|
+
automatically via Politis–White (2004). Empirical size recovers to ≈ α across
|
|
98
|
+
AR(1), ARFIMA, and GARCH dependence structures. Size-adjusted power is within
|
|
99
|
+
5–7 percentage points of the uncalibrated naive test at moderate effect sizes.
|
|
100
|
+
|
|
101
|
+
All corrections already exist in the literature; this package operationalizes
|
|
102
|
+
them in a unified interface with a validated default.
|
|
103
|
+
|
|
104
|
+
## Reproduce paper results
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/vivekch2018/driftdep
|
|
108
|
+
cd driftdep
|
|
109
|
+
pip install -e ".[research]"
|
|
110
|
+
make data # download public datasets (requires internet)
|
|
111
|
+
make reproduce # regenerate all figures and tables from seeds
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Citation
|
|
115
|
+
|
|
116
|
+
```bibtex
|
|
117
|
+
@misc{chaudhary2025driftdep,
|
|
118
|
+
author = {Chaudhary, Vivek},
|
|
119
|
+
title = {Two-Sample Drift Tests Break Under Serial Dependence:
|
|
120
|
+
A Benchmark and Deployable Correction},
|
|
121
|
+
year = {2025},
|
|
122
|
+
note = {Preprint},
|
|
123
|
+
url = {https://github.com/vivekch2018/driftdep},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# manifest.yaml — factor grid and root seed for driftdep paper experiments.
|
|
2
|
+
# All results are fully reproducible from this file.
|
|
3
|
+
# See CLAUDE.md §7 for the reproducibility contract.
|
|
4
|
+
# See the paper plan §8 for the scientific design rationale.
|
|
5
|
+
|
|
6
|
+
root_seed: 20250101
|
|
7
|
+
|
|
8
|
+
# ── experimental design ────────────────────────────────────────────────────────
|
|
9
|
+
# Strategy: screening design first (vary one factor family at a time around the
|
|
10
|
+
# baseline cell), then focused full-factorial on the interesting region.
|
|
11
|
+
# This avoids the millions-of-cells full cross-product while covering the space.
|
|
12
|
+
|
|
13
|
+
screening:
|
|
14
|
+
description: >
|
|
15
|
+
Vary one factor family at a time around the baseline cell to identify
|
|
16
|
+
where size inflation is largest. Used to select the focused-factorial region.
|
|
17
|
+
baseline:
|
|
18
|
+
test: ks
|
|
19
|
+
correction: naive
|
|
20
|
+
dgp: ar1
|
|
21
|
+
rho: 0.5
|
|
22
|
+
n: 500
|
|
23
|
+
marginal: gaussian
|
|
24
|
+
alpha: 0.05
|
|
25
|
+
n_reps: 1000
|
|
26
|
+
|
|
27
|
+
focused_factorial:
|
|
28
|
+
description: >
|
|
29
|
+
Full factorial on the region identified by screening. This is the source
|
|
30
|
+
of Table 1, Figs 1, 4, 5, 6 in the paper.
|
|
31
|
+
tests:
|
|
32
|
+
- ks
|
|
33
|
+
- cvm
|
|
34
|
+
- ad
|
|
35
|
+
- energy
|
|
36
|
+
- mmd_rbf
|
|
37
|
+
- wass
|
|
38
|
+
- psi
|
|
39
|
+
- js
|
|
40
|
+
corrections:
|
|
41
|
+
- naive
|
|
42
|
+
- block_perm
|
|
43
|
+
- mbb
|
|
44
|
+
- cbb
|
|
45
|
+
- stationary
|
|
46
|
+
- dep_wild
|
|
47
|
+
- ess_adjust
|
|
48
|
+
- thinning
|
|
49
|
+
- prewhiten
|
|
50
|
+
dgps:
|
|
51
|
+
- ar1
|
|
52
|
+
- arma11
|
|
53
|
+
- ar2cyclic
|
|
54
|
+
- arfima0d0
|
|
55
|
+
- garch11
|
|
56
|
+
- seasonal_ar
|
|
57
|
+
- heavy_tail_ar1
|
|
58
|
+
rho_grid: [0.0, 0.3, 0.5, 0.7, 0.9, -0.3]
|
|
59
|
+
arfima_d_grid: [0.1, 0.2, 0.4]
|
|
60
|
+
n_grid: [100, 250, 500, 1000, 2000]
|
|
61
|
+
marginals:
|
|
62
|
+
- gaussian
|
|
63
|
+
- student_t4
|
|
64
|
+
- lognormal
|
|
65
|
+
- bimodal
|
|
66
|
+
alpha: 0.05
|
|
67
|
+
alpha_appendix: [0.01, 0.10]
|
|
68
|
+
n_reps_size: 10000 # tight CIs around 0.05 require many reps
|
|
69
|
+
n_reps_power: 5000
|
|
70
|
+
|
|
71
|
+
# ── block length selection ─────────────────────────────────────────────────────
|
|
72
|
+
block_length:
|
|
73
|
+
methods:
|
|
74
|
+
- politis_white_auto # arch.bootstrap.optimal_block_length (PW 2004)
|
|
75
|
+
- fixed_n13 # b = floor(n^(1/3)); simple rule
|
|
76
|
+
|
|
77
|
+
# ── streaming experiment ───────────────────────────────────────────────────────
|
|
78
|
+
streaming:
|
|
79
|
+
window_sizes: [100, 250, 500]
|
|
80
|
+
alpha_grid: [0.01, 0.05, 0.10]
|
|
81
|
+
n_reps_arl: 5000
|
|
82
|
+
drift_types:
|
|
83
|
+
- location_shift
|
|
84
|
+
- scale_change
|
|
85
|
+
- shape_change
|
|
86
|
+
|
|
87
|
+
# ── power alternatives ─────────────────────────────────────────────────────────
|
|
88
|
+
# All reported at size-adjusted power with matched dependence.
|
|
89
|
+
power_alternatives:
|
|
90
|
+
- type: location_shift
|
|
91
|
+
delta_sd_units: [0.2, 0.5, 1.0, 2.0]
|
|
92
|
+
- type: scale_change
|
|
93
|
+
variance_multiplier: [1.5, 2.0, 4.0]
|
|
94
|
+
- type: shape_change
|
|
95
|
+
description: skewness/kurtosis shift at matched mean & variance
|
|
96
|
+
- type: partial_contamination
|
|
97
|
+
contamination_fraction: [0.1, 0.2, 0.5]
|
|
98
|
+
- type: gradual_drift
|
|
99
|
+
description: linear interpolation of marginal across window
|