Operational datasets often contain rare rows that do not follow the pattern learned from the rest of the sample. scikit-learn can train an unsupervised anomaly detector for that screening job when the rows have numeric features and the workflow needs a repeatable way to mark likely outliers.
The IsolationForest estimator isolates unusual observations through random tree splits instead of requiring class labels. Setting contamination defines the expected outlier share for thresholding, while random_state keeps the small smoke run reproducible.
Start with a controlled dataset before pointing the detector at production rows. The fitted estimator returns 1 for inliers and -1 for outliers, and decision_function() returns negative values for rows on the outlier side of the learned threshold.
import numpy as np from sklearn.ensemble import IsolationForest row_names = np.array( [ "baseline-01", "baseline-02", "baseline-03", "baseline-04", "baseline-05", "baseline-06", "spike-01", ] ) readings = 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], ] ) detector = IsolationForest(contamination=0.15, random_state=42) detector.fit(readings) labels = detector.predict(readings) scores = detector.decision_function(readings) print("Training rows") for row_name, label, score in zip(row_names, labels, scores): state = "outlier" if label == -1 else "inlier" print(f"{row_name:11s} {state:7s} label={label:2d} score={score: .3f}") outliers = row_names[labels == -1] print(f"Detected training outliers: {', '.join(outliers)}") new_rows = np.array( [ [10.3, 200.5], [35.0, 390.0], ] ) new_names = np.array(["incoming-normal", "incoming-spike"]) new_labels = detector.predict(new_rows) new_scores = detector.decision_function(new_rows) print("\nNew rows") for row_name, label, score in zip(new_names, new_labels, new_scores): state = "outlier" if label == -1 else "inlier" print(f"{row_name:15s} {state:7s} label={label:2d} score={score: .3f}") assert "spike-01" in outliers assert "incoming-spike" in new_names[new_labels == -1] print("\nSmoke check passed: expected spike rows are outliers")
Replace the sample array with numeric features from the same measurement window or business process. Encode categorical fields and impute missing numeric values before fitting the detector.
$ 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 Detected training outliers: spike-01 New rows incoming-normal inlier label= 1 score= 0.110 incoming-spike outlier label=-1 score=-0.206 Smoke check passed: expected spike rows are outliers
The training row and incoming row with spike-sized values both return label=-1 and negative scores. Adjust contamination to the review rate the dataset can support instead of tuning it only until a specific row changes labels.