web-dev-qa-db-ja.com

カウントとデータ合計を維持せずに移動平均を計算する方法は?

これまでに受信したカウントと合計データを保存せずに移動累積平均を計算する方法を見つけようとしています。

私は2つのアルゴリズムを思いつきましたが、両方ともカウントを保存する必要があります:

  • 新しい平均=((古いカウント*古いデータ)+次のデータ)/次のカウント
  • 新しい平均=古い平均+(次のデータ-古い平均)/次のカウント

これらの方法の問題は、カウントが次第に大きくなり、結果の平均の精度が失われることです。

最初の方法は、明らかに1離れている古いカウントと次のカウントを使用します。これにより、カウントを削除する方法があるかもしれないと思うようになりましたが、残念ながらまだ見つかりませんでした。しかし、もう少し進めて、2番目の方法になりましたが、まだカウントがあります。

それは可能ですか、それとも不可能を探しているだけですか?

97
user1705674

簡単にできます:

double approxRollingAverage (double avg, double new_sample) {

    avg -= avg / N;
    avg += new_sample / N;

    return avg;
}

ここで、Nは、平均化するサンプルの数です。この近似は、指数移動平均に相当することに注意してください。参照: C++でローリング/移動平均を計算

87
Muis
New average = old average * (n-1)/n + new value /n

これは、カウントが1つの値だけ変更されたと仮定しています。 M値によって変更される場合:

new average = old average * (n-len(M))/n + (sum of values in M)/n).

これは数式です(最も効率的な数式だと思います)。自分でコードを追加できると信じています

70

ブログ 実行中のサンプル分散計算で、平均も ウェルフォード法 を使用して計算されます。

enter image description here

SVG画像をアップロードできないのは残念です。

25
Flip

ここでは、MuisAbdullah Al-Ageel、およびFlipの答えは数学的にはすべて同じです

もちろん、JoséManuel Ramosの分析は、丸め誤差がそれぞれにわずかに異なる影響を与えることを説明していますが、実装に依存しており、各回答がコードに適用される方法に基づいて変化します。

ただし、かなり大きな違いがあります

MuisNFlipk、およびAbdullah Al-AgeelnAbdullah Al-Ageelnがどうあるべきかを完全には説明しませんが、NkNが異なりますは、「平均オーバーしたいサンプルの数」であり、kはサンプリングされた値のカウントです。 (Nのサンプル数が正確であるかどうかは疑問ですが)

そして、以下の答えになります。それは本質的に古いものと同じ指数加重移動平均ですので、探しているなら代わりに、ここで停止します。

指数加重移動平均

最初は:

average = 0
counter = 0

各値に対して:

counter += 1
average = average + (value - average) / min(counter, FACTOR)

違いはmin(counter, FACTOR)部分です。これは、min(Flip's k, Muis's N)と同じです。

FACTORは、平均が最新のトレンドに「追いつく」速度に影響する定数です。数字が小さいほど速くなります。 (1では、もはや平均ではなく、単に最新の値になります。)

この答えには、実行カウンターcounterが必要です。問題がある場合は、min(counter, FACTOR)FACTORだけに置き換えて、Muisの答えに変えることができます。これを行う際の問題は、移動平均がaverageの初期化の影響を受けます。 0に初期化された場合、そのゼロは平均から抜け出すのに長い時間がかかる可能性があります。

見た目

Exponential moving average

11
antak

Flipの答えは、Muisのものよりも計算的に一貫しています。

二重数値形式を使用すると、Muisアプローチの丸めの問題を確認できます。

The Muis approach

除算と減算を行うと、以前の保存値に丸めが表示され、変更されます。

ただし、Flipアプローチでは、格納された値が保持され、除算の数が減るため、丸めが削減され、格納された値に伝播されるエラーが最小限に抑えられます。追加すると、追加するものがある場合にのみ丸めが表示されます(Nが大きい場合、追加するものはありません)

The Flip approach

大きな値を平均すると、その平均はゼロになりますが、これらの変化は顕著です。

スプレッドシートプログラムを使用して結果を示します。

まず、得られた結果: Results

A列とB列は、それぞれnとX_nの値です。

C列はフリップアプローチであり、D列はミュイスアプローチであり、結果は平均に格納されます。 E列は、計算で使用される中間値に対応しています。

偶数値の平均を示すグラフが次のグラフです:

Graph

ご覧のとおり、両方のアプローチには大きな違いがあります。

7

比較のためにJavaScriptを使用した例:

https://jsfiddle.net/drzaus/Lxsa4rpz/

function calcNormalAvg(list) {
    // sum(list) / len(list)
    return list.reduce(function(a, b) { return a + b; }) / list.length;
}
function calcRunningAvg(previousAverage, currentNumber, index) {
    // [ avg' * (n-1) + x ] / n
    return ( previousAverage * (index - 1) + currentNumber ) / index;
}
(function(){
  // populate base list
var list = [];
function getSeedNumber() { return Math.random()*100; }
for(var i = 0; i < 50; i++) list.Push( getSeedNumber() );

  // our calculation functions, for comparison
function calcNormalAvg(list) {
        // sum(list) / len(list)
        return list.reduce(function(a, b) { return a + b; }) / list.length;
}
function calcRunningAvg(previousAverage, currentNumber, index) {
        // [ avg' * (n-1) + x ] / n
        return ( previousAverage * (index - 1) + currentNumber ) / index;
}
  function calcMovingAvg(accumulator, new_value, alpha) {
        return (alpha * new_value) + (1.0 - alpha) * accumulator;
}

  // start our baseline
var baseAvg = calcNormalAvg(list);
var runningAvg = baseAvg, movingAvg = baseAvg;
console.log('base avg: %d', baseAvg);
  
  var okay = true;
  
  // table of output, cleaner console view
  var results = [];

  // add 10 more numbers to the list and compare calculations
for(var n = list.length, i = 0; i < 10; i++, n++) {
        var newNumber = getSeedNumber();

        runningAvg = calcRunningAvg(runningAvg, newNumber, n+1);
        movingAvg = calcMovingAvg(movingAvg, newNumber, 1/(n+1));

        list.Push(newNumber);
        baseAvg = calcNormalAvg(list);

        // assert and inspect
        console.log('added [%d] to list at pos %d, running avg = %d vs. regular avg = %d (%s), vs. moving avg = %d (%s)'
                , newNumber, list.length, runningAvg, baseAvg, runningAvg == baseAvg, movingAvg, movingAvg == baseAvg
        )
results.Push( {x: newNumber, n:list.length, regular: baseAvg, running: runningAvg, moving: movingAvg, eqRun: baseAvg == runningAvg, eqMov: baseAvg == movingAvg } );

if(runningAvg != baseAvg) console.warn('Fail!');
okay = okay && (runningAvg == baseAvg);    
}
  
  console.log('Everything matched for running avg? %s', okay);
  if(console.table) console.table(results);
})();
4
drzaus

Java8の場合:

LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();

IntSummaryStatisticsDoubleSummaryStatisticsもあります...

0
jmhostalet