指数加权移动均值的计算方法及代码实现
本文主要介绍了一种指数加权移动均值的计算方法,并给出了一些计算示例。另外,本文还提供了所述算法的SQL和R语言代码实现。
引言
指数加权移动均值(Exponentially Weighted Moving Average, EWMA)是一种计算有序序列的加权均值的统计学方法。该方法可以根据各个观测值在序列中的位置分配呈指数变化的权重,使得序列中越靠后的观测值对计算结果的影响越大;并且,它还可以通过参数调节权重的变化幅度。由于具有这些特性,指数加权移动均值相对于常规的算术均值、加权均值和移动均值,更加适用于解决时间序列相关问题,例如:
1 常用于时间序列预测的指数平滑模型 (ETS模型),就是基于指数加权移动均值设计的。
但另一方面,指数加权移动均值的计算方法却比较复杂、现成的程序实现也并不多见,使得其日常应用比较受限。针对这些问题,本文将对该算法进行详细介绍,同时还将结合具体的例子给出算法的SQL和R语言代码实现;所提供的代码除了可以直接使用外,也可以作为参考材料指导读者在其他编程语言中实现该算法。
计算方法
以
定义 1
若是递归地对 定义 1 右侧所含的指数加权移动平均项
定义 2
假设
为了对上述偏差进行校正,可以将
定义 3
有了以上校正方法,即使参与计算的序列观测值个数比较少,结果也不会有太大的误差。因此,在计算截至序列某点(
参数意义
定义 1 / 定义 2 中的
如果
根据上面的理解,对于
图 2 直观地展示了参数
SQL代码实现及示例
本文将以PostgreSQL为例给出有关代码 2 并进行讲解。
2 这些代码在PostgreSQL 15.4中测试通过。
由于实际应用时,往往需要同时对多组序列计算指数加权移动均值;因此,为了提高参考价值,下面将使用含有多组序列的样例数据。
假设表xt
保存了由信号A和信号B的观测值分别组成的序列,且该表的具体定义及所含数据如下:
CREATE TEMPORARY TABLE xt (
VARCHAR, -- 信号标识
signal INT, -- 观测值序号
t DOUBLE PRECISION -- 观测值大小
x
);
INSERT INTO xt
VALUES ('A', 1, 11.0), ('B', 1, 13.0),
'A', 2, 15.0), ('B', 2, 19.0),
('A', 3, 22.0), ('B', 3, 20.0),
('A', 4, 23.0), ('B', 4, 22.0),
('A', 5, 25.0), ('B', 5, 26.0),
('A', 6, 30.0), ('B', 6, 32.0),
('A', 7, 37.0), ('B', 7, 34.0),
('A', 8, 40.0); (
如果以代码变量x0
代表算法参数w
代表参数correct_bias
表示是否对偏差进行校正,再假设x0 = 0
、w = 0.25
、correct_bias = TRUE
,那么基于 定义 1 和 定义 3 (下称方法1)、以及基于 定义 2 和 定义 3 (下称方法2)的两种指数加权移动均值计算方法实现如下所示:
DO $$
DECLARE
DOUBLE PRECISION := 0;
x0 DOUBLE PRECISION := 0.25;
w BOOLEAN := TRUE;
correct_bias BEGIN
-- 方法1:对应定义1和定义3
CREATE TEMPORARY TABLE result_1 AS
WITH RECURSIVE ta AS (
SELECT CAST(NULL AS VARCHAR) AS signal,
CAST(0 AS INT) AS t,
CAST(x0 AS DOUBLE PRECISION) AS x
UNION ALL
SELECT tb.signal, tb.t AS t, (1 - w) * ta.x + w * tb.x AS x
FROM ta
INNER JOIN xt AS tb ON (ta.signal IS NULL OR ta.signal = tb.signal) AND ta.t + 1 = tb.t
)SELECT signal, t, x / CASE WHEN correct_bias THEN (1 - (1 - w) ^ t) ELSE 1 END AS x
FROM ta
WHERE t >= 1
ORDER BY signal, t;
-- 方法2:对应定义2和定义3
CREATE TEMPORARY TABLE result_2 AS
SELECT signal, t, (sum(w * x * (1 - w) ^ i) + (1 - w) ^ t * x0) / CASE WHEN correct_bias THEN (1 - (1 - w) ^ t) ELSE 1 END AS x
FROM (SELECT ta.signal, ta.t AS t, ta.t - tb.t AS i, tb.x
FROM xt AS ta
INNER JOIN xt AS tb ON ta.signal = tb.signal
WHERE ta.t >= tb.t) ta
GROUP BY signal, t
ORDER BY signal, t;
END $$;
表 1 给出了计算结果(基于两种方法的计算结果相同):
signal | t | x |
---|---|---|
A | 1 | 11.0 |
A | 2 | 13.3 |
A | 3 | 17.1 |
A | 4 | 19.2 |
A | 5 | 21.1 |
A | 6 | 23.8 |
A | 7 | 27.6 |
A | 8 | 31.1 |
B | 1 | 13.0 |
B | 2 | 16.4 |
B | 3 | 18.0 |
B | 4 | 19.4 |
B | 5 | 21.6 |
B | 6 | 24.8 |
B | 7 | 27.4 |
值得一提的是,尽管两种计算方法实现所得的最终结果相同,但它们存在一些功能要求和性能上的差异:
- 基于方法1的SQL实现需要用到递归查询,但并非所有数据库管理系统支持该功能,因此可移植性并不如基于方法2的实现;
- 两者在计算速度上往往具有差异,且在不同情况下优势可以互换 3 。
3 简单的测试结果提示,当序列观察值个数(本例中字段t
的唯一值个数)较多时,基于方法1的实现可能更高效;当序列分组个数(本例中字段signal
的唯一值个数)较多时,则基于方法2的实现可能更高效。
R语言代码实现及示例
下面给出的ewma()
函数可以用于计算校正后的指数加权移动均值,其中参数x
为原始序列数据、参数w
为指数加权的权重、参数x0
为序列的初始值、参数correct_bias
代表是否校正偏差;与前例类似,这里分别给出基于 定义 1 和 定义 3 (方法1)、以及基于 定义 2 和 定义 3 (方法2)的代码实现(两者的计算结果相同,但性能存在差异):
# 方法1:对应定义1和定义3
# 绝大多数情况下计算速度较快,推荐使用
<- function(x, w = 0.25, x0 = 0, correct_bias = TRUE) {
ewma <- Reduce(function(xt_1, xt) {
result 1 - w) * xt_1 + w * xt
(x = x, accumulate = T, init = x0)[-1]
}, if (correct_bias) {
<- seq_along(x)
t <- result / (1 - (1 - w) ^ t)
result
}
result
}
# 方法2:对应定义2和定义3
# 绝大多数情况下计算速度较慢,不推荐使用
# ewma <- function(x, w = 0.25, x0 = 0, correct_bias = TRUE) {
# result <- vapply(seq_along(x), function(t) {
# i <- seq_len(t)
# (1 - w) ^ t * x0 + sum(x[i] * w * (1 - w) ^ (t - i))
# }, double(1))
# if (correct_bias) {
# t <- seq_along(x)
# result <- result / (1 - (1 - w) ^ t)
# }
# result
# }
下面将前例中信号A的数据赋值给向量xt
,并仅以该数据演示计算结果:
<- c(11.0, 15.0, 22.0, 23.0, 25.0, 30.0, 37.0, 40.0) xt
以下是利用函数ewma()
算出的校正前和校正后的指数加权移动均值:
# 校正前
ewma(xt, w = 0.25, x0 = 0, correct_bias = FALSE)
[1] 2.8 5.8 9.9 13.1 16.1 19.6 23.9 28.0
# 校正后
ewma(xt, w = 0.25, x0 = 0, correct_bias = TRUE)
[1] 11.0 13.3 17.1 19.2 21.1 23.8 27.6 31.1