Layer 2: ADEngine Lifecycle Orchestration

ADEngine is PyOD’s anomaly detection lifecycle engine. It profiles your data, selects benchmark-backed detectors from PyOD’s 60-detector catalog, runs multiple detectors in parallel, computes consensus scores, and reports descriptive diagnostics, all in one call.

Use Layer 2 when you are not sure which detector to pick.

Full example: investigate_example.py


One-shot Investigation

The simplest way to use ADEngine is investigate(), which runs the full workflow (profile, plan, run, analyze) in one call:

import numpy as np
from pyod.utils.ad_engine import ADEngine

np.random.seed(42)  # for reproducible output
data = np.genfromtxt('examples/data/cardio.csv', delimiter=',', skip_header=1)
X = data[:, :-1]

engine = ADEngine()
state = engine.investigate(X)

print(state.analysis['summary'])
# "172 anomalies detected out of 1831 samples (9.4%) by
#  consensus of 3 detectors. Quality: high (0.83)."

print(state.quality['verdict'])            # 'high'
print(state.quality['overall'])            # 0.83

Multi-Detector Consensus

ADEngine runs the top-3 detectors from PyOD’s knowledge base and computes a consensus:

state = engine.investigate(X)

# Per-detector results
for result in state.results:
    print("%s: %d anomalies in %.2fs" % (
        result['detector_name'],
        result['n_anomalies'],
        result['runtime_seconds']))

# Consensus
print("Consensus scores:", state.consensus['scores'][:5])
print("Agreement (Spearman):", state.consensus['agreement'])
print("Disagreements:", len(state.consensus['disagreements']))

Quality Assessment

ADEngine reports three descriptive diagnostics of a run. They summarize the score distribution and cross-detector behavior. They are not a label-free guarantee that the results are correct (see the note below):

  • Separation – relative mean score gap between the run’s flagged set and the rest ([0, 1]). It is computed from the run’s own predicted labels, so it is descriptive only; it does not show that the cutoff or the vote is correct.

  • Agreement – mean pairwise Spearman correlation between detectors ([0, 1]). The most useful of the three: low agreement flags inputs with no shared structure (near-noise), where the detectors rank points differently.

  • Stability – standardized score gap at the rank-k cutoff ([0, 1]). Low values mean many tied scores near the threshold, so the flagged set is sensitive to the contamination value.

q = state.quality
print("Separation:", q['separation'])      # 1.00
print("Agreement:",  q['agreement'])       # 0.68
print("Stability:",  q['stability'])       # cutoff-gap value (data-dependent)
print("Overall:",    q['overall'])         # 0.83
print("Verdict:",    q['verdict'])         # 'high'

Verdicts are 'high' (>=0.7), 'medium' (>=0.4), or 'low' (<0.4).

Note

The verdict is a heuristic summary of the score distribution and cross-detector behavior, not a guarantee that the results are correct. Use it as a rough signal, not as a basis for trusting results without labels: low agreement is the most reliable component and flags near-noise inputs, while separation is descriptive only. To judge correctness, validate against held-out labels or a domain review.


Session API (Step by Step)

For finer control, use the session methods individually:

engine = ADEngine()
state = engine.start(X)                    # phase='profiled'
state = engine.plan(state)                 # phase='planned'
state = engine.run(state)                  # phase='detected'
state = engine.analyze(state)              # phase='analyzed'

At each step, state.next_action tells the caller what to do next. This is how agents follow the workflow without knowing OD.


Iteration

If the results are not what you want, iterate with structured feedback:

# Adjust contamination (structured feedback)
state = engine.iterate(state, {"action": "adjust_contamination", "value": 0.05})
state = engine.run(state)
state = engine.analyze(state)

# Exclude a detector
state = engine.iterate(state, {"action": "exclude", "detectors": ["IForest"]})

# Add a detector
state = engine.iterate(state, {"action": "include", "detectors": ["ECOD"]})

# Or pass natural language (best-effort parsing):
state = engine.iterate(state, "too many false positives")
# state.next_action == 'confirm_with_user' with proposed_change

Reports

Generate text or JSON reports:

text_report = engine.report(state, format='text')
print(text_report)

json_report = engine.report(state, format='json')
# Native Python dict with session + best_detector sections

Data Type Routing

ADEngine routes automatically based on data type:

Data Type

Default Detectors

Benchmark

Tabular

IForest, ECOD, KNN

ADBench (NeurIPS 2022)

Time Series

KShape, SpectralResidual, TimeSeriesOD

TSB-AD (NeurIPS 2024)

Graph

DOMINANT, CoLA, Radar

BOND (NeurIPS 2022)

Text

EmbeddingOD (MiniLM + KNN)

NLP-ADBench

Image

EmbeddingOD (DINOv2 + KNN)

NLP-ADBench

The data type is auto-detected from the input (dict → multimodal, list[str] → text, numpy array → tabular/time_series, PyG Data → graph). You can override with engine.investigate(X, data_type='time_series').