HBOS - Histogram-Based Outlier Score.

Classes

HBOSDetector

class algorithms.anomaly.hbos.HBOSDetector(Classifier)

HBOS scores outliers by per-feature histogram density.

HBOS is the fastest useful detector there is: one histogram per feature, then a sum of log-inverse densities. Scoring is O(d) per point with no distance computation and no neighbour search at all, which makes it the method of choice when throughput matters — streaming triage, first-pass filtering ahead of something more expensive, or datasets far too large for a quadratic detector.
Constructor
__init__(
    self,
    n_bins: int | str = 'auto',
    strategy: str = 'equal_frequency',
    contamination: float = 0.1,
    tol: float = 1e-12,
)

Overview

  1. Build a univariate histogram of each feature over the training data,
with either equal-width or equal-frequency bins.
  1. Normalise each histogram to a density.
  2. Score a point by summing \log(1 / \text{density}) over
features — rare bins contribute heavily, common bins barely at all.

Theory

With \hat{p}_j the fitted density of feature j, the score is

\mathrm{HBOS}(x) = \sum_{j=1}^{d} \log \left( \frac{1}{\hat{p}_j(x_j) + \varepsilon} \right)

which is, up to sign and constants, the negative log-likelihood under a naive density model that assumes independent features. That assumption is exactly the trade: it buys linear-time scoring and costs the ability to see any anomaly defined by a combination of otherwise ordinary values.

Bin choice matters more than any other decision here. Equal-width bins follow the classic formulation but degrade badly on skewed or heavy-tailed features, where nearly all mass lands in one bin; equal-frequency (dynamic) bins adapt to the empirical distribution and are the better default on real tabular data.

Parameters

n_bins
int or str = 'auto'
Number of bins per feature. 'auto' uses the Birge-Rozenholc rule :math:`\lceil n^{1/3} \rceil` clipped to [5, 100], which grows with the sample size without overfitting small data.
strategy
{'equal_frequency', 'equal_width'} = 'equal_frequency'
Binning strategy. Equal-frequency bins adapt to skewed features and are the safer default; equal-width matches the original paper.
contamination
float = 0.1
Expected proportion of outliers. Sets the decision threshold.
tol
float = 1e-12
Density floor, preventing an infinite score in an empty bin.

Attributes

edges_
np.ndarray of shape (n_features, n_bins + 1)
Fitted bin edges per feature.
density_
np.ndarray of shape (n_features, n_bins)
Fitted density per bin per feature.
n_bins_
int
Resolved number of bins.
threshold_
float
Decision-function value separating inliers from outliers.
n_features_in_
int
Number of features seen during fit.

Notes

Complexity. Training is O(n d \log n) for equal-frequency binning (a sort per feature) or O(n d) for equal-width. Prediction is O(m d \log b) for b bins. Memory is O(d b) — unlike ECOD, the training data is not retained, so the fitted model is tiny regardless of n. Binning and lookup run in the shared C++ kernel tuiml._cpp_ext.stats.

When to use. Reach for HBOS when speed dominates, when the model must stay small, or as a cheap first stage in a cascade. Its blind spot is correlated features: on data where anomalies are joint rather than marginal, prefer IsolationForestDetector or LocalOutlierFactorDetector. Against ECODDetector, HBOS is faster to score and far smaller in memory, but needs its bin count chosen and is not invariant to monotone transforms.

References

Goldstein2012
Goldstein, M., & Dengel, A. (2012). Histogram-based Outlier Score (HBOS): A Fast Unsupervised Anomaly Detection Algorithm. KI-2012: Poster and Demo Track, 59-63.
python
>>> import numpy as np
>>> from tuiml.algorithms.anomaly import HBOSDetector
>>> rng = np.random.default_rng(0)
>>> X = np.vstack([rng.normal(0, 1, (300, 4)), rng.normal(9, 1, (15, 4))])
>>> detector = HBOSDetector(contamination=0.05).fit(X)
>>> int((detector.predict(X)[-15:] == -1).sum())
15
>>> detector.n_bins_
7

Methods

get_parameter_schema (cls) -> Dict[str, Dict[str, Any]]

Return JSON Schema for algorithm parameters.

get_capabilities (cls) -> List[str]

Return supported capabilities.

get_complexity (cls) -> str

Return complexity analysis.

get_references (cls) -> List[str]

Return academic citations.

fit (self, X: np.ndarray, _y: Optional[np.ndarray]=None) -> 'HBOSDetector'

Fit one histogram per feature.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Training data. Labels are ignored; the method is unsupervised.
_y
np.ndarray
Ignored, present for API consistency.
Returns
self
HBOSDetector
The fitted detector.
decision_function (self, X: np.ndarray) -> np.ndarray

Compute anomaly scores for samples.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
scores
np.ndarray of shape (n_samples,)
Anomaly scores. Lower scores indicate anomalies.
predict (self, X: np.ndarray) -> np.ndarray

Predict if samples are anomalies or not.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
predictions
np.ndarray of shape (n_samples,)
-1 for anomalies, 1 for normal instances.
score_samples (self, X: np.ndarray) -> np.ndarray

Alias for decision_function for compatibility.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
scores
np.ndarray of shape (n_samples,)
Anomaly scores. Lower scores indicate anomalies.
feature_contributions (self, X: np.ndarray) -> np.ndarray

Return each feature's contribution to a sample's outlier score.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
contributions
np.ndarray of shape (n_samples, n_features)
Per-feature :math:`\log(1 / \text{density})`. Row sums equal the raw HBOS score, so the largest entries name the features that drove the flag.
__repr__ (self) -> str

Return a readable representation of the detector.