Featured image of post Алгоритм сглаживания значений временных рядов методом прокатки шара

Алгоритм сглаживания значений временных рядов методом прокатки шара

Описание и пример реализации алгоритма сглаживания значений временных рядов, когда известно, что искажения данных могут быть направлены только в одну сторону

Пример реальной задачи из своего опыта

Нужно проанализировать значения индекса NDVI для для конкретной географической точки. NDVI (Normalized Difference Vegetation Index - это индекс, который рассчитывается по данным спутниковой съёмки, и отражает здоровье растений, которые попали на снимок.

Скачиваем готовые или рассчитываем сами значения индекса NDVI с космического спутника за год. Если визуализировать полученные значения, то получится такой график:

Значения NDVI за год

Из графика видно, что в период с 100 по 280 день значения индекса скачут от нуля до 0,8. Зная то, как индекс NDVI ведёт себя фундаментально, можно уверенно пометить значения ниже основного тренда как ошибочные. Например, на фотографии со спутника в этом месте было облако.

Задача: сгладить значения индексов и получить примерно такой тренд:

Целевые NDVI за год

Другие примеры задач:

  • Посчитать максимальные значения значение покупательного траффика, чтобы спрогнозировать запасы товара для торговой точки
  • Гибкий расчёт границ ценовых каналов для биржевых торгов

Теоретическое описание алгоритма

Прокатываем виртуальный шар по точкам временного ряда, оставляя только те значения, которых коснётся шар.

Пример визуализации:

Прокатка шара

Алгоритм является подмножеством алгоритмов сглаживания по максимальным точкам.

Главная сильная сторона такого алгоритма то, что можно гибко настраивать параметры сглаживания временного ряда: длину окна в днях и рамки отклонения целевой метрики.

Как это работает

Последовательно проходимся по значениям временного ряда:

  • Находясь в текущей точке-значении, выбираем 1 следующую точку, которой бы коснулся наш виртуальный шар. На примере ниже: мы находимся в точке “А”, следующей точкой будет точка “C”. Выбор следующей точки
    • Иногда случается, что нет возможности выбрать следующую точку: нет точек в выбранном диапазоне или слишком большие отклонения метрики. В таком случае нужно выбирать свою стратегию под свои задачи: брать первую следующую точку, либо брать максимальную точку, либо брать точку, у которой вектор изменения минимален или другой вариант.
  • Удаляем из временного ряда все точки, между нашей текущей точкой и следующей выбранной. В примере выше - удаляем точку “B”.
  • Переходим в следующую точку. В примере выше - переходим в точку “С”.
  • Повторяем алгоритм выбора

Выбор следующей точки

Самая сложная часть проектирования алгоритма - выбор следующей точки, которой коснётся шар.

Вариант решения наивным способом через геометрические формулы:

  • Отобрать ближайшие следующие точки в пределах двух радиусов шара в днях
  • Для каждой из отобранных точек рассчитать угол между горизонтальной осью и линией от текущей точки до центра виртуального шара
  • Следующая точка, которой коснётся шар, будет иметь наибольшее значение этого угла

Выбор следующей точки по углу

Это угол считается сложением двух углов:

  • угол α1 между вектором от текущего значения к следующему и осью Х
  • угол α2 между вектором от текущего значения к следующему и радиусом шара к текущей точке

Оба угла рассчитываются на базе формул из школьного курса геометрии:

Расчёт угла

Пример реализации

Ниже представлен листинг кода на Python который реализует алгоритм расчёта угла на основе:

  • значения и даты текущей точки
  • значения и даты следующей точки
  • радиуса шара в днях и в значениях метрики
  • направления огибания - по верхней или по нижней границе временного ряда

На выходе функция выдаст положительное значение угла в радианах. Для выбора следующей точки во временном ряде нужно выбрать ту точку, которая имеет наибольший положительный угол.

import math

# Основная функция для расчёта угла
def calculate_angle_to_circle_center_from_measurement(curr_metric_value, curr_date, next_metric_value, next_date, radius_days, radius_metric, on_top=True):
    days_to_metric_coversion_ratio = radius_metric/radius_days
    
    x1 = 0
    y1 = 0
    
    x2 = (next_date - curr_date).days * days_to_metric_coversion_ratio
    y2 = float(next_metric_value - curr_metric_value)
    
    radius_metric_value = radius_days * days_to_metric_coversion_ratio
    try:
        return calculate_angle_to_circle_center(x1,y1,x2,y2,radius=radius_metric_value, on_top=on_top)
    except ValueError:
        # Заглушка на случай, если "шар" не касается следующей точки
        return -1000

    
def calculate_angle_to_circle_center(x1, y1, x2, y2, radius, on_top):
    chord_to_x_angle = math.atan((y2-y1)/(x2-x1))
   
    chord_length = calculate_chord_length(x1,y1,x2,y2)
    chord_lenth = math.sqrt((x2-x1)**2 + (y2-y1)**2)
    chord_to_circle_center_angle = math.acos(chord_lenth/2/radius)
    if on_top:
        angle_to_circle_center = chord_to_x_angle + chord_to_circle_center_angle
    else:
        angle_to_circle_center = -(chord_to_x_angle - chord_to_circle_center_angle)
    return angle_to_circle_center

def calculate_chord_length(x1, y1, x2, y2):
    x_delta = x2 - x1
    y_delta = y2 - y1
    chord_lenth = math.sqrt(x_delta**2 + y_delta**2)
    return chord_lenth

Примеры работы алгоритма

Возьмём за базу те значения NDVI, которые мы исследовали в начале статьи и визуализируем результаты сглаживания алгоритмом:

  • сглаживание с радиусом шара - 60 дней и 0,3 значения индекса Пример сглаживания
  • сглаживание с радиусом шара - 45 дней и 0,5 значения индекса Пример сглаживания
  • сглаживание с радиусом шара - 30 дней и 0,3 значения индекса Пример сглаживания
comments powered by Disqus