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

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

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

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

Математически скользящее среднее является одним из видов свертки, и поэтому его можно рассматривать как фильтр низких частот, используемых в обработке сигналов.

Простое (арифметическое) скользящее среднее численно равно среднему арифметическому значений исходной функции за установленный период и вычисляется по формуле:

{\displaystyle{\widetilde{f}_k ={\frac {1}{h} \sum_{i=l}^k f_i}}}

где {\displaystyle{f_i}} – исходные значения рассматриваемой функции, {\displaystyle{h=k-l}} – сглаживающий интервал – количество значений исходной функции для расчета скользящего среднего.

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

Скользящее среднее с разным интервалом усреднения

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

Реализация на 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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()%101)-50)*0.05;
    f[2][i] = 0; // для заполнения усредненными значениями
  }
  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 < n; i++)
    mas[i] = 0;
  for (int i = 0; i < n - size; i++)
  {
    mas[index] = x[1][i];
    index++;
    if (index >= size)
      index = 0; // возврат к началу "окна"
    sumx = 0;
    for (int k = 0; k < size; k++)
      sumx += mas[k];
    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);
  cout.width(2);
  cout.fixed;
  cout.precision(1);
  for (int i = 0; i < n; i++)
  {
    cout.width(2);
    cout << x[0][i] << ": ";
    cout.width(4);
    cout.precision(3);
    cout << x[1][i] << " - ";
    cout.width(5);
    cout.precision(3);
    cout << x[2][i] << endl;
  }
  cin.get();
  cin.get();
  return 0;
}

Результат выполнения

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

В примере использовался циклический буфер mas для хранения усредняемых значений. Индекс index движется по циклу, и как только выходит за пределы буфера, возвращается на начальную (нулевую) позицию.

Из предыдущего своего значения простое скользящее среднее может быть получено по следующей рекуррентной формуле:

{\displaystyle{\widetilde{ f}_{k+1} = \widetilde{f}_k — {\frac {f_{k-h}}{h}} + {\frac {f_k}{h}}}}

где {\displaystyle{f_{k-h}}} — значение исходной функции в точке {\displaystyle{k-h}} (в случае временного ряда, самое «раннее» значение исходной функции, используемое для вычисления предыдущей скользящей средней), {\displaystyle{f_k}} — значение исследуемой функции в точке {\displaystyle{k}} (последнее значение).

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

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <stdlib.h>
using namespace std;
const double pi = 3.1415926535897932384626433832795;
// Задание начального набора значений
void getData(int n, int *y) 
{
  for (int i = 0; i < n; i++)
    y[i] = 1000 * sin(i * 2 * pi / 100);
}
// Вычисление результата скользящего среднего
void getMA(int* y, int *z, int n, int size) 
{
  // size - количество отсчетов интервала усреднения
  int sumx = 0; // сумма отсчетов на интервале (рекуррентная формула)
  int *mas; // массив для хранения size отсчетов
  int index = 0; // индекс элемента массива
  mas = new int[size];
  for (int i = 0; i < size; i++) mas[i] = 0;
  for (int i = 0; i < n - size; i++)   
  {
    sumx -= mas[index];
    mas[index] = y[i];
    sumx += mas[index];
    index++;
    if (index >= size)
      index = 0; // возврат к началу "окна"
    z[i] = sumx / size;
  }
  return;
}
int main()
{
  int n, h;
  int* y, * z;
  system("chcp 1251");
  system("cls");
  cout << "Введите количество точек: ";
  cin >> n;
  cout << "Введите сглаживающий интервал: ";
  cin >> h;
  y = new int[n];
  z = new int[n];
  for (int i = 0; i < n; i++)
  {
    y[i] = 0;
    z[i] = 0;
  }
  getData(n, y);
  getMA(y, z, n, h);
  cout.width(2);
  cout.fixed;
  cout.precision(1);
  for (int i = 0; i < n; i++)
  {
    cout.width(2);
    cout << i << ": ";
    cout.width(4);
    cout.precision(4);
    cout << y[i] << " - ";
    cout.width(4);
    cout.precision(4);
    cout << z[i] << endl;
  }
  system("pause");
  return 0;
}

Результат выполнения

Прокрутить вверх