Skip to content

Tag: add lagCorrelation(other, MaxLag) — toolbox-free cross-correlation & best time-lag between two tags (time-delay sibling of correlate #340) #341

Description

@HanSur94

Problem / motivation

The relational (two-tag) analysis dimension was opened by correlate(other) (#340), which returns a single scalar Pearson r at zero lag — instantaneous agreement. It answers "do A and B move together?" but structurally cannot answer "how far does channel B trail channel A?".

Transport delay and propagation lag are canonical sensor-analysis questions:

  • how long does an upstream temperature change take to reach a downstream sensor?
  • how far does a vibration wavefront trail its source?
  • does a control output lead a process variable, and by how much?

correlate (#340) only sees zero-lag agreement, so two perfectly-related-but-delayed channels look uncorrelated to it. The best-lag argmax of a cross-correlation is the standard toolbox-free answer. Today a FastSense user must drop to getXY on both tags, resample onto a uniform grid, hand-roll the lagged sums, and find the argmax — exactly the boilerplate the Tag analysis family exists to eliminate.

Proposed feature

A base-Tag convenience method returning the best time-lag (and the correlation at that lag) between two tags, toolbox-free:

dt      = tagA.lagCorrelation(tagB);              % scalar: best lag in TIME units (B lags A by +dt)
[dt, r] = tagA.lagCorrelation(tagB);              % + Pearson r at that lag
[dt, r, lags, rr] = tagA.lagCorrelation(tagB);    % + full lag axis (time) and r-vs-lag curve
...  = tagA.lagCorrelation(tagB, 'MaxLag', 10);   % cap the search window (time units); default = fraction of overlap
...  = tagA.lagCorrelation(tagB, t0, t1);         % over a time window (mirrors correlate/getStats range args)

Rough sketch

Single method on libs/SensorThreshold/Tag.m (base class → every subclass inherits it — same placement as correlate #340, getStats #223, spectrum #338, percentile #339). Builds on accessors already on every Tag: getXYRange (Tag.m:125) and valueAt (Tag.m:161, overridden per subclass):

  1. [X, ~] = obj.getXYRange(t0, t1) for A over the overlapping window; establish a uniform grid tg = t0:dt:t1 (dt = median sample spacing of A, or explicit) so a lag index maps cleanly to a time delay — the one piece of real design surface.
  2. Sample both tags onto tg via ZOH: a = arrayfun(@(t) obj.valueAt(t), tg), b = arrayfun(@(t) other.valueAt(t), tg) — reuses the existing per-subclass valueAt alignment primitive (same convention correlate Tag: add correlate(other) — toolbox-free Pearson correlation between two tags (first relational/two-tag analysis primitive) #340 adopts).
  3. Drop pairwise NaNs; guard n < 2 and zero-variance (constant channel → NaN, documented).
  4. Cross-correlate toolbox-free over ±MaxLag samples (a short sliding-window loop of mean-removed normalized sums). Return best-lag dt = bestLagIndex * dt (+ optional r, lag axis, r-vs-lag curve).

Pure-function, read-only; no new property, no toStruct/fromStruct change, no Tag/DashboardWidget contract touched. The lag axis + r-vs-lag curve are a natural [x, y] pair the caller can hand to a standalone FastSense/ad-hoc plot.

Value

High — adds time-delay estimation, a core machinery/process-analysis capability that correlate (#340) structurally cannot provide; directly serves transport-delay, propagation, and lead/lag control-loop workflows that force the user out of FastSense today.

Constraints check

Fit note (for triage): cross-correlation needs a uniform time grid to convert a lag index into a time delay — more design surface than the scalar correlate (#340). The method should state its policy in its header: ZOH-resample both tags onto a uniform grid (dt = median A-spacing or explicit) over the overlapping window, and default MaxLag to a fraction of that window. Documented design decision, not a blocker; reuses machinery already in the repo (valueAt ZOH; the intent of resampleUniform #308) and is toolbox-free either way.

Effort estimate

M — one method in a single file, plus a focused test: delayed copy of a signal → recovered lag ≈ known delay; zero-lag identical tags → lag ≈ 0, r ≈ 1; MaxLag clamping; ZOH alignment of mismatched timestamps; NaN and n < 2 / zero-variance guards; time-range path.

Dedup

Clean. No issue/PR mentions cross-correlation, xcorr, lag, time-delay, or coherence. correlate (#340) is the zero-lag scalar relational primitive — this is its time-delay sibling, orthogonal. DerivedTag/CompositeTag (arithmetic combination) and PR #206 (cross-machine comparison view) are different concerns. The entire unary analysis family (#223/#258/#306/#308/#312/#316/#326/#327/#328/#329/#338/#339) is orthogonal.


AI-proposed via /feature-scout — needs a human product decision before implementation.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions