Detecting outliers with the OutlierDetector

The Halerium objective class OutlierDetector can be used to detect outliers, i.e. events that are uncommon or unexpected for a given model.

By default, events with a probability lower than that of 5% of a large sample of random events are considered outliers. Optionally, that outlier threshold can be set by the user to any value between 0 (no events are considered outliers) and 1 (all but the most likely events are considered outliers).

Ingredients

To detect outliers, you only need a graph and data, and optionally an outlier threshold.

Imports

[1]:
# for handling data:
import numpy as np

# for plotting:
import matplotlib.pyplot as plt

# for some Halerium specific functions:
import halerium.core as hal

# for building graphs:
from halerium.core import Graph, Entity, Variable, show

# for detecting outliers:
from halerium import OutlierDetector

The graph and input data

Let us define a simple graph:

[2]:
g = Graph("g")
with g:
    Variable("z", mean=0, variance=1)
    Variable("x", mean=z, variance=0.1)
    Variable("y", mean=z, variance=0.1)

show(g)

This graph just contains the three variables x, y, and z. The means of x and y are set to z, and the variances of x and y are much smaller than that of z. Thus, both x and y depend on the variable z and follow it fairly closely.

Now let’s look at a certain set of data.

[3]:
data = {g.x: [0,  1,  1, 5,      1],
        g.y: [0,  1, -1, 5, np.nan]}

Missing values for specific events are indicated by np.nan.

The OutlierDetector

We instantiate an OutlierDetector.

[4]:
outlier_detector = OutlierDetector(graph=g,
                                   data=data)

We can now call the outlier_detector with the graph g as argument to estimate which of the events in the data are outliers.

[5]:
outlier_detector(g)
[5]:
array([False, False,  True,  True, False])

We can see that events where x and y both have the same value -1, 0, or 1, and events without any data or just x = 1 are not considered outliers. In contrast, the event with x = 1 and y = -1 is considered an outlier, as well as the event with x = 5 and y = 5 or an event with just y = 5.

One can call the outlier_detector with any scopetor of the graph.

[6]:
outlier_detector(g.x)
[6]:
array([False, False,  True,  True, False])

The outlier detector then only looks at the values of the variables in that scopetor and judges their likelihood in the context of the provided data. In this case g.x is flagged as an outlier when the total graph is as well.

Results for multiple graph elements can be obtained using nested structures, e.g. dicts.

[7]:
outlier_detector({'x': g.x, 'y': g.y, 'z': g.z})
[7]:
{'x': array([False, False,  True,  True, False]),
 'y': array([False, False,  True,  True, False]),
 'z': array([False, False, False,  True, False])}

Here we can see, that in the event with x = 1 and y = -1, z is not flagged as an outlier while x and y are. This is because z is estimated to be ~0 which is a common value.

Calling without an argument yields results for the graph, its subgraphs, entities, and variables as a dict.

[8]:
outlier_detector()
[8]:
{'g': array([False, False,  True,  True, False]),
 'g/z': array([False, False, False,  True, False]),
 'g/x': array([False, False,  True,  True, False]),
 'g/y': array([False, False,  True,  True, False])}

Options

Calculation method

By default, missing data will be estimated by sampling (method="upsampled"). Alternatively, their values can be marginalized over.

Warning: the marginalized method can be very memory consuming.

[9]:
outlier_detector_default = OutlierDetector(graph=g,
                                           data=data,
                                           method="upsampled")

outlier_detector_marginalized = OutlierDetector(graph=g,
                                                data=data,
                                                method="marginalized")

In the case of our example, where there are no data for g.z, the difference lies in the outlier detection for the individual variables g.x and g.y. With the default method “upsampled” their values are coupled so that g.x was flagged as an outlier when g.y was -1 and g.x was +1. This is because g.z was estimated to be around 0 for that case.

With the “marginalized” method the individual values are not coupled. After marginalizing over z a value of +1 is not a all uncommon. Therefore that entry is not flagged as an outlier.

[10]:
outlier_detector_default(g.x)
[10]:
array([False, False,  True,  True, False])
[11]:
outlier_detector_marginalized(g.x)
[11]:
array([False, False, False,  True, False])

Outlier theshold

By default, events with a probability less than that of 5% of a large sample of random events are considered outliers. That 5% threshold can be adjusted to other values between 0 and 1. The higher the threshold, the more events are classified as outliers.

[12]:
outlier_detector = OutlierDetector(graph=g,
                                   data=data,
                                   outlier_threshold=0.7)
outlier_detector()
[12]:
{'g': array([False,  True,  True,  True,  True]),
 'g/z': array([False,  True, False,  True,  True]),
 'g/x': array([ True,  True,  True,  True,  True]),
 'g/y': array([ True,  True,  True,  True,  True])}

Accuracy

The accuracy of the OutlierDetector is affected by the number of examples used for estimating probabilities and ranks.

Small numbers of examples yield quick results at the expense of accuracy.

[13]:
outlier_detector = OutlierDetector(graph=g,
                                   data=data,
                                   n_samples=100)
outlier_detector()
[13]:
{'g': array([False, False,  True,  True, False]),
 'g/z': array([False, False, False,  True, False]),
 'g/x': array([False, False,  True,  True, False]),
 'g/y': array([False, False,  True,  True, False])}

Many examples yield higher accuracy at the expense of speed (in the current implementeation, the computing time increases quadratically with increasing number of examples).

[14]:
outlier_detector = OutlierDetector(graph=g,
                                   data=data,
                                   n_samples=1000)
outlier_detector()
[14]:
{'g': array([False, False,  True,  True, False]),
 'g/z': array([False, False, False,  True, False]),
 'g/x': array([False, False,  True,  True, False]),
 'g/y': array([False, False,  True,  True, False])}
[ ]: