移动平均线是分析数据趋势和模式的基本工具,广泛应用于金融、经济和工程领域。
它们有助于消除短期波动并揭示潜在趋势,使数据更易于解释。
在本教程中,我们将探索计算移动平均值的各种方法和技术,从传统方法到库和 Stream API。
计算移动平均线的常用方法
在本节中,我们将探讨计算移动平均线的三种常用方法。
1.使用 Apache Commons 数学库
Apache Commons Math是一个功能强大的 Java 库,提供广泛的数学和统计函数,包括计算移动平均值的工具。
通过利用Apache Commons Math 库中的DescriptiveStatistics类,我们可以简化移动平均计算的过程,并利用优化的算法进行高效的数据处理。它将数据点添加到统计对象并检索平均值(表示移动平均值)。
让我们使用DescriptiveStatistics类来计算带有windowSize的移动平均值:
public class MovingAverageWithApacheCommonsMath { private final DescriptiveStatistics stats; public MovingAverageWithApacheCommonsMath(int windowSize) { this.stats = new DescriptiveStatistics(windowSize); } public void add(double value) { stats.addValue(value); } public double getMovingAverage() { return stats.getMean(); } }
|
让我们测试一下我们的实现:@Test public void whenValuesAreAdded_shouldUpdateAverageCorrectly() { MovingAverageWithApacheCommonsMath movingAverageCalculator = new MovingAverageWithApacheCommonsMath(3); movingAverageCalculator.add(10); assertEquals(10.0, movingAverageCalculator.getMovingAverage(), 0.001); movingAverageCalculator.add(20); assertEquals(15.0, movingAverageCalculator.getMovingAverage(), 0.001); movingAverageCalculator.add(30); assertEquals(20.0, movingAverageCalculator.getMovingAverage(), 0.001); }
|
首先,我们创建一个窗口大小为 3 的MovingAverageWithApacheCommonsMath类的实例。然后,将三个值(10、20 和 30)分别添加到计算器中,并验证其平均值。2.使用循环缓冲区方法
循环缓冲区方法是计算移动平均值的经典方法,并以其高效的内存使用而闻名。这种方法很简单,在某些情况下可能会提供更好的性能,特别是当我们担心外部依赖项的开销时。
在这种方法中,新的数据点会覆盖最旧的数据点,并且平均值是根据缓冲区中的当前元素计算的。
通过循环循环缓冲区,我们可以为每次更新实现恒定的时间复杂度,使其适合实时数据处理应用。
让我们使用循环缓冲区计算移动平均值:
public class MovingAverageByCircularBuffer { private final double[] buffer; private int head; private int count; public MovingAverageByCircularBuffer(int windowSize) { this.buffer = new double[windowSize]; } public void add(double value) { buffer[head] = value; head = (head + 1) % buffer.length; if (count < buffer.length) { count++; } } public double getMovingAverage() { if (count == 0) { return Double.NaN; } double sum = 0; for (int i = 0; i < count; i++) { sum += buffer[i]; } return sum / count; } }
|
我们来写一个测试用例来验证该方法:@Test public void whenValuesAreAdded_shouldUpdateAverageCorrectly() { MovingAverageByCircularBuffer ma = new MovingAverageByCircularBuffer(3); ma.add(10); assertEquals(10.0, ma.getMovingAverage(), 0.001); ma.add(20); assertEquals(15.0, ma.getMovingAverage(), 0.001); ma.add(30); assertEquals(20.0, ma.getMovingAverage(), 0.001); }
|
我们创建一个窗口大小为 3 的MovingAverageByCircularBuffer类的实例。添加每个值后,测试断言计算出的移动平均值与预期值匹配,容差为 0.001。3.使用指数移动平均线
另一种方法是使用指数平滑来计算移动平均值。
指数平滑为较旧的观测值分配指数递减的权重,这对于捕获趋势和对数据变化快速做出反应非常有用:
public class ExponentialMovingAverage { private double alpha; private Double previousEMA; public ExponentialMovingAverage(double alpha) { if (alpha <= 0 || alpha > 1) { throw new IllegalArgumentException("Alpha must be in the range (0, 1]"); } this.alpha = alpha; this.previousEMA = null; } public double calculateEMA(double newValue) { if (previousEMA == null) { previousEMA = newValue; } else { previousEMA = alpha * newValue + (1 - alpha) * previousEMA; } return previousEMA; } }
|
在这里,alpha参数控制衰减率,较小的值赋予最近的观察结果更大的权重。当我们想要对数据变化快速做出反应,同时仍然捕捉长期趋势时,指数移动平均线特别有用。
我们用一个测试用例来验证一下:
@Test public void whenValuesAreAdded_shouldUpdateExponentialMovingAverageCorrectly() { ExponentialMovingAverage ema = new ExponentialMovingAverage(0.4); assertEquals(10.0, ema.calculateEMA(10.0), 0.001); assertEquals(14.0, ema.calculateEMA(20.0), 0.001); assertEquals(20.4, ema.calculateEMA(30.0), 0.001); }
|
我们首先创建平滑因子 ( alpha ) 为 0.4 的ExponentialMovingAverage (EMA)实例。然后,当添加每个值时,测试断言计算出的 EMA 与预期值相匹配,误差范围为 0.001。
4.基于流的方法
我们可以利用Stream API 以更具功能性和声明性的方式计算移动平均线。如果我们想要处理数据流或集合,这种方法特别有用。
以下是我们如何使用基于流的方法来计算移动平均值的简化示例:
public class MovingAverageWithStreamBasedApproach { private int windowSize; public MovingAverageWithStreamBasedApproach(int windowSize) { this.windowSize = windowSize; } public double calculateAverage(double[] data) { return DoubleStream.of(data) .skip(Math.max(0, data.length - windowSize)) .limit(Math.min(data.length, windowSize)) .summaryStatistics() .getAverage(); } }
|
在这里,我们从输入数据数组创建一个流,跳过指定窗口大小之外的元素,将流限制在 windowSize 内,然后使用summaryStatistics()计算平均值。这种方法利用Java Streams API的函数式编程功能以简洁高效的方式执行计算。
现在,让我们编写一些 JUnit 测试来确保我们的代码按预期工作:
@Test public void whenValidDataIsPassed_shouldReturnCorrectAverage() { double[] data = {10, 20, 30, 40, 50}; int windowSize = 3; double expectedAverage = 40; MovingAverageWithStreamBasedApproach calculator = new MovingAverageWithStreamBasedApproach(windowSize); double actualAverage = calculator.calculateAverage(data); assertEquals(expectedAverage, actualAverage); }
|
在这些测试中,我们检查calculateAverage()方法是否返回给定场景(例如有效数据和windowSize)的正确平均值。附加方法
虽然上述方法是在 Java 中计算移动平均线的一些更方便、更有效的方法,但我们可以根据我们的具体要求和约束考虑其他方法。在这里,我们将介绍两种这样的方法。
1.并行处理
如果性能是我们的首要任务,并且我们可以使用多个 CPU 内核,那么我们可以利用并行处理技术更有效地计算移动平均值。
Java提供了对并行流的支持,它可以自动将计算分布到多个线程上。
2.加权移动平均线
加权移动平均 (WMA) 是一种计算移动平均的方法,它为窗口内的每个数据点分配不同的权重。
权重通常是根据预定义的标准来确定的,例如重要性、相关性或与窗口中心的接近度。
3.累积移动平均线
累积移动平均线 (CMA) 计算截至特定时间点的所有数据点的平均值。与其他移动平均方法不同,CMA 不使用固定大小的窗口,而是包含所有可用数据。
结论
计算移动平均线是时间序列分析的一个基本方面,其应用涵盖金融、经济和工程等各个领域。
使用 Apache Commons Math、循环缓冲区和指数移动平均技术,分析师可以深入了解数据的潜在趋势和模式。
此外,探索加权和累积移动平均线可以扩展分析师的工具包,从而能够对时间序列数据进行更复杂的分析和解释。
同样,选择完全取决于具体的项目要求和偏好。