Local Outlier Factor (LOF)
Why global outlier thresholds fail
The simplest way to flag an anomaly is a global rule: anything more than N standard deviations from the mean, or farther than some fixed distance from its nearest neighbor. This works fine when data has one uniform density throughout — but real data rarely does. A dataset might have one dense cluster and one sparse, spread-out cluster. A point sitting comfortably inside the sparse cluster might be farther from its neighbors than a genuine anomaly sitting at the edge of the dense cluster, and a global distance threshold would misclassify both.
Local Outlier Factor (LOF) fixes this by making 'outlier-ness' relative to local context: a point is anomalous not based on its absolute distance to neighbors, but based on how much sparser its neighborhood is compared to the neighborhoods of its neighbors.
Building up to the LOF score
LOF is built from a stack of three nested definitions, each one feeding the next.
k-distance. For a point p, k-distance(p) is the distance to its k-th nearest neighbor. This sets a local scale for 'how far is far' around p.
Reachability distance. reach-dist_k(p, o) = max(k-distance(o), dist(p, o)). This smooths out fluctuations for points very close to o — it never lets the reachability distance drop below o's own k-distance, which stabilizes the density estimate for points deep inside a cluster.
Local reachability density (lrd). lrd_k(p) = 1 / (average reachability distance from p to its k nearest neighbors). This is p's local density estimate — high when p's neighbors are close by, low when they're far.
LOF score. LOF_k(p) = (average of lrd(neighbor) for each of p's k nearest neighbors) / lrd(p). This is the key ratio: it compares p's own density to the average density of the points around it.
Note
Global distance threshold vs. LOF
| Property | Global distance/density threshold | Local Outlier Factor |
|---|---|---|
| Handles varying-density clusters | No — one threshold for the whole dataset | Yes — density compared locally, per neighborhood |
| False positives in sparse clusters | Common — sparse-but-normal points look anomalous | Rare — compared only to local neighbors |
| Output | Binary inlier/outlier | Continuous score, interpretable as degree of anomaly |
| Key hyperparameter | Distance/density cutoff | Number of neighbors (k) used for local density |
| Computational cost | Cheap | Moderate — requires k-NN search for every point |
Note
Real-world applications
Fraud detection — a transaction that looks normal in isolation can still be anomalous relative to a specific user's or merchant's local transaction pattern, which is exactly the locally-relative comparison LOF is built for.
Network intrusion detection — traffic that's unremarkable in a high-volume subnet but anomalous in a typically-quiet one.
Manufacturing quality control — sensor readings that are fine in one operating regime (e.g. high throughput) but anomalous in another (e.g. idle), where a single global threshold across all regimes would miss context-dependent defects.
Scientific data cleaning — flagging measurement errors in datasets with naturally uneven sampling density across different regions of a feature space.
What's next
LOF reasons about local density structure in the original feature space. When that space has many dimensions, visualizing it directly becomes impossible — which is exactly the problem dimensionality reduction for visualization solves. The t-SNE Deep Dive lesson explores how to project high-dimensional data down to two dimensions while preserving local structure, and the pitfalls of reading too much into the result.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.