Арифметическое скользящее среднее

Алгоритмизация / Арифметическое скользящее среднее

 

При обработке экспериментальных данных часто возникает необходимость их усреднения. С этой целью часто используют алгоритм арифметического скользящего среднего.

Скользящее среднее — общее название для семейства функций, значения которых в каждой точке определения равны среднему значению исходной функции за предыдущий период. Скользящие средние обычно используются с данными временных рядов для сглаживания краткосрочных колебаний и выделения основных тенденций или циклов. Математически скользящее среднее является одним из видов свертки и поэтому его можно рассматривать как фильтр низких частот, используемых в обработке сигналов.
Простое (арифметическое) скользящее среднее численно равно среднему арифметическому значений исходной функции за установленный период и вычисляется по формуле:
Скользящее среднее
где fi – исходные значения рассматриваемой функции; h=k-l – сглаживающий интервал – количество значений исходной функции для расчета скользящего среднего.
На рисунке представлены функции скользящего среднего для исходной последовательности значений с разной величиной сглаживающего интервала.
Скользящее среднее с разным интервалом усреднения
Чем шире сглаживающий интервал, тем более плавным будет график результирующей функции.
Однако с другой стороны, увеличение сглаживающего интервала приводит к временному сдвигу усредненной функции относительно исходной.
Из предыдущего своего значения простое скользящее среднее может быть получено по следующей рекуррентной формуле:
Рекуррентная формула скользящего среднего
где fk-h — значение исходной функции в точке k-hfk — значение исследуемой функции точке k (последнее значение).
При реализации рекуррентной формулы удобно использовать массив для хранения значений сглаживающего интервала, а также переменную, содержащую сумму этих значений.

Реализация на C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <stdlib.h>
using namespace std;
// Задание начального набора значений
double ** getData(int n) {
  double **f;
  f = new double*[3];
  f[0] = new double[n];
  f[1] = new double[n];
  f[2] = new double[n];
  for (int i = 0; i<n; i++) {
    f[0][i] = (double)i;
    f[1][i] = 8 * (double)i - 3;
    // Добавление случайной составляющей
    //f[1][i] = 8*(double)i - 3 + ((rand()%100)-50)*0.05;
  }
  return f;
}
// Вычисление результата скользящего среднего
void getMA(double **x, int n, int size) {
  // size - количество отсчетов интервала усреднения
  double sumx = 0; // сумма отсчетов на интервале
  double *mas; // массив для хранения size отсчетов
  int index = 0; // индекс элемента массива
  mas = new double[size];
  for (int i = 0; i<size; i++) mas[i] = 0;
  for (int i = 0; i<n; i++) {
    sumx -= mas[index];
    mas[index] = x[1][i];
    sumx += mas[index];
    index++;
    if (index >= size)
      index = 0; // возврат к началу "окна"
    x[2][i] = sumx / size;
  }
  return;
}
int main() {
  double **x;
  int n, h;
  system("chcp 1251");
  system("cls");
  cout << "Введите количество точек: ";
  cin >> n;
  cout << "Введите сглаживающий интервал: ";
  cin >> h;
  x = getData(n);
  getMA(x, n, h);
  for (int i = 0; i<n; i++)
    cout << x[0][i] << " - " << x[1][i] << " - " << x[2][i] << endl;
  getchar(); getchar();
  return 0;
}

Результат выполнения
Скользящее среднее: консоль
График функции скользящего среднего с интервалом усреднения h=4 для данных, приведенных здесь, показан на рисунке.
Скользящее среднее: график


Назад: Алгоритмизация

Комментариев к записи: 9

  • Доброго времени Елена. Я правильно понимаю что у данного расчёта значимый результат в значении, уже получается на первой итерации, независимо от окна интервала? То есть нет начального лага, который равен длине интервала? Возможно ли данный расчёт сделать адаптивным? Буду благодарен за пример адаптивной скользящей средней.

    • Елена Вставская
      Нет, задержка первого корректного результата равна размеру окна.

      • А не знаете, существует ли такой расчёт? Который бы устранял эту начальную задержку. Возможно двухсторонний какой то расчёт.

        • Елена Вставская
          Можно вначале усреднять на имеющееся количество точек, до достижения окна

          • Спасибо попробую. Уточните пожалуйста, усреднять на количество точек окна, или на количество точек всей выборки?

          • Елена Вставская
            Конечно на количество точек окна. Если Вам скользящее среднее нужно. А если просто среднее значение, то сложить все выборки и разделить на количество.


  • Честно говоря я не совсем смог разобраться в коде. У меня есть своя наработка в вычислении скользящего среднего на МК теперь уже на STM32 благодаря Вашей помощи )) Если интересно покритикуйте. файл stm32fxx_it.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    uint16_t tmp_arr[256] = {0}; // массив где храним мгновенные значения
    uint16_t tmp_rez = 0; // для хранения текущего результата
    uint32_t sum = 0; // здесь хранится сумма всех элементов массива
    uint32_t number = 0; // а сюда запишем результат вычисления среднего скользящего
    void TIM1_UP_IRQHandler(void// обработчик прерывания таймера (интервал через который будем делать замер запуская АЦП)
    {
    HAL_ADC_Start_IT(&hadc1);
    HAL_TIM_IRQHandler(&htim1);
    }

    void ADC1_2_IRQHandler(void// обработчик прерывания по окончанию преобразования
    {
    static uint8_t cnt = 0; // статическая переменная которая будет переполняться, специально делаю так чтобы не использовать условия if и т.д. , но размер окна получается в 256 значений
    sum -= tmp_arr[cnt]; // отнимаем от общ. сум. знач. ячейки массива которую в дальнейшем перезапишем новым знач (т.е. самое старое значение)
    tmp_res = HAL_ADC_GetValue(&hadc1); // по оконч. преобраз, присваиваем результ.временной перем.
    sum += tmp_res; // прибавляем результат в общую сумму
    tmp_arr[cnt] = tmp_res; // перезаписываем значение ячейки с которой работали
    ++cnt; // увеличиваем счетчик
    number = sum >> 8; // делим на 256, т.е. количество элементов массива
    HAL_ADC_IRQHandler(&hadc1);
    }
    собственно все, из недостатков, то что размер окна 256 если делать больше или меньше стандартных размеров типов языка, то придется вводить условие if. А так все шустро летает.

    • Елена Вставская
      Ну, думаю, что решение неплохое. Если Вам кажется, что окно из 256 отсчетов слишком велико, а условие ставить не хочется, то можно пойти другим путём. Например, для окна 16
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      uint16_t tmp_arr[16] = {0};

      static uint8_t cnt = 0;
      sum -= tmp_arr[cnt];
      tmp_res = HAL_ADC_GetValue(&hadc1);
      sum += tmp_res;
      tmp_arr[cnt] = tmp_res;
      ++cnt;
      cnt = cnt % 16; // находим остаток от деления на 16 **
      number = sum >> 4;
      Строку ** можно еще записать с помощью гашения старших битов с помощью логической операции:
      1
      cnt = cnt &amp; 0x0F;

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *