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 y². 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.

Metadata

Release files for regscan 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for regscan 0.1.2
File Size Uploaded Provenance Actions
regscan-0.1.2.tar.gz 28.0 kB With provenance Details for regscan-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for regscan 0.1.2
File Interpreter ABI Platform Actions
regscan-0.1.2-py3-none-any.whl Python 3 none any Details for regscan-0.1.2-py3-none-any.whl

Total release size: 53.2 kB

Release files / regscan-0.1.2.tar.gz

Download URL regscan-0.1.2.tar.gz
Size 28.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3185754ea249766ca6845aa10e74847b2f9460d804c3476e9a0401a257e0e0cd
BLAKE2b-256 checksum
How to use checksums
caaa4d4be2fbc6790fd80102f47134c58293779cefb152561e2a738b77134163
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / regscan-0.1.2-py3-none-any.whl

Download URL regscan-0.1.2-py3-none-any.whl
Size 25.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c47ec1276bb75a285826e8b3525debffb48ddbb28a1ec84b809071f6f64c367f
BLAKE2b-256 checksum
How to use checksums
850f047291624422a321c50302c651b75697b1a77833ec3fd43f4e84fc7e47fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Security for regscan 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for regscan-0.1.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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