def MakeChangePoint(series, split_index):
    """Makes a ChangePoint object for the given series at the given point.

  Args:
    series: A list of (x, y) pairs.
    split_index: Index of the first point after the split.

  Returns:
    A ChangePoint object.
  """
    assert 0 <= split_index < len(series)
    x_values, y_values = zip(*series)
    left, right = y_values[:split_index], y_values[split_index:]
    left_median, right_median = math_utils.Median(left), math_utils.Median(
        right)
    ttest_results = ttest.WelchsTTest(left, right)
    return ChangePoint(
        x_value=x_values[split_index],
        median_before=left_median,
        median_after=right_median,
        size_before=len(left),
        size_after=len(right),
        window_start=x_values[0],
        window_end=x_values[-1],  # inclusive bound
        relative_change=math_utils.RelativeChange(left_median, right_median),
        std_dev_before=math_utils.StandardDeviation(left),
        t_statistic=ttest_results.t,
        degrees_of_freedom=ttest_results.df,
        p_value=ttest_results.p)
def _PassesThresholds(values, split_index, min_segment_size,
                      min_absolute_change, min_relative_change, min_steppiness,
                      multiple_of_std_dev):
    """Checks whether a point in a series appears to be an change point.

  Args:
    values: A list of numbers.
    split_index: An index in the list of numbers.
    min_segment_size: Threshold for size of segments before or after a point.
    min_absolute_change: Minimum absolute median change threshold.
    min_relative_change: Minimum relative median change threshold.
    min_steppiness: Threshold for how similar to a step a change point must be.
    multiple_of_std_dev: Threshold for change as multiple of std. deviation.

  Returns:
    A tuple of (bool, string) where the bool indicates whether the split index
    passes the thresholds and the string being the reason it did not.
  """
    left, right = values[:split_index], values[split_index:]
    left_median, right_median = math_utils.Median(left), math_utils.Median(
        right)

    # 1. Segment size filter.
    if len(left) < min_segment_size or len(right) < min_segment_size:
        return (False, 'min_segment_size')

    # 2. Absolute change filter.
    absolute_change = abs(left_median - right_median)
    if absolute_change < min_absolute_change:
        return (False, 'min_absolute_change')

    # 3. Relative change filter.
    relative_change = math_utils.RelativeChange(left_median, right_median)
    if relative_change < min_relative_change:
        return (False, 'min_relative_change')

    # 4. Multiple of standard deviation filter.
    min_std_dev = min(math_utils.StandardDeviation(left),
                      math_utils.StandardDeviation(right))
    if absolute_change < multiple_of_std_dev * min_std_dev:
        return (False, 'min_std_dev')

    # 5. Steppiness filter.
    steppiness = find_step.Steppiness(values, split_index)
    if steppiness < min_steppiness:
        return (False, 'min_steppiness')

    # Passed all filters!
    return (True, 'passed')
 def testRelativeChange_FromZero_ReturnsInf(self):
     self.assertEqual(float('inf'), math_utils.RelativeChange(0, 1))
 def testRelativeChange_NoChange_ReturnsZero(self):
     self.assertEqual(0, math_utils.RelativeChange(7, 7))
 def testRelativeChange(self):
     # The relative difference is with respect to the first number, and the
     # absolute value is taken. So 1 means doubling, and 0.5 means halving.
     self.assertEqual(1, math_utils.RelativeChange(32, 64))
     self.assertEqual(0.5, math_utils.RelativeChange(64, 32))
 def _IsApproximatelyEqual(self, delta1, delta2):
   smaller = min(delta1, delta2)
   larger = max(delta1, delta2)
   return math_utils.RelativeChange(smaller, larger) <= _MAX_DELTA_DIFFERENCE