blob: c15e1a5964630aa6a982c338162689c6017119a4 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import numpy as np
import pytest
from otava.analysis import (
TTestSignificanceTester,
compute_change_points,
compute_change_points_orig,
)
from otava.change_point_divisive.base import CandidateChangePoint
def test_single_series():
series = [
1.02,
0.95,
0.99,
1.00,
1.12,
1.00,
1.01,
0.98,
1.01,
0.96,
0.50,
0.51,
0.48,
0.48,
0.55,
0.50,
0.49,
0.51,
0.50,
0.49,
]
cps, _ = compute_change_points(series, window_len=10, max_pvalue=0.0001)
indexes = [c.index for c in cps]
assert indexes == [10]
# incremental change point detection:
cps, _ = compute_change_points(series, max_pvalue=0.0001, new_data=0.47, old_weak_cp=cps)
indexes = [c.index for c in cps]
assert indexes == [10]
# decrease window_len to generate more cp and hit the last two lines in code cov...
cps, _ = compute_change_points(series+[0.47, 0.48, 0.45, 0.01, 0.1, 0.22], max_pvalue=0.0001, new_data=0.27, old_weak_cp=cps, window_len=5)
indexes = [c.index for c in cps]
assert indexes == [10, 23]
cps = [cps[0]]
cps[0].index = 2
cps, _ = compute_change_points(series+[0.47, 0.48, 0.45, 0.01, 0.1, 0.22], max_pvalue=0.0001, new_data=0.27, old_weak_cp=cps, window_len=5)
indexes = [c.index for c in cps]
assert indexes == [10, 23]
def test_single_series_original():
series = [
1.02,
0.95,
0.99,
1.00,
1.12,
1.00,
1.01,
0.98,
1.01,
0.96,
0.50,
0.51,
0.48,
0.48,
0.55,
0.50,
0.49,
0.51,
0.50,
0.49,
]
cps, _ = compute_change_points_orig(series, max_pvalue=0.0001, seed=1)
indexes = [c.index for c in cps]
assert indexes == [10]
def test_significance_tester():
tester = TTestSignificanceTester(0.001)
series = np.array([1.00, 1.02, 1.05, 0.95, 0.98, 1.00, 1.02, 1.05, 0.95, 0.98])
candidate = CandidateChangePoint(index=5, qhat=0.)
cp = tester.change_point(candidate, series, intervals=[slice(None, None)])
assert not tester.is_significant(cp)
assert 0.99 < cp.stats.pvalue < 1.01
series = np.array([1.00, 1.02, 1.05, 0.95, 0.98, 0.80, 0.82, 0.85, 0.79, 0.77])
candidate = CandidateChangePoint(index=5, qhat=0.)
cp = tester.change_point(candidate, series, intervals=[slice(None, None)])
assert tester.is_significant(cp)
assert 0.00 < cp.stats.pvalue < 0.001
@pytest.mark.parametrize("tail, expected_indexes", [([], [4]), ([3.] * 4, [4, 12])])
def test_merge_refreshes_adjacent_statistics(tail, expected_indexes):
# Removing boundary 8 joins the 2.0 and 2.05 segments. The surviving
# boundaries must describe that combined segment, on both sides if present.
values = [1.] * 4 + [2.] * 4 + [2.05] * 4 + tail
points, _ = compute_change_points(
values, window_len=50, max_pvalue=.001, min_magnitude=.1
)
assert [point.index for point in points] == expected_indexes
np.testing.assert_allclose(points[0].stats.mean_2, 2.025)
np.testing.assert_allclose(points[0].stats.std_2, .025)
if tail:
np.testing.assert_allclose(points[1].stats.mean_1, 2.025)
np.testing.assert_allclose(points[1].stats.std_1, .025)