Skip to content

Tag: add percentile()/quantile() — toolbox-free order-statistics primitive (P50/P95/P99 & IQR, order-stat sibling to getStats #223) #339

Description

@HanSur94

Problem / motivation

The Tag analysis family has grown a rich set of reduction primitives, but its
statistics surface only covers moment statistics. #223 (getStats) was scoped
explicitly to N / Min / Max / Mean / Rms / Std — the summing/squaring family — and
deliberately left out order statistics (median, percentiles, IQR). Meanwhile #251
(HistogramWidget: overlay distribution percentiles) shows the domain actively wants
percentiles, but it renders them as a widget display overlay — there is no way to get
percentile values from a Tag programmatically.

Verified gap:

Order statistics answer questions moment statistics cannot:

These are canonical sensor-analysis / loads / duty-cycle descriptors. Today an engineer must
drop to getXY and hand-roll a sort every time.

Proposed feature

A base-Tag convenience method returning percentile values of the resolved series,
toolbox-free, inherited by every subclass:

p95 = tag.percentile(95);              % scalar level  -> scalar value
pv  = tag.percentile([5 50 95]);       % vector levels -> vector of values
pv  = tag.percentile([5 95], t0, t1);  % over a time window (mirrors getStats #223 range args)
m   = tag.median();                     % optional convenience == percentile(50)
r   = tag.iqr();                        % optional convenience == percentile(75) - percentile(25)

Rough sketch

Single method on libs/SensorThreshold/Tag.m (base class — same placement as getStats
#223 and the rest of the family), built on the existing accessors getXY (Tag.m:120) /
getXYRange(obj, tStart, tEnd) (Tag.m:125, which already returns the full series for
empty bounds):

  1. [~, Y] = obj.getXYRange(tStart, tEnd) (or getXY when no range) — reuse the same
    range plumbing Tag: add a public getStats() statistics primitive (N/Min/Max/Mean/Rms/Std over a series or time range) #223 uses.
  2. Drop NaNs; guard the empty series; validate levels are in [0, 100].
  3. Ys = sort(Y(:)); then the standard linear-interpolation percentile:
    i = p/100 * (n-1) + 1, interpolate between Ys(floor(i)) and Ys(ceil(i)).
  4. Return values matching the shape of the requested levels.

Pure-function, read-only: no new property, no toStruct/fromStruct change, no
Tag/DashboardWidget contract touched. Output is a plain numeric array — same downstream shape
as the rest of the family.

Value

Medium-High. Completes the Tag statistics surface with the robust / order-statistic
dimension that #223 deliberately left out and that #251 shows is wanted; unlocks
exceedance-level, median, and IQR workflows the library currently forces users to leave for.

Constraints check

  • Toolbox-free: ⚠️ importantprctile and quantile are Statistics Toolbox
    functions and must not be used. The sort + linear-interpolation implementation is base
    MATLAB and Octave, keeping the method toolbox-free.
  • Backward-compatible: a brand-new method cannot break existing scripts or serialized
    dashboards; no serialization or contract change.
  • Pure MATLAB/Octave: yes (sort + arithmetic only).
  • Works through the existing Tag contract: yes — reuses getXY / getXYRange.

Effort estimate

S — one method (+ optional median / iqr one-liners) in a single file, plus a focused
test (known vector → known percentiles at endpoints and interior; scalar-vs-vector level
input; NaN handling; empty-series guard; time-range path).

Dedup / relationship note (honest triage flag)


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