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.
Files changed (66) hide show
  1. driftdep-0.1.0/.claude/settings.local.json +15 -0
  2. driftdep-0.1.0/.gitattributes +2 -0
  3. driftdep-0.1.0/.github/workflows/publish.yml +88 -0
  4. driftdep-0.1.0/.gitignore +56 -0
  5. driftdep-0.1.0/LICENSE +21 -0
  6. driftdep-0.1.0/Makefile +67 -0
  7. driftdep-0.1.0/PKG-INFO +155 -0
  8. driftdep-0.1.0/README.md +129 -0
  9. driftdep-0.1.0/manifest.yaml +99 -0
  10. driftdep-0.1.0/pyproject.toml +68 -0
  11. driftdep-0.1.0/research/__init__.py +17 -0
  12. driftdep-0.1.0/research/analysis/__init__.py +16 -0
  13. driftdep-0.1.0/research/analysis/fig1_false_alarm.py +134 -0
  14. driftdep-0.1.0/research/analysis/fig2_ks_null.py +123 -0
  15. driftdep-0.1.0/research/analysis/fig3_ess_indicator.py +95 -0
  16. driftdep-0.1.0/research/analysis/fig4_size_after_correction.py +129 -0
  17. driftdep-0.1.0/research/analysis/fig5_power.py +124 -0
  18. driftdep-0.1.0/research/analysis/fig6_arl.py +88 -0
  19. driftdep-0.1.0/research/analysis/table1_size.py +135 -0
  20. driftdep-0.1.0/research/analysis/table2_realdata.py +77 -0
  21. driftdep-0.1.0/research/corrections/__init__.py +39 -0
  22. driftdep-0.1.0/research/corrections/block_perm.py +105 -0
  23. driftdep-0.1.0/research/corrections/bootstrap_corrections.py +226 -0
  24. driftdep-0.1.0/research/corrections/dep_wild.py +155 -0
  25. driftdep-0.1.0/research/corrections/ess_adjust.py +159 -0
  26. driftdep-0.1.0/research/corrections/naive.py +77 -0
  27. driftdep-0.1.0/research/corrections/prewhiten.py +147 -0
  28. driftdep-0.1.0/research/corrections/thinning.py +96 -0
  29. driftdep-0.1.0/research/data/__init__.py +14 -0
  30. driftdep-0.1.0/research/data/download_fred.py +64 -0
  31. driftdep-0.1.0/research/data/download_m4.py +88 -0
  32. driftdep-0.1.0/research/data/download_uci.py +122 -0
  33. driftdep-0.1.0/research/dgp/__init__.py +7 -0
  34. driftdep-0.1.0/research/dgp/alternatives.py +98 -0
  35. driftdep-0.1.0/research/dgp/ar1.py +61 -0
  36. driftdep-0.1.0/research/dgp/arfima.py +78 -0
  37. driftdep-0.1.0/research/dgp/arma.py +45 -0
  38. driftdep-0.1.0/research/dgp/garch.py +89 -0
  39. driftdep-0.1.0/research/dgp/heavy_tail.py +38 -0
  40. driftdep-0.1.0/research/dgp/seasonal.py +63 -0
  41. driftdep-0.1.0/research/experiments/__init__.py +13 -0
  42. driftdep-0.1.0/research/experiments/compute_cost.py +87 -0
  43. driftdep-0.1.0/research/experiments/power.py +244 -0
  44. driftdep-0.1.0/research/experiments/realdata.py +241 -0
  45. driftdep-0.1.0/research/experiments/size.py +299 -0
  46. driftdep-0.1.0/research/experiments/streaming.py +338 -0
  47. driftdep-0.1.0/research/harness/__init__.py +17 -0
  48. driftdep-0.1.0/research/harness/interfaces.py +44 -0
  49. driftdep-0.1.0/research/harness/registry.py +27 -0
  50. driftdep-0.1.0/research/harness/runner.py +155 -0
  51. driftdep-0.1.0/research/results/.gitkeep +0 -0
  52. driftdep-0.1.0/research/tests_battery/__init__.py +29 -0
  53. driftdep-0.1.0/research/tests_battery/stats.py +211 -0
  54. driftdep-0.1.0/src/driftdep/__init__.py +470 -0
  55. driftdep-0.1.0/src/driftdep/_blocklen.py +42 -0
  56. driftdep-0.1.0/src/driftdep/_blockperm.py +69 -0
  57. driftdep-0.1.0/src/driftdep/_ess.py +120 -0
  58. driftdep-0.1.0/src/driftdep/_stats.py +124 -0
  59. driftdep-0.1.0/src/driftdep/py.typed +0 -0
  60. driftdep-0.1.0/tests/conftest.py +7 -0
  61. driftdep-0.1.0/tests/test_m1.py +204 -0
  62. driftdep-0.1.0/tests/test_m2.py +209 -0
  63. driftdep-0.1.0/tests/test_m3.py +354 -0
  64. driftdep-0.1.0/tests/test_m4.py +135 -0
  65. driftdep-0.1.0/tests/test_package.py +17 -0
  66. 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,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -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.
@@ -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/
@@ -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
@@ -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