Streaming Estimation¶
streaming
¶
Tail index estimation over a stream, without holding the sample.
The Hill estimator needs the top k order statistics. That sounds like it
rules out a streaming version, since order statistics are a global property of
the sample -- but only the top k + 1 values ever matter, and which values
those are can be maintained incrementally. A min-heap of size k + 1 keeps
them in O(k) memory and O(log k) time per observation, whatever the
length of the stream.
That gives an exact result, not an approximation. :class:StreamingTailIndex
holds the same numbers a batch estimator would sort out of the full sample, so
it returns the same estimate to the last bit -- which the test suite asserts
rather than assumes. Nothing is traded away except the ability to ask a
different k later.
Two semantics, and the difference matters:
:class:StreamingTailIndex
The whole stream, in O(k) memory. Every observation ever seen
contributes, so a change in the tail is diluted by everything before it.
:class:WindowedTailIndex
The most recent window observations, in O(window) memory. Responds
to a change in the tail, at a cost in memory and variance.
The memory difference is not an implementation shortcoming to be fixed later.
When the largest value in a window expires, the new maximum can be any of the
remaining ones, so a window cannot be summarised more compactly than by keeping
it. Anything claiming to track windowed order statistics in O(k) is either
approximating or wrong.
StreamingTailIndex
¶
Tail index estimators over an unbounded stream, in O(k) memory.
Holds the top k + 1 observations and nothing else. The estimate is
exact: it is the same number a batch estimator would produce from the whole
sample, because the estimators depend on the sample only through those
values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
Number of top order statistics the estimators use. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from heavytails import Pareto
>>> from heavytails.tail_index import hill_estimator
>>> data = Pareto(alpha=2.0, xm=1.0).rvs(20000, seed=1)
>>> stream = StreamingTailIndex(k=500)
>>> stream.extend(data)
>>> stream.hill() == hill_estimator(data, k=500)
True
Source code in heavytails/streaming.py
extend
¶
hill
¶
The Hill estimate of the extreme-value index.
Returns:
| Type | Description |
|---|---|
float
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than |
Source code in heavytails/streaming.py
moment
¶
The Dekkers-Einmahl-de Haan moment estimate.
Unlike the Hill estimator this does not assume the tail is heavy, so it
is the one to reach for when the sign of gamma is in doubt.
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If there is not yet enough data, or it is not positive. |
Source code in heavytails/streaming.py
TopK
¶
The k largest values of a stream, in O(k) memory.
A min-heap holds the retained values, so its root is the smallest of them and is what a new observation has to beat. That is the whole trick: the comparison that decides whether to keep a value is against the smallest kept, not against anything in the discarded majority.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
How many values to retain, at least one. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> top = TopK(3)
>>> top.extend([5.0, 1.0, 9.0, 3.0, 7.0])
>>> top.descending()
[9.0, 7.0, 5.0]
>>> top.n_seen, len(top)
(5, 3)
Source code in heavytails/streaming.py
WindowedTailIndex
¶
The same estimators over the most recent window observations.
The whole-stream version dilutes a change in the tail with everything that came before, which is the wrong behaviour for monitoring: a portfolio whose tail index has moved from 3 to 1.5 does not want an estimate averaging the two. This forgets.
Memory is O(window), not O(k), and that is inherent rather than
a gap to close later. When the largest value in the window expires, the new
largest can be any of the survivors, so nothing smaller than the window
itself determines the answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
How many recent observations to keep. |
required |
k
|
int
|
Number of top order statistics the estimators use, below |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from heavytails import Pareto
>>> monitor = WindowedTailIndex(window=5000, k=400)
>>> monitor.extend(Pareto(alpha=3.0, xm=1.0).rvs(5000, seed=1))
>>> round(monitor.hill(), 3) # true gamma 0.333
0.33
>>> monitor.extend(Pareto(alpha=1.5, xm=1.0).rvs(5000, seed=2))
>>> round(monitor.hill(), 3) # true gamma 0.667, the old regime gone
0.667
Source code in heavytails/streaming.py
extend
¶
hill
¶
moment
¶
update
¶
Add one observation, evicting the oldest if the window is full.