Rare observations can distort monitoring, fraud review, and data-quality workflows even when no anomaly labels exist. An Isolation Forest learns which numeric rows are easier to isolate from the rest and can flag those rows without a labeled target column.

Every training and incoming row must use the same ordered numeric features. Categorical fields need numeric encoding and missing measurements need imputation before fitting so the detector receives a consistent feature shape.

The contamination value sets the expected outlier share used to choose the decision threshold, while random_state makes the randomized tree splits reproducible. predict() returns 1 for an inlier and -1 for an outlier; a negative decision_function() value places a row on the outlier side of the threshold.

Steps to train an anomaly detector with scikit-learn:

  1. Create train_anomaly_detector.py with the imports and controlled training rows.
    train_anomaly_detector.py
    import numpy as np
    from sklearn.ensemble import IsolationForest
     
    training_names = np.array(
        [
            "baseline-01", "baseline-02", "baseline-03",
            "baseline-04", "baseline-05", "baseline-06",
            "spike-01",
        ]
    )
    training_rows = np.array(
        [
            [10.0, 200.0], [10.4, 198.0], [9.8, 202.0],
            [10.2, 201.0], [10.1, 199.0], [10.5, 203.0],
            [42.0, 410.0],
        ]
    )

    The first six rows form a tight baseline, while spike-01 supplies a known anomaly for the training smoke test.

  2. Add the unseen evaluation rows below the training arrays.
    train_anomaly_detector.py
    evaluation_names = np.array(["incoming-normal", "incoming-spike"])
    evaluation_rows = np.array(
        [
            [10.3, 200.5],
            [35.0, 390.0],
        ]
    )
  3. Add the result formatter below the evaluation rows.
    train_anomaly_detector.py
    def print_results(title, names, labels, scores):
        print(title)
        for name, label, score in zip(names, labels, scores):
            state = "outlier" if label == -1 else "inlier"
            print(f"{name:15s} {state:7s} label={label:2d} score={score: .3f}")
  4. Add the fitted IsolationForest block below the formatter.
    train_anomaly_detector.py
    detector = IsolationForest(contamination=0.15, random_state=42)
    detector.fit(training_rows)

    A contamination value of 0.15 expects roughly one outlier among the seven controlled training rows. Production thresholds should reflect the share of rows that can receive anomaly review.

  5. Add the prediction and decision-score block below the fitted detector.
    train_anomaly_detector.py
    training_labels = detector.predict(training_rows)
    training_scores = detector.decision_function(training_rows)
    evaluation_labels = detector.predict(evaluation_rows)
    evaluation_scores = detector.decision_function(evaluation_rows)
  6. Add the training and incoming-row reports below the scoring block.
    train_anomaly_detector.py
    print_results("Training rows", training_names, training_labels, training_scores)
    print()
    print_results(
        "Incoming rows",
        evaluation_names,
        evaluation_labels,
        evaluation_scores,
    )
  7. Add fail-capable anomaly assertions below the report calls.
    train_anomaly_detector.py
    assert training_labels[training_names == "spike-01"].item() == -1
    assert evaluation_labels[evaluation_names == "incoming-spike"].item() == -1
    assert evaluation_scores[evaluation_names == "incoming-spike"].item() < 0
  8. Run the completed script to verify the known and incoming spikes cross the anomaly threshold.
    $ python3 train_anomaly_detector.py
    Training rows
    baseline-01     inlier  label= 1 score= 0.114
    baseline-02     inlier  label= 1 score= 0.052
    baseline-03     inlier  label= 1 score= 0.053
    baseline-04     inlier  label= 1 score= 0.123
    baseline-05     inlier  label= 1 score= 0.111
    baseline-06     inlier  label= 1 score= 0.031
    spike-01        outlier label=-1 score=-0.280
    
    Incoming rows
    incoming-normal inlier  label= 1 score= 0.110
    incoming-spike  outlier label=-1 score=-0.206

    The baseline-shaped incoming row remains an inlier, while both spike rows return -1 with negative decision scores. A failed assertion stops the script with a nonzero exit status.