在Java中实现指数移动平均

13

我基本上有一个这样的值数组:

0.25, 0.24, 0.27, 0.26, 0.29, 0.34, 0.32, 0.36, 0.32, 0.28, 0.25, 0.24, 0.25
上面的数组过于简化了,在我的真正代码中,我每毫秒收集1个值,并且我需要在一个我编写的算法上处理输出,以查找在某个时间点之前最接近的峰值。我的逻辑失败了,因为在上面的例子中,0.36是真正的峰值,但是我的算法会向后查找并将最后一个数字0.25视为峰值,因为它之前有一次降低到0.24。
目标是将这些值应用于算法,以便对它们进行“平滑”处理,使我拥有更加线性的值。(即:我希望我的结果是弯曲的,而不是参差不齐的)
有人告诉我要对我的值应用指数移动平均滤波器。我该怎么做?对我来说,阅读数学方程式非常困难,我更善于处理代码。
如何处理数组中的值,并将指数移动平均计算应用于它们,以使它们更加平滑?
float[] mydata = ...
mySmoothedData = exponentialMovingAverage(mydata, 0.5);

float[] exponentialMovingAverage(float[] input, float alpha) {
    // what do I do here?
    return result;
}
6个回答

36

要计算指数移动平均值,您需要保留一些状态并且需要一个调整参数。这需要一个小类(假设您使用的是Java 5或更高版本):

class ExponentialMovingAverage {
    private double alpha;
    private Double oldValue;
    public ExponentialMovingAverage(double alpha) {
        this.alpha = alpha;
    }

    public double average(double value) {
        if (oldValue == null) {
            oldValue = value;
            return value;
        }
        double newValue = oldValue + alpha * (value - oldValue);
        oldValue = newValue;
        return newValue;
    }
}

使用所需的衰减参数(可能需要调整;应在0到1之间)实例化,然后使用 average(…) 进行过滤。


当阅读某些数学递归的页面时,你需要知道的是,数学家喜欢使用下标来表示数组和序列。(他们还有一些其他符号,这并不有帮助。)但是,EMA非常简单,因为您只需要记住一个旧值;无需复杂的状态数组。


事实上,EMA是最容易编码的移动平均线(前提是您有一个类似Java对象的状态存储位置),因为您不需要进行复杂的状态管理。 - Donal Fellows
1
那么我基本上只需要这样写 for (float dude : input) { output[index] = ema.average(dude); } - Naftuli Kay
1
@TKKocheran: 差不多就是这样。当事情变得简单时,不是很好吗?(如果从一个新序列开始,请获取一个新的平均值计算器。)请注意,由于边界效应,平均序列中的前几个项将会跳动一些,但其他移动平均数也会出现这种情况。然而,一个好处是你可以将移动平均逻辑封装到平均值计算器中,并进行实验,而不会太大干扰程序的其余部分。 - Donal Fellows

4
我很难理解你的问题,但我会尽力回答。
1)如果算法找到的是0.25而不是0.36,则是错误的。这是因为它假设有单调递增或递减(即“始终上升”或“始终下降”)。除非您平均所有数据,否则按您提供的方式呈现的数据点是非线性的。如果您真的想在两个时间点之间找到最大值,则从t_min到t_max切割您的数组,并找到该子数组的最大值。
2)现在,“移动平均值”的概念非常简单:想象我有以下列表:[1.4,1.5,1.4,1.5,1.5]。我可以通过取两个数字的平均值来“平滑它”:[1.45,1.45,1.45,1.5]。注意第一个数字是1.5和1.4(第二个和第一个数字)的平均值;第二个(新列表)是1.4和1.5(旧列表的第三个和第二个)的平均值;第三个(新列表)是1.5和1.4(第四个和第三个数字)的平均值,依此类推。我可以将其设置为“周期三”或“四”,或者“n”。注意数据要平滑得多。观看移动平均线的效果的好方法是进入Google Finance,选择一只股票(尝试特斯拉汽车;相当波动 (TSLA)),并在图表底部单击“技术指标”。选择给定周期的“移动平均值”,并选择“指数移动平均值”以比较它们之间的差异。
指数移动平均线只是另一种详细说明,但它使更“老”的数据的权重小于“新”的数据; 这是一种将平滑向后偏置的方法。请阅读维基百科条目。
所以,这更像是评论而不是答案,但是评论框太小了。祝好运。

在编程中相关内容的翻译:+1:找到极小值和极大值相对容易,但确定它们的重要性则更难,因为您需要查看与长期模式的偏差。这是在假设这样的模式确实存在的情况下。 - Donal Fellows

0
以滚动方式进行...我还使用commons.apache math库。
  public LinkedList EMA(int dperiods, double alpha)
                throws IOException {
            String line;
            int i = 0;
            DescriptiveStatistics stats = new SynchronizedDescriptiveStatistics();
            stats.setWindowSize(dperiods);
            File f = new File("");
            BufferedReader in = new BufferedReader(new FileReader(f));
            LinkedList<Double> ema1 = new LinkedList<Double>();
            // Compute some statistics
            while ((line = in.readLine()) != null) {
                double sum = 0;
                double den = 0;
                System.out.println("line: " + " " + line);
                stats.addValue(Double.parseDouble(line.trim()));
                i++;
                if (i > dperiods)
                    for (int j = 0; j < dperiods; j++) {
                        double var = Math.pow((1 - alpha), j);
                        den += var;
                        sum += stats.getElement(j) * var;
                        System.out.println("elements:"+stats.getElement(j));
                        System.out.println("sum:"+sum);
                    }
                else
                    for (int j = 0; j < i; j++) {
                        double var = Math.pow((1 - alpha), j);
                        den += var;
                        sum += stats.getElement(j) * var;
                    }
                ema1.add(sum / den);
                System.out.println("EMA: " + sum / den);
            }
            return ema1;
        }

1
在点赞之前,请使用commons.apache math库。 :( - user3392362

0

-2
public class MovingAvarage {

public static void main(String[] args) {
    double[] array = {1.2, 3.4, 4.5, 4.5, 4.5};

    double St = 0D;
    for(int i=0; i<array.length; i++) {
        St = movingAvarage(St, array[i]);
    }
    System.out.println(St);

}

private static double movingAvarage(double St, double Yt) {
    double alpha = 0.01, oneMinusAlpha = 0.99;
    if(St <= 0D) {
        St = Yt;
    } else {
        St = alpha*Yt + oneMinusAlpha*St;
    }
    return St;
   }

}

1
虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - Alex Riabov

-5

如果你在数学方面遇到了困难,可以使用简单移动平均代替指数移动平均。因此,您得到的输出将是最后x个术语除以x。未经测试的伪代码:

int data[] = getFilled();
int outdata[] = initializeme()
for (int y = 0; y < data.length; y++)
    int sum = 0;
    for (int x = y; x < y-5; x++)
        sum+=data[x];
    outdata[y] = sum / 5;

请注意,由于您在第二个数据点上时无法平均最后5个术语,因此您需要处理数据的开始和结束部分。 此外,有更有效的方法来计算这个移动平均值(sum = sum - oldest + newest),但这是为了让您理解正在发生的概念。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接