← All Posts
Deep Learning · Data Preparation and Preprocessing · Outlier Detection and Treatment

Isolation Forest

Isolation Forest can turn several feature columns into a ranking of unusual rows. In data preparation, the useful result is an inspectable score attached to the original row, together with a clear rule for deciding which rows deserve review.

This note focuses on that workflow. The algorithm chapter follows a point through random cuts and derives the path-length score. Here we connect a fitted forest to library outputs, thresholds, future values and treatment decisions.

Short isolation paths supply the ranking

The forest repeatedly partitions a training subsample using a randomly chosen feature and a random cut within that feature’s range. A separated point can reach a leaf after few cuts; points surrounded by others usually require more. Averaging over trees reduces dependence on one lucky partition.

Use [1, 2, 3, 4, 5, 6, 7, 8, 9, 30] as a one-feature reference. We fit 128 trees, each using all ten observations, with random seed 7. The value 30 receives the strongest anomaly score, but points near the other edges can also receive shorter paths than central points.

Separate the score from the threshold

Scikit-learn’s score_samples uses lower values for more unusual observations. Let $r(x)$ denote that raw score and $b$ the fitted threshold stored in offset_. The decision value subtracts the threshold:

$$d(x)=r(x)-b.$$

A negative decision value is flagged. predict returns −1 for that case and +1 otherwise; a decision value exactly zero is unflagged. For easier descending review order, you can sort by the negated raw score, $-r(x)$.

With contamination="auto", the offset is −0.5. A numeric contamination value instead sets a threshold from the lower tail of the training scores. It does not estimate which rows are truly erroneous.

Changing contamination can change labels without changing the forest

The following control keeps every fitted raw score fixed and changes only the numeric contamination threshold. It matches fitting the same forest settings and random seed with those contamination values.

The review boundary moves across a fixed ranking
Reference valueRaw score rReview flag
1-0.5384—
2-0.4368—
3-0.4073—
4-0.3989—
5-0.4086—
6-0.4145—
7-0.4249—
8-0.4468—
9-0.5060—
30-0.7994flag
Offset = -0.5645. Flagged reference values: 30.
These are measured outputs from the stated fitted forest. Increasing the contamination setting expands the flagged group; it does not prove that more rows contain errors.

The numerical setting calibrates a training-score cutoff, not a probability for each observation or a guaranteed future flag rate. Tied scores can also prevent the flagged fraction from matching the requested proportion exactly. If the goal is a review queue of a fixed size, keep that queue policy distinct from a model’s default labels.

Fit a reference and inspect future rows

This example uses the automatic threshold so the raw-score shift is easy to check. Preserve the original observation next to each score.

Run the fitted forest and inspect all three outputs
import numpy as np
from sklearn.ensemble import IsolationForest

train = np.array([1.,2.,3.,4.,5.,6.,7.,8.,9.,30.]).reshape(-1, 1)
future = np.array([4.5, 30., 100., 1000.]).reshape(-1, 1)
model = IsolationForest(n_estimators=128, max_samples=10,
                        contamination="auto", random_state=7).fit(train)
raw = model.score_samples(future)
print(raw.round(4).tolist())
print(model.decision_function(future).round(4).tolist())
print(model.predict(future).tolist())
np.testing.assert_allclose(model.decision_function(future), raw - model.offset_)
[-0.3995, -0.7994, -0.7994, -0.7994]
[0.1005, -0.2994, -0.2994, -0.2994]
[1, -1, -1, -1]

The automatic threshold also flags reference values 1 and 9 in this fitted example. That does not make them recording errors. The ranking, the threshold and the interpretation are three separate pieces.

A much larger value need not get a stronger score

Notice that future values 30, 100 and 1,000 get the same score. In this one-feature forest, every learned cut lies within the training range. All three observations follow the same rightmost route in each tree, giving the same leaf and path correction.

01Input 30Raw score ≈ −0.7994.
02Input 100Same route in every fitted tree; same score.
03Input 1,000Same route again; same score.
The forest cannot distinguish these tail magnitudes. The score describes how the learned partitions isolate a point. It is not an unbounded distance from the training range or a severity estimate.

If extreme magnitude itself matters, preserve it as separate evidence or add a task-specific severity measure. A forest score alone cannot recover distinctions absent from its partitions.

Make the training reference meaningful

Use feature columns that express comparable behavior. Numeric row IDs create arbitrary cut points. A raw count may mostly separate large entities from small ones; the telemetry chapter explains exposure and rate features.

Fit learned preprocessing and the detector on training data, preserving feature order and units for later scoring. Missing values require a consistent policy. Changing a nonlinear representation such as a log transform changes the gaps available for random cuts, so choose it as part of the model-selection process.

Tree count and subsample size serve different purposes. More trees average more random partitions; max_samples changes the population each tree sees. Try different seeds when judging ranking stability, and compare the highest-scored rows rather than looking only at the number of flags.

Use scores to support an explicit preparation policy

Attach the score and flag to the row identifier, then inspect the original record and its context. The treatment chapter explains when to correct, retain, cap or exclude a row, including how to evaluate a filtered training set on the full intended workload.

If the forest is part of an ongoing anomaly-monitoring system, evaluate its ranking and threshold on later data as well. Review some unflagged cases to learn what the policy misses. The queue and threshold notes develop that operational question.

C++: preserve the library’s score convention downstream

A scoring service might export raw forest scores and its fitted offset. The following postprocessing helper converts those outputs into a decision and label. It does not implement or re-fit the forest. Its example uses the rounded scores from the Python output.

Compile the score-to-label conversion
#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>

struct Decision { double margin; int label; };
Decision classify(double raw_score, double offset) {
    if (!std::isfinite(raw_score) || !std::isfinite(offset))
        throw std::invalid_argument("finite score and offset required");
    const double margin = raw_score - offset;
    return {margin, margin < 0 ? -1 : 1};
}
int main() {
    std::cout << std::fixed << std::setprecision(4);
    for (double raw : {-0.3995, -0.7994}) {
        const auto result = classify(raw, -0.5);
        std::cout << result.margin << " label=" << result.label << "\n";
    }
}
0.1005 label=1
-0.2994 label=-1

References and further reading