Skip to content
This repository was archived by the owner on Apr 8, 2024. It is now read-only.

Commit ba794de

Browse files
authored
Implement a bootstrap confidence interval method (#179)
* bootstrap ci math helper * add unit tests
1 parent 5d9b0c9 commit ba794de

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

src/common/math.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Helper math functions
3+
"""
4+
import os
5+
import argparse
6+
import logging
7+
import numpy as np
8+
9+
def bootstrap_ci(data, iterations=1000, operators={'mean':np.mean}, confidence_level=0.95, seed=None):
10+
"""
11+
Args:
12+
data (np.array) : input data
13+
iterations (int) : how many bootstrapped samples to generate
14+
operators (Dict[str->func]) : map of functions to produce CI for
15+
confidence_level (float) : confidence_level = 1-alpha
16+
17+
Returns:
18+
operators_ci: Dict[str->tuple]
19+
"""
20+
# values will be stored in a dict
21+
bootstrap_runs = {}
22+
for operator_key in operators.keys():
23+
bootstrap_runs[operator_key] = []
24+
25+
sample_size = len(data)
26+
for _ in range(iterations):
27+
bootstrap = np.random.choice(data, size=sample_size, replace=True)
28+
for operator_key, operator_func in operators.items():
29+
bootstrap_runs[operator_key].append(operator_func(bootstrap))
30+
31+
operators_ci = {}
32+
for operator_key in operators.keys():
33+
values = np.array(bootstrap_runs[operator_key])
34+
ci_left = np.percentile(values, ((1-confidence_level)/2*100))
35+
ci_right = np.percentile(values, (100-(1-confidence_level)/2*100))
36+
ci_mean = np.mean(values) # just for fun
37+
operators_ci[operator_key] = (ci_left, ci_mean, ci_right)
38+
39+
return(operators_ci)

tests/common/test_math.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Tests src/common/math.py"""
2+
import os
3+
import pytest
4+
import numpy as np
5+
6+
from common.math import bootstrap_ci
7+
8+
def test_bootstrap_ci_fixed_seed():
9+
"""Testing the bootstrap_ci method, but we can't have a non-deterministic test here. """
10+
sample_data = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 5.0])
11+
operators={
12+
'mean':np.mean,
13+
'p90': (lambda x : np.percentile(x, 90)),
14+
'p99': (lambda x : np.percentile(x, 99)),
15+
}
16+
17+
np.random.seed(404) # fixed const
18+
19+
# because we're fixing the seed, we can actually go deeper
20+
expected_values = {
21+
'mean': (0.30000000000000004, 0.99395, 2.1624999999999996),
22+
'p90': (0.5, 2.36413, 5.0),
23+
'p99': (0.593, 3.469213, 5.0),
24+
}
25+
26+
returned_values = bootstrap_ci(
27+
sample_data,
28+
iterations=1000,
29+
operators=operators,
30+
confidence_level=0.95
31+
)
32+
33+
assert returned_values == expected_values
34+
35+
36+
def test_bootstrap_ci_no_seed():
37+
"""Testing the bootstrap_ci method, but we can't have a non-deterministic test here. """
38+
np.random.seed(None) # not const
39+
40+
sample_data = np.random.rand(100)
41+
operators={
42+
'mean':np.mean,
43+
'p90': (lambda x : np.percentile(x, 90)),
44+
'p99': (lambda x : np.percentile(x, 99)),
45+
}
46+
47+
returned_values = bootstrap_ci(
48+
sample_data,
49+
iterations=1000,
50+
operators=operators,
51+
confidence_level=0.95
52+
)
53+
54+
for key in operators:
55+
# check type
56+
assert key in returned_values
57+
assert isinstance(returned_values[key], tuple)
58+
assert len(returned_values[key]) == 3
59+
60+
# basic interval ordering
61+
ci_left, ci_mean, ci_right = returned_values[key]
62+
assert ci_left <= ci_mean
63+
assert ci_mean <= ci_right
64+
65+
# because it's a bootstrap, these are supposed to be true
66+
assert min(sample_data) <= ci_left
67+
assert ci_right <= max(sample_data)
68+
69+
# tests that are specific to the operators
70+
assert returned_values['p90'][0] <= returned_values['p99'][0] # p90 < p99 so left CI also
71+
assert returned_values['p90'][1] <= returned_values['p99'][1] # p90 < p99 so mean also
72+
assert returned_values['p90'][2] <= returned_values['p99'][2] # p90 < p99 so right CI also

0 commit comments

Comments
 (0)