web-dev-qa-db-ja.com

リストを保存せずにリストの中央値を計算または概算する方法

一連の値の中央値を計算しようとしていますが、すべての値を保存したくないので、メモリ要件が低下する可能性があります。個々の値をすべて保存およびソートせずに中央値を計算または概算する方法はありますか?

理想的には、次のようなコードを書きたいと思います

var medianCalculator = new MedianCalculator();
foreach (var value in SourceData)
{
  medianCalculator.Add(value);
}
Console.WriteLine("The median is: {0}", medianCalculator.Median);

必要なのは、実際のMedianCalculatorコードだけです。

更新:中央値を計算しようとしている値に既知のプロパティがあるかどうかを尋ねる人がいます。答えはイエスです。 1つの値は、約-25から-0.5まで0.5ずつ増加します。もう1つは、-120から-60まで0.5刻みです。これは、各値に何らかの形のヒストグラムを使用できることを意味すると思います。

ありがとう

ニック

43
Nick Randell

値が離散的であり、個別の値の数が多すぎない場合は、各値がヒストグラムで発生する回数を累積し、ヒストグラムカウントから中央値を見つけることができます(上部と下部のカウントを合計するだけ)ヒストグラムの中央に到達するまで)。または、それらが連続値である場合、それらをビンに分配することができます-正確な中央値はわかりませんが、範囲が与えられます。さらに正確に知る必要がある場合は、リストを繰り返し調べて、中央ビンの要素。

42
David Z

'remedian'統計があります。まず、それぞれ長さbのk個の配列を設定します。データ値は最初の配列に入力され、これがいっぱいになると、中央値が計算され、次の配列の最初の位置に格納されます。その後、最初の配列が再利用されます。 2番目の配列がいっぱいになると、その値の中央値が3番目の配列の最初のposなどに格納されます。

シンプルでかなり堅牢です。参考はこちら...

http://web.ipac.caltech.edu/staff/fmasci/home/astro_refs/Remedian.pdf

お役に立てれば

マイケル

39
michael

私はこれらの増分/再帰平均と中央値推定量を使用します。これらはどちらも定数ストレージを使用します。

mean += eta * (sample - mean)
median += eta * sgn(sample - median)

ここで、etaは小さな学習率パラメーター(0.001など)で、sgn()は{-1、0、1}のいずれかを返す符号関数です。

このタイプの増分平均推定量は、至る所で使用されているようです。教師なしニューラルネットワーク学習ルールでは、その利点(外れ値に対する堅牢性)にも関わらず、中央値バージョンはそれほど一般的ではないようです。中央値バージョンは、多くのアプリケーションで平均推定量の代わりとして使用できるようです。

私は同様の形のインクリメンタルモード推定器を見たいです...

(注:これも同様のトピックに投稿しました: "オンライン"(反復子)アルゴリズムで統計的中央値、モード、歪度、尖度を推定しますか?

19
Tyler Streeter

ここにあなたが試すかもしれないクレイジーなアプローチがあります。これはストリーミングアルゴリズムの古典的な問題です。ルールは

  1. メモリが限られている、たとえばO(log n)とします。ここで、nは必要なアイテムの数です
  2. 一度に各アイテムを見て、その時その場所でそれをどうするかを決めることができます。それを保存すると、メモリが消費され、捨てると永久に失われます。

中央値を見つけるためのアイデアは簡単です。リストからO(1 / a^2 * log(1 / p)) * log(n)要素をランダムにサンプリングします。これは、貯水池サンプリングを介して行うことができます( 前の質問 を参照)。次に、従来の方法を使用して、サンプリングした要素から中央値を返すだけです。

返されるアイテムのインデックスは、少なくとも_(1 +/- a) / 2_の確率で_1-p_になることが保証されます。したがって、失敗する確率pがあります。より多くの要素をサンプリングすることで、それを選択できます。また、中央値を返したり、返された項目の値が中央値に近いことは保証されません。リストを並べ替えると、返された項目はリストの半分に近くなります。

このアルゴリズムはO(log n)追加スペースを使用し、線形時間で実行されます。

14
Pall Melsted

これは、一般に正しくするために、特に、既にソートされている、またはリストの「開始」に一連の値があるがリストの最後に異なる範囲の値がある退化した系列を処理するのは難しいです。

ヒストグラムを作成する基本的な考え方は最も有望です。これにより、分布情報を蓄積し、そこからクエリ(中央値など)に答えることができます。すべての値を保存するわけではないため、中央値はおおよその値になります。保管スペースは固定されているため、どのような長さのシーケンスでも機能します。

ただし、最初の100個の値からヒストグラムを作成して、そのヒストグラムを継続的に使用することはできません。データの変更により、ヒストグラムが無効になる場合があります。そのため、その範囲とビンをその場で変更できる動的ヒストグラムが必要です。

N個のビンを持つ構造体を作成します。各スロット遷移のX値(合計N + 1値)とビンの母集団を保存します。

データをストリーミングします。最初のN + 1値を記録します。ストリームがこれより前に終了した場合は、すべての値が読み込まれ、正確な中央値を見つけて返すことができます。または、値を使用して最初のヒストグラムを定義します。値を並べ替え、それらをビンの定義として使用します。各ビンには1の母集団があります。複製(0の幅のビン)があってもかまいません。

新しい値をストリーミングします。それぞれについて、それが属するビンを見つけるためのバイナリ検索。一般的なケースでは、そのビンの数を増やして続行します。サンプルがヒストグラムのエッジ(最高または最低)を超えている場合は、エンドビンの範囲を拡張して、それを含めます。ストリームが完了したら、その両側に人口が等しいビンを見つけ、残りのビン幅を線形補間することにより、サンプル値の中央値を見つけます。

しかし、それだけでは十分ではありません。データがストリーミングされるときに、ヒストグラムをデータに適合させる必要があります。ビンがいっぱいになると、そのビンのサブ分布に関する情報が失われます。これは、ヒューリスティックに基づいて調整することで修正できます...最も簡単で最も堅牢なのは、ビンが特定のしきい値の母集団(10 * v/Nのようなもので、v =ストリームでこれまでに見られた値の数)に達した場合です。 Nはビンの数です)、あなたはその過剰なビンを分割します。ビンの中点に新しい値を追加し、元のビンの母集団の両側の半分を与えます。ただし、ビンが多すぎるため、ビンを削除する必要があります。そのための良いヒューリスティックは、人口と幅の積が最小のビンを見つけることです。それを削除し、その左または右の隣人(隣人のいずれかが幅と母集団の積が最小である方)とマージします。できた!ビンをマージまたは分割すると情報が失われますが、それは避けられないことに注意してください。固定ストレージしかありません。

このアルゴリズムはallタイプの入力ストリームを処理し、良好な結果を提供するという点で優れています。サンプルの順序を選択できる余裕がある場合は、分割とマージを最小限に抑えるため、ランダムサンプルが最適です。

このアルゴリズムでは、完全な分布推定値があるため、中央値だけでなく任意のパーセンタイルをクエリすることもできます。

私は多くの場所で自分のコードでこの方法を使用しています。主にログをデバッグするためです。ここで、記録しているいくつかの統計が不明な分布を持っています。このアルゴリズムを使用すると、事前に推測する必要はありません。

欠点は、ビンの幅が等しくないことです。つまり、サンプルごとにバイナリ検索を実行する必要があるため、ネットアルゴリズムはO(NlogN)になります。

7
SPWorley

デビッドの提案は、中央値を概算するための最も賢明なアプローチのようです。

同じ問題に対してmeanを実行すると、計算がはるかに簡単になります。

M = Mn-1 +((V -Mn-1)/ n)

どこM n個の値の平均、Mn-1 は以前の平均であり、V 新しい値です。

言い換えると、新しい平均は、既存の平均に新しい値と平均の差を値の数で割ったものです。

コードでは、これは次のようになります。

new_mean = prev_mean + ((value - prev_mean) / count)

ただし、浮動小数点の丸めエラーなど、言語固有のものを検討したい場合もあります。

3
GrahamS

リストをメモリに保存せずに行うことはできないと思います。あなたは明らかに近似することができます

  • データが対称的に分散していることがわかっている場合は平均
  • または、データの小さなサブセット(メモリに収まる)の適切な中央値を計算します-データがサンプル全体で同じ分布を持っていることがわかっている場合(たとえば、最初のアイテムが最後のものと同じ分布を持っていること)
3
Grzenio

線形検索によりN個のアイテムを含むリストの最小値と最大値を見つけ、HighValueおよびLowValueという名前を付けます。MedianIndex=(N + 1)/ 2

1次バイナリ検索:

LowValue <HighValueになるまで、次の4つの手順を繰り返します。

  1. およそMedianValueを取得する=(HighValue + LowValue)/ 2

  2. NumberOfItemsWhichAreLessThanorEqualToMedianValue = Kを取得します

  3. k = MedianIndexの場合、MedianValueを返します

  4. k> MedianIndexとは?次に、HighValue = MedianValueまたはLowValue = MedianValue

メモリを消費せずに速くなります

2次バイナリ検索:

LowIndex = 1 HighIndex = N

(LowIndex <HighIndex)になるまで、次の5つのステップを繰り返します。

  1. 近似DistrbutionPerUnit =(HighValue-LowValue)/(HighIndex-LowIndex)を取得します

  2. おおよそのMedianValue = LowValue +(MedianIndex-LowIndex)* DistributionPerUnitを取得します

  3. NumberOfItemsWhichAreLessThanorEqualToMedianValue = Kを取得します

  4. (K = MedianIndex)は? MedianValueを返す

  5. (K> MedianIndex)は?次に、HighIndex = KおよびHighValue = MedianValueまたはLowIndex = KおよびLowValue = MedianValue

メモリを消費せずに1次よりも速くなります

HighValue、LowValue、MedianValueをHighIndex、LowIndex、MedianIndexに放物線に適合させることも考えられ、メモリを消費することなく2次よりも高速なThirdOrderバイナリ検索を取得できます。

2
lakshmanaraj

通常、入力が特定の範囲内(たとえば、100万から100万)にある場合、カウントの配列を作成するのは簡単です。ここで「quantile」と「ibucket」のコードを読んでください http://code.google。 com/p/ea-utils/source/browse/trunk/clipper/sam-stats.cpp

この解決策は、出口で反転する関数を使用して、ある範囲内の整数に入力を強制することにより、近似として一般化することができます:IE:foo.Push((int)input/1000000)およびquantle(foo)* 1000000 。

入力が任意の倍精度数である場合、値が範囲外になると、ヒストグラムを自動スケーリングする必要があります(上記を参照)。

または、このペーパーで説明されている中央値-トリプレット法を使用できます。 http://web.cs.wpi.edu/~hofri/medsel.pdf

0
Erik Aronesty

私は反復分位数計算のアイデアを取り入れました。開始点とetaに適切な値を設定することが重要です。これらは平均値とシグマに由来する場合があります。だから私はこれをプログラムしました:

Function QuantileIterative(Var x : Array of Double; n : Integer; p, mean, sigma : Double) : Double;
Var eta, quantile,q1, dq : Double;
    i : Integer;
Begin
  quantile:= mean + 1.25*sigma*(p-0.5);
  q1:=quantile;
  eta:=0.2*sigma/xy(1+n,0.75); // should not be too large! sets accuracy
  For i:=1 to n Do 
     quantile := quantile + eta * (signum_smooth(x[i] - quantile,eta) + 2*p - 1);
  dq:=abs(q1-quantile);
  If dq>eta
     then Begin
          If dq<3*eta then eta:=eta/4;
          For i:=1 to n Do 
             quantile := quantile + eta * (signum_smooth(x[i] - quantile,eta) + 2*p - 1);
     end;
  QuantileIterative:=quantile
end;

2つの要素の中央値が平均であるため、平滑化された符号関数を使用しました。xy()はx ^ yです。それを改善するためのアイデアはありますか?もちろん、さらに先験的な知識があれば、配列の最小値と最大値、スキューなどを使用してコードを追加できます。ビッグデータの場合は配列を使用しないでしょうが、テストの方が簡単です。

0
user32038