Detector Compatibility Guide¶
Most detectors implementing AnomalyDetector work with nonconform: PyOD,
scikit-learn, or custom implementations.
For strict inductive conformal/FDR guarantees, the detector must use a fixed
training-only score map after fit(...).
AnomalyDetector Protocol¶
Your detector must implement these four methods:
from typing import Any, Self
import numpy as np
class MyDetector:
def fit(self, X: np.ndarray, y: np.ndarray | None = None) -> Self:
"""Train on normal data. Return self."""
...
def decision_function(self, X: np.ndarray) -> np.ndarray:
"""Return anomaly scores. Higher = more anomalous."""
...
def get_params(self, deep: bool = True) -> dict[str, Any]:
"""Return detector parameters as dict."""
...
def set_params(self, **params: Any) -> Self:
"""Set parameters. Return self."""
...
The detector must also be copyable (copy.copy and copy.deepcopy).
PyOD Detectors¶
PyOD detectors are supported with strict-inductive caveats. Install PyOD with:
pip install nonconform[pyod]
Compatible Detectors¶
One-class classification detectors work with nonconform:
| Detector | Class | Best For |
|---|---|---|
| Isolation Forest | IForest |
High-dimensional data, large datasets |
| Local Outlier Factor | LOF |
Dense clusters, local anomalies |
| K-Nearest Neighbors | KNN |
Simple distance-based detection |
| One-Class SVM | OCSVM |
Complex boundaries, small datasets |
| PCA | PCA |
Linear anomalies, interpretability |
| INNE | INNE |
Isolation-based nearest-neighbor ensembles |
| HBOS | HBOS |
Feature independence assumptions |
| GMM | GMM |
Probabilistic modeling |
| AutoEncoder | AutoEncoder |
Deep learning, complex patterns |
Strict-Inductive Unsafe (Hard-Blocked)¶
These detectors are blocked in ConformalDetector because they do not keep a
fixed training-only score rule at inference:
CDCOFCOPODECODLMDDLOCIRGraphSODSOS
Meta / Inherited-Risk Detectors¶
These are not hard-blocked, but they inherit base-detector validity risks and require careful curation:
FeatureBaggingLSCPSUOD(unsafe by default if it includes blocked base detectors)
Basic Usage¶
from pyod.models.iforest import IForest
from nonconform import ConformalDetector, Split
detector = ConformalDetector(
detector=IForest(random_state=42),
strategy=Split(n_calib=0.3),
seed=42
)
detector.fit(X_train)
p_values = detector.compute_p_values(X_test)
For strict inductive workflows, choose detectors that score against frozen
training state (for example IForest, KNN, LOF, HBOS, PCA, OCSVM,
INNE).
Automatic Configuration¶
ConformalDetector applies a standard parameter normalization step during
construction for supported detectors. It attempts to:
- set
contaminationto a minimal value when that parameter exists - set
n_jobs/n_threads/num_workersto-1when available (use all cores) - set a seed parameter (
random_state/seed/random_seed) fromseedwhen supported
If a parameter is not available on your estimator, the request is skipped rather than failing initialization. Unsupported contamination and parallelism parameters are logged at debug level; if no supported seed parameter is available, the configuration step emits a warning.
Custom Detectors¶
Implement the protocol to use any anomaly detection algorithm:
from typing import Any, Self
import numpy as np
class MahalanobisDetector:
"""Simple Mahalanobis distance-based anomaly detector."""
def __init__(self, random_state: int | None = None):
self.random_state = random_state
self._mean = None
self._cov_inv = None
def fit(self, X: np.ndarray, y: np.ndarray | None = None) -> Self:
self._mean = np.mean(X, axis=0)
cov = np.cov(X.T) + 1e-6 * np.eye(X.shape[1])
self._cov_inv = np.linalg.inv(cov)
return self
def decision_function(self, X: np.ndarray) -> np.ndarray:
diff = X - self._mean
return np.sqrt(np.sum(diff @ self._cov_inv * diff, axis=1))
def get_params(self, deep: bool = True) -> dict[str, Any]:
return {"random_state": self.random_state}
def set_params(self, **params: Any) -> Self:
for key, value in params.items():
setattr(self, key, value)
return self
Use it like any other detector:
from nonconform import ConformalDetector, Split
detector = ConformalDetector(
detector=MahalanobisDetector(random_state=42),
strategy=Split(n_calib=0.3),
score_polarity="higher_is_anomalous", # explicit for clarity; also the default
seed=42
)
See examples/custom/centroid_detector.py for a complete working example.
Scikit-learn Detectors¶
Scikit-learn detectors that implement fit, decision_function, get_params, and set_params work directly:
from sklearn.svm import OneClassSVM
from nonconform import ConformalDetector, Split
detector = ConformalDetector(
detector=OneClassSVM(kernel="rbf", nu=0.05),
strategy=Split(n_calib=0.3),
score_polarity="auto",
seed=42
)
If you omit score_polarity, nonconform defaults to:
- "higher_is_normal" for known sklearn normality detectors
- "higher_is_anomalous" for PyOD detectors and custom detectors outside
recognized PyOD/known-sklearn families
score_polarity="auto" is strict and raises for custom estimators outside
recognized PyOD/known-sklearn families.
Troubleshooting¶
Missing Methods Error¶
TypeError: Detector must implement AnomalyDetector protocol. Missing methods: decision_function
Your detector is missing required methods. Implement all four: fit, decision_function, get_params, set_params.
PyOD Not Installed¶
ImportError: Detector appears to be a PyOD detector, but PyOD is not installed.
Install PyOD: pip install nonconform[pyod]
Blocked PyOD Detector¶
ValueError: PyOD detector 'ECOD' is incompatible with strict inductive conformal/FDR workflows.
Use an inductive-safe detector (for example IForest, KNN, LOF, HBOS,
PCA, OCSVM, INNE) instead of blocked batch-adaptive detectors.
Score Direction¶
nonconform computes p-values using higher scores = more anomalous internally.
- Use
score_polarity="higher_is_anomalous"when your detector already follows that convention. - Use
score_polarity="higher_is_normal"when larger scores mean more normal. - Omit
score_polarityfor convenience defaults (automatic handling for known sklearn normality detector families, plus custom-detector fallback to anomalous-higher). - Use
score_polarity="auto"for strict detector-family validation (raises on custom estimators outside recognized PyOD/known-sklearn families).
Copyability¶
Your detector must support copy.copy() and copy.deepcopy(). Most Python classes work by default, but if you have complex state (file handles, connections), implement __copy__ and __deepcopy__.