Skip to contents

Many outlier-detection workflows produce per-detector flags (flagged_by_bridge, flagged_by_prob, flagged_by_speed, flagged_by_detour) and then ask a separate question: given the agreement structure across detectors, which fixes are outliers? mt_flag_consensus() is the canonical home for that decision.

Usage

mt_flag_consensus(
  x,
  mode = c("class_aware", "strict", "majority", "speed_trusted", "any", "custom"),
  custom = NULL,
  bridge_col = "flagged_by_bridge",
  prob_col = "flagged_by_prob",
  speed_col = "flagged_by_speed",
  detour_col = "flagged_by_detour"
)

Arguments

x

A move2 object that already has per-detector flag columns from a cascade run or from running the four primitives standalone. Missing flag columns are treated as identically FALSE (no flags from that detector).

mode

Character. Which consensus rule to apply. One of "class_aware" (default), "strict", "majority", "speed_trusted", "any", or "custom". See Consensus modes below.

custom

Function used when mode = "custom". Receives four logical vectors of equal length (by_bridge, by_prob, by_speed, by_detour) and must return a single logical vector of the same length giving the per-fix outlier decision. Ignored unless mode = "custom".

bridge_col, prob_col, speed_col, detour_col

Character. Names of the columns in x that hold the per-detector flags. Defaults match the columns emitted by mt_clean_track and the four detector primitives (mt_flag_outliers_bridge, mt_flag_outliers, mt_flag_speed_cap, mt_flag_outliers_detour). If a column is missing from x, that detector is treated as silent.

Value

The input x with an updated is_outlier logical column. Other per-fix columns are passed through unchanged. If is_outlier already existed on input, it is replaced.

Details

The function is used internally by mt_clean_track() each iteration, but is also exported so users running their own cascades (or running the four primitives standalone) can apply the same consensus rules to their own per-detector flag columns.

Consensus modes

"class_aware" (default)

Empirically validated default (round-4 audit, 2026-05-11). A fix is flagged if ANY of the following class rules fires:

  • consensus: at least 3 of the 4 detectors agree

  • geometric_spike: bridge AND detour (symmetric out-and-back is geometrically impossible regardless of state; the autodiff framework is blind to it, so kinematic confirmation would defeat detection)

  • state_anomaly: (bridge OR detour) AND speed

  • kinematic_confluence: (bridge OR detour) AND prob

Each detector contributes where its structural strengths apply, rather than being a symmetric voter. The class taxonomy used downstream (error_class) shares this structure.

"strict"

(bridge OR detour) AND (prob OR speed). At least one geometric AND one kinematic confirmer. Highest precision; on clean synthetic ground truth (CPF tracks) achieves zero false positives.

"majority"

Flag iff at least 2 of the 4 detectors agree. Tolerates one silent detector; slightly higher recall than "strict", slightly lower precision.

"speed_trusted"

speed OR ((bridge OR detour) AND prob). Trusts the speed detector alone because in its "auto" mode it is already dip-test-validated. Catches extreme-speed transitions that strict misses, at the cost of flagging both endpoints of fast steps.

"any"

Union of all four detectors. Maximum recall, lowest precision. Rarely the right choice for production pipelines.

"custom"

Apply a user-supplied function. See the custom argument.

Composing with custom cascades

If you have built your own cascade that does not use one of the four standard detectors (or uses them under different column names), supply the *_col arguments to point at your column names. Detectors with no corresponding column are silently treated as FALSE, so a three-detector cascade (e.g. without detour) gives the expected reduced-form rules.

For consensus rules outside the documented set, use mode = "custom" with a function of four logical vectors. Inside the function you can use base-R logical operations, rowSums(cbind(...)) for vote counts, etc.

See also

mt_clean_track (which uses this function internally each iteration); mt_flag_outliers_bridge, mt_flag_outliers, mt_flag_speed_cap, mt_flag_outliers_detour (the four primitives that emit the per-detector flag columns this function consumes).

Examples

if (FALSE) { # \dontrun{
# Default: class_aware on a cleaned cascade output
cleaned <- mt_clean_track(track)
cleaned <- mt_flag_consensus(cleaned)   # same as mt_clean_track default

# More conservative: require a geometric AND a kinematic confirmer
cleaned <- mt_flag_consensus(cleaned, mode = "strict")

# User-defined: bridge alone (geometric impossibility only)
cleaned <- mt_flag_consensus(cleaned, mode = "custom",
                             custom = function(b, p, s, d) b)
} # }