При обработке экспериментальных данных часто возникает необходимость их усреднения. С этой целью часто используют алгоритм арифметического скользящего среднего.
Скользящее среднее — общее название для семейства функций, значения которых в каждой точке определения равны среднему значению исходной функции за предыдущий период. Скользящие средние обычно используются с данными временных рядов для сглаживания краткосрочных колебаний и выделения основных тенденций или циклов.
Математически скользящее среднее является одним из видов свертки, и поэтому его можно рассматривать как фильтр низких частот, используемых в обработке сигналов.
Простое (арифметическое) скользящее среднее численно равно среднему арифметическому значений исходной функции за установленный период и вычисляется по формуле:
{\displaystyle{\widetilde{f}_k ={\frac {1}{h} \sum_{i=l}^k f_i}}}
где {\displaystyle{f_i}} – исходные значения рассматриваемой функции, {\displaystyle{h=k-l}} – сглаживающий интервал – количество значений исходной функции для расчета скользящего среднего.
На рисунке представлены функции скользящего среднего для исходной последовательности значений с разной величиной сглаживающего интервала.
Чем шире сглаживающий интервал, тем более плавным будет график результирующей функции. Однако с другой стороны, увеличение сглаживающего интервала приводит к временному сдвигу усредненной функции относительно исходной.
Реализация на C++
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}} (последнее значение).
При реализации рекуррентной формулы удобно использовать массив для хранения значений сглаживающего интервала, а также переменную, содержащую сумму этих значений. Однако такая реализация доступна только в случае если значения функции хранятся в целочисленном виде.
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;
}
Результат выполнения