Skip to main content

regscan

Regression-based scan statistics for detecting interval anomalies in smoothly varying 1-D signals.

Given a signal and a function family F, the score of an interval I = [a, b] is

S(I) = 1 - (SR_I + SR_O) / SR_A

where SR_A, SR_I and SR_O are the sums of squared residuals from fitting F to the whole signal, to the inside of I, and to the outside. The score approaches 0 when splitting the signal explains nothing and approaches 1 when it explains everything. A scan returns the highest-scoring interval.

Installation

pip install regscan

Requires Python 3.10 or later. The only dependencies are numpy and numba.

The kernel scan is a tight scalar loop, which numba compiles well and numpy vectorizes poorly, so it is JIT-compiled. The first call in a process pays a one-off compilation cost; numba caches the result on disk thereafter.

Quick start

import numpy as np
import regscan

t = np.linspace(0, 1, 300)
x = 4.7 + 0.10 * t + 0.05 * np.random.default_rng(0).normal(0, 1, 300)
x[140:170] -= 0.25                      # the anomaly

res = regscan.scan(x, method="nwkr_gaussian", w=12)
res.score, res.a, res.b                 # 0.248, 140, 169
res.width_frac, res.at_edge()           # 0.10, False

On the same signal with no anomaly planted, nwkr_gaussian scores 0.036 while mean scores 0.185. The constant family is responding to the ramp, not to an anomaly.

Notation

symbol meaning
n length of the signal, in samples
w window width: the kernel bandwidth for F_KR, and the scale of local structure any family's fit can follow
r range cap: the longest candidate interval considered, 3w by default
d polynomial degree for the F_d family (poly_deg1 is d = 1)
a, b inclusive start and end indices of a candidate interval
I the candidate interval [a, b]

n is fixed by the data. w and r are the parameters worth thinking about, and both may be passed explicitly to regscan.scan.

Methods

method family model cost
mean F_0 constant O(nr)
poly_deg1 ... poly_deg3 F_d degree-d polynomial O(nr d³)
nwkr_gaussian, nwkr_laplace F_KR Nadaraya-Watson kernel regression O(nrw)
krr_gaussian, krr_laplace F_KRR kernel ridge regression O(n⁴w)

regscan.available() lists them at run time.

F_KR is the method this package exists for. A weak family such as F_0 or F_1 cannot represent a curved background, so it lowers the residual by splitting the interval wherever the curvature is worst, which flags smooth structure as an anomaly. A kernel fit follows that structure instead, so the structure enters SR_A, SR_I and SR_O alike and cancels out of the score.

Choosing w and r

w sets the scale of structure the fit can follow. Set it too small and the kernel reproduces the anomaly itself, which cancels it from the score; set it too large and the fit cannot follow the background, which is the failure mode of the weaker families.

r is the longest interval the scan will consider. An anomaly wider than r cannot be returned at all, and cost falls linearly with r, so it is the parameter to reach for when the approximate width of the feature is known.

regscan.scan(x, method="nwkr_gaussian", w=12, r=90)

When omitted, w defaults to max(3, n // 16) and r to 3 * w. These defaults exist so that a scan runs unattended; they are not recommendations.

Sweeping w across a range and checking whether (a, b) holds steady is a cheap way to distinguish a resolved feature from an artifact of the bandwidth. A real interval stays put, whereas one that tracks w is measuring the kernel.

Configuration

from regscan import ScanConfig

cfg = ScanConfig(
    kernel="laplace",     # "gaussian" or "laplace"
    buffer=24,            # exclude this many samples at each end
    min_width=0.01,       # interval length bounds, as a fraction of n
    max_width=0.25,
)
regscan.scan(x, method="nwkr_gaussian", w=16, config=cfg)

ScanConfig is immutable and passed explicitly. Nothing is stored in module globals, so scanning several methods in one process cannot leak settings between them.

buffer excises samples rather than merely forbidding interval placement there, so the fit, SR_A and the outside residuals are all computed on the trimmed signal. Because a non-zero buffer suppresses detections at the ends of the signal, give every family the same value when comparing them.

max_width is worth capping. As an interval approaches n/2 the inside and outside become comparable in size, the statistic stops discriminating, and the maximizer drifts toward whatever split best absorbs slow curvature.

Super-resolution

F_KR can block-mean the signal, scan the shorter version, then search the original samples around the winning blocks to recover exact endpoints.

from regscan import ScanConfig

regscan.scan(x, method="nwkr_gaussian", w=100,
             config=ScanConfig(super_resolution=4))     # or "auto"

At n = 1600 with w = 100:

factor time interval
1 (exact) 3.11 s (700, 819)
2 0.44 s (700, 819)
4 0.08 s (700, 819)
8 0.02 s (704, 815)

A factor of 4 runs 39 times faster and returns the same interval. A factor of 8 does not, which is the trade-off: the coarse pass locates each endpoint only to within a block of factor samples, and a wrong block gives a wrong answer.

Refinement therefore searches one block either side of each coarse endpoint. This matters more than it may appear. Block means smooth an anomaly's edges, so the coarse pass selects a neighboring block often enough that confining the search to the winning block alone recovered the exact interval in only 20 of 40 trials at factor 4. Including the neighbors recovered all 40.

It remains an approximation. Verify against super_resolution=1 on a sample of your own data before relying on it.

Passing "auto" derives the factor from the signal length: 1 below 450 samples, then doubling at 900, 1800 and so on. sr_cap bounds it, which matters for narrow features, since an interval must survive decimation to be found. The default is 1, which scans every sample exactly.

Performance

One full scan with w = n // 16, after JIT warm-up:

n mean poly_deg1 nwkr_gaussian
100 3 ms 44 ms 4 ms
200 12 ms 181 ms 18 ms
400 51 ms 754 ms 83 ms
800 187 ms 2.7 s 0.45 s
1600 738 ms 11.0 s 3.2 s

At n = 800, F_KR is six times faster than F_1 despite fitting a far richer model. That is the practical case for it: the polynomial family pays O(d³) for every candidate interval, whereas the kernel family pays O(r) to extend one.

Each family reaches its stated complexity by carrying state rather than refitting. mean scores an interval in O(1) from prefix sums of y and . poly_deg1 uses prefix sums of the moments t^p and t^p y, so a fit costs O(d³) to solve regardless of how many samples the interval spans. F_KR grows an interval one sample at a time and updates in O(r): the inside buffer and sse_in, the nin and din arrays holding the inside points' kernel contribution to every index, and sse_out, obtained from those by subtraction from the all-points totals. Since sse_out is adjusted rather than recomputed, it is refreshed exactly on a fixed cadence to keep floating-point error from accumulating.

Super-resolution reduces these times substantially again on longer signals.

Comparing scores

Scores are comparable within a family but not across families. Each family divides by its own SR_A, and a kernel fit has a smaller SR_A than a constant fit before any interval is chosen, so the same interval scores differently under F_0 and F_KR. Compare families by rank, by whether they agree on the interval, or by the contrast between anomalous and clean signals, rather than by absolute score.

Citation

Rakib et al., Efficient Regression Models for Scan Statistics, arXiv:2608.22201 (2026).

@misc{rakib2026efficientregressionmodelsscan,
      title={Efficient Regression Models for Scan Statistics},
      author={Gazi Abdur Rakib and Tristan Ashton and Ryan A. Loomis and Brian S. Mason and Eric J. Murphy and Ci Xue and Jeff M. Phillips},
      year={2026},
      eprint={2608.22201},
      archivePrefix={arXiv},
      primaryClass={stat.ME},
      url={https://arxiv.org/abs/2608.22201},
}

License

BSD 3-Clause. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

regscan-0.1.2.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

regscan-0.1.2-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file regscan-0.1.2.tar.gz.

File metadata

  • Download URL: regscan-0.1.2.tar.gz
  • Upload date:
  • Size: 28.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for regscan-0.1.2.tar.gz
Algorithm Hash digest
SHA256 3185754ea249766ca6845aa10e74847b2f9460d804c3476e9a0401a257e0e0cd
MD5 0787a8986ce383e5b585eade212e6f92
BLAKE2b-256 caaa4d4be2fbc6790fd80102f47134c58293779cefb152561e2a738b77134163

See more details on using hashes here.

Provenance

The following attestation bundles were made for regscan-0.1.2.tar.gz:

Publisher: publish.yml on BeardyMan37/regscan

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file regscan-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: regscan-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 25.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for regscan-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c47ec1276bb75a285826e8b3525debffb48ddbb28a1ec84b809071f6f64c367f
MD5 e892fd67bdad47ca9ee08d3fc03d7d6d
BLAKE2b-256 850f047291624422a321c50302c651b75697b1a77833ec3fd43f4e84fc7e47fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for regscan-0.1.2-py3-none-any.whl:

Publisher: publish.yml on BeardyMan37/regscan

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page