【问题标题】:How can I (efficiently) compute a moving average of a vector?我如何(有效地)计算向量的移动平均值?
【发布时间】:2015-01-14 21:12:26
【问题描述】:

我有一个向量,我想计算它的移动平均值(使用宽度为 5 的窗口)。

例如,如果有问题的向量是[1,2,3,4,5,6,7,8],那么

  • 结果向量的第一个条目应该是[1,2,3,4,5] 中所有条目的总和(即15);
  • 结果向量的第二个条目应该是[2,3,4,5,6] 中所有条目的总和(即20);
  • 等

最后,结果向量应该是[15,20,25,30]。我该怎么做?

【问题讨论】:

标签: matlab moving-average


【解决方案1】:

conv 功能就在您的身边:

>> x = 1:8;
>> y = conv(x, ones(1,5), 'valid')

y =
    15    20    25    30

基准测试

三个答案,三种不同的方法……这是一个使用timeit的快速基准测试(不同的输入大小,固定窗口宽度为5);如果您认为需要对其进行改进,请随时在其中(在 cmets 中)戳洞。

conv 成为最快的方法;它大约是coin's approach (using filter) 的两倍,大约是Luis Mendo's approach (using cumsum) 的四倍。

这是另一个基准测试(固定输入大小为1e4,不同的窗口宽度)。在这里,Luis Mendo's cumsum approach 显然是赢家,因为它的复杂性主要取决于输入的长度,并且对窗口的宽度不敏感。

结论

总而言之,你应该

  • 如果您的窗口相对较小,请使用conv 方法,
  • 如果您的窗口相对较大,请使用cumsum 方法。

代码(用于基准测试)

function benchmark

    clear all
    w = 5;                 % moving average window width
    u = ones(1, w); 
    n = logspace(2,6,60);  % vector of input sizes for benchmark
    t1 = zeros(size(n));   % preallocation of time vectors before the loop
    t2 = t1;
    th = t1;

    for k = 1 : numel(n)

        x = rand(1, round(n(k)));  % generate random row vector

        % Luis Mendo's approach (cumsum)
        f = @() luisMendo(w, x);
        tf(k) = timeit(f);

        % coin's approach (filter)
        g = @() coin(w, u, x);
        tg(k) = timeit(g);

        % Jubobs's approach (conv)
        h = @() jubobs(u, x);
        th(k) = timeit(h);
    end

    figure
    hold on
    plot(n, tf, 'bo')
    plot(n, tg, 'ro')
    plot(n, th, 'mo')
    hold off
    xlabel('input size')
    ylabel('time (s)')
    legend('cumsum', 'filter', 'conv')

end

function y = luisMendo(w,x)
    cs = cumsum(x);
    y(1,numel(x)-w+1) = 0; %// hackish way to preallocate result
    y(1) = cs(w);
    y(2:end) = cs(w+1:end) - cs(1:end-w);
end

function y = coin(w,u,x)
    y = filter(u, 1, x);
    y = y(w:end);
end

function jubobs(u,x)
    y = conv(x, u, 'valid');
end

function benchmark2

    clear all
    w = round(logspace(1,3,31));    % moving average window width 
    n = 1e4;  % vector of input sizes for benchmark
    t1 = zeros(size(n));   % preallocation of time vectors before the loop
    t2 = t1;
    th = t1;

    for k = 1 : numel(w)
        u = ones(1, w(k));
        x = rand(1, n);  % generate random row vector

        % Luis Mendo's approach (cumsum)
        f = @() luisMendo(w(k), x);
        tf(k) = timeit(f);

        % coin's approach (filter)
        g = @() coin(w(k), u, x);
        tg(k) = timeit(g);

        % Jubobs's approach (conv)
        h = @() jubobs(u, x);
        th(k) = timeit(h);
    end

    figure
    hold on
    plot(w, tf, 'bo')
    plot(w, tg, 'ro')
    plot(w, th, 'mo')
    hold off
    xlabel('window size')
    ylabel('time (s)')
    legend('cumsum', 'filter', 'conv')

end

function y = luisMendo(w,x)
    cs = cumsum(x);
    y(1,numel(x)-w+1) = 0; %// hackish way to preallocate result
    y(1) = cs(w);
    y(2:end) = cs(w+1:end) - cs(1:end-w);
end

function y = coin(w,u,x)
    y = filter(u, 1, x);
    y = y(w:end);
end

function jubobs(u,x)
    y = conv(x, u, 'valid');
end

【讨论】:

  • 看一看 R2016b:故事几乎相同。但是,R2016a 引入了 movmean 内置函数。对于小窗口大小的情况,它的性能与filter 方法(though slightly noisy)大致相当。对于大窗口大小的情况,它的性能与cumsum相当。
【解决方案2】:

另一种可能性是使用cumsum。这种方法可能需要比conv 更少的操作:

x = 1:8
n = 5;
cs = cumsum(x);
result = cs(n:end) - [0 cs(1:end-n)];

为了节省一点时间,您可以将最后一行替换为

%// clear result
result(1,numel(x)-n+1) = 0; %// hackish way to preallocate result
result(1) = cs(n);
result(2:end) = cs(n+1:end) - cs(1:end-n);

【讨论】:

  • @Jubobs 为什么是u = ones(1, 6)?不应该是u = ones(1, w)吗?您对y 的三个计算应该给出相同的大小。此外,为了可靠的计时使用timeit
  • @Jubobs 如果你更新你的基准测试(顺便说一句,已经 +1 了),你可以使用我的第二个版本吗?
  • 是的,6 是一个错字;我不确定它是如何到达那里的。稍后我将重新运行基准测试。我现在无法访问 MATLAB,但如果有机会,我会这样做(使用 timit)。
  • @Jubobs 我明白了。 cumsum 的优势仅在 w 的较大值时才明显。例如,对于w=50,它确实是最快的方法(在我的机器上)。很好的基准测试工作!
  • 是的,既然你已经把它拼写给我了,这一切都说得通了。伟大的!总的结论是,如果你的窗口很大,你最好使用cumsum,但如果你的窗口很窄,你应该使用conv。
【解决方案3】:

如果你想保留输入向量的大小,我建议使用filter

>> x = 1:8;
>> y = filter(ones(1,5), 1, x)

y =
     1     3     6    10    15    20    25     30

>> y = (5:end)

y =
     15    20    25    30

【讨论】:

  • 请注意,您错误地使用了filter。语法是filter(b,a,x),所以你应该改用filter(ones(1,5), 1, x)。之后您还应该丢弃结果的前 4 个元素。
猜你喜欢
  • 2015-05-03
  • 2017-02-20
  • 2017-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-18
相关资源
最近更新 更多