一連の循環データの平均をどのように計算しますか?


147

一連の循環データの平均を計算したい。たとえば、コンパスの読みからいくつかのサンプルがあるかもしれません。もちろん問題はラップアラウンドの扱い方です。同じアルゴリズムが文字盤にも役立つかもしれません。

実際の質問はより複雑です-統計は球または「包み込む」代数空間で何を意味するか、たとえば加法群mod n。答えは一意ではない可能性があります。たとえば、359度と1度の平均は0度または180である可能性がありますが、統計的には0の方が良く見えます。

これは私にとって本当のプログラミング問題であり、私はそれを単なる数学の問題のように見えないようにしようとしています。


1
平均的な角度で、私はあなたが実際に平均方位を望んでいると思います。2つの線の間には角度があり、方位は1つの線の方向です。この場合、starblueは正しいです。
SmacL 2009年

@Nick Fortescue:より具​​体的になるように質問を更新できますか?角度または方位を意味しますか?
ミッチウィート

1
私は実際にはもう少し複雑なものを欲しがっていましたが(ベアリングに類似しています)、質問を簡単にするために単純化しようとしていましたが、いつものようにそれをより複雑にしました。catless.ncl.ac.uk/Risks/7.44.html#subj4必要な答えを見つけました。qnを再編集します。
Nick Fortescue、

答えは分母が0であるとき、それは、問題に遭遇する場合がありことを除いて、私は、提案していますどのような基本的にリスク
starblue

角度の意味に関する興味深い記事:twistedoakstudios.com/blog/
p=

回答:


99

角度から単位ベクトルを計算し、それらの平均の角度をとります。


8
ベクトルが互いに打ち消し合う場合、これは機能しません。この場合でも、正確な定義によっては、Averageが意味を持つ可能性があります。
David Hanak、2009年

21
@ David、180度外側の2つのベアリングの平均方向は定義されていません。これはstarblueの答えを間違ったものにするものではなく、多くの幾何学的な問題で発生する例外的なケースです。
SmacL 2009年

5
@smacl:角度が方向を表す場合、私は同意します。しかし、たとえば、複素数を考えて、平均を "cの引数は何であるか、たとえばc c == a b" として定義します。ここで、aとbの係数は1であり、平均は0と180です。 90です
デヴィッドHanak


5
@PierreBdR:0degの方向に2つのステップと90degの方向に1つのステップを実行すると、開始した場所に対して26.56 degの方向に移動します。この意味で、26.56は、30度よりも{0,0,90}度の平均方向としてはるかに意味があります。代数平均は、多くの可能な平均の1つにすぎません(en.wikipedia.org/wiki/Meanを参照))。これは、方向を平均化する目的にように)まったく無関係のようです。
Janus

60

この質問は、本で詳細に検討されています。http://catless.ncl。 ac.uk/Risks/7.44.html#subj4 by Bruce Karsh。

一連の角度測定値a [i] 0 <= iから平均角度Aを推定する良い方法

                   sum_i_from_1_to_N sin(a[i])
a = arctangent ---------------------------
                   sum_i_from_1_to_N cos(a[i])

starblueによって与えられた方法は計算的に同等ですが、彼の理由はより明確で、おそらくプログラム的に効率的であり、ゼロの場合にもうまく機能するので、彼に敬意を表します。

現在、この主題はWikipediaで詳細探究されており、分数部分のような他の用途も含まれています。


8
これも、私があなたと同時に投稿したアルゴリズムとほとんど同じです。そうでなければ、答えはである象限伝えることができないので、あなたは、しかし、平野ATANではなく、ATAN2使用する必要があります。
オリオン座ゼータ星

あなたはまだいくつかの不確定な答えで終わるかもしれません。0、180サンプルのように。したがって、エッジケースを確認する必要があります。また、通常はatan2関数を使用できますが、これはお客様のケースでより高速になる可能性があります。
ロキ、

50

問題がわかります。たとえば、45度の角度と315度の角度がある場合、「自然な」平均は180 'ですが、必要な値は実際には0'です。

スターブルーは何かに向かっていると思います。各角度の(x、y)デカルト座標を計算し、それらの結果のベクトルを加算するだけです。最終ベクトルの角度オフセットが必要な結果になるはずです。

x = y = 0
foreach angle {
    x += cos(angle)
    y += sin(angle)
}
average_angle = atan2(y, x)

今のところ、コンパスの向きは北から時計回りに進むのを無視していますが、「通常の」デカルト座標はX軸に沿ってゼロから始まり、反時計回りに進みます。数学は関係なく同じように機能するはずです。


13
あなたの数学ライブラリはおそらく角度にラジアンを使用しています。変換することを忘れないでください。
Martin Beckett、

2
多分それは夜には遅すぎるかもしれませんが、このロジックを使用すると、[320、330、340、350、10]の角度の342ではなく、341.8947の平均角度を取得します。誰かが私のタイプミスを見ますか?
Alex Robinson、

1
@AlexRobinsonこれはタイプミスではありません。最終角度は、各角度のステップのセットを個別に取得することによって得られる最終的な角度にすぎないためです。
アルニタク2014

1
@AlexRobinson、より具体的に:cos()sin()およびatan2()近似値(良いものですが、1から2 ulpはずれています)を与えるため、平均値が大きいほど、含めるエラーも多くなります。
Matthieu 2016年

23

2つの角度の特別な場合:

((a + b)mod 360)/ 2の答えは間違っています。角度350および2の場合、最も近い点は176ではなく356です。

単位ベクトルとトリガーソリューションは高すぎる可能性があります。

私が少しいじくり回して得たものは:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0、180-> 90(これに対する2つの答え:この方程式はaから時計回りの答えを取ります)
  • 180、0-> 270(上記を参照)
  • 180、1-> 90.5
  • 1、180-> 90.5
  • 20、350-> 5
  • 350、20-> 5(以下の例もすべて適切に反転します)
  • 10、20-> 15
  • 350、2-> 356
  • 359、0-> 359.5
  • 180、180-> 180

これは、さらにBAMSの使用によって最適化することができますstackoverflow.com/questions/1048945/...
ダロン

悪くない。最初の線は[-180、179]の範囲でbに対するaの相対角度を計算し、2番目の線はそこから中央の角度を計算します。わかりやすくするために、a-diff / 2の代わりにb + diff / 2を使用します。
starblue

1
何か不足していますか?私はDO 295取得
ダロン

ああ、わかった。Matlabのmod演算子は-10〜350をラップします。コードを変更します。これは単純な追加の360です
ダロン

この方法のもう1つの優れた機能は、2つの角度の加重平均を簡単に実装できることです。2行目で、diffに最初の角度の重みを掛け、分母の2を重みの合計で置き換えます。角度=(360 + b +(WEIGHT [a] * diff /(WEIGHT [a] + WEIGHT [b])))mod 360
oosterwal

14

ackbは、これらのベクトルベースのソリューションを真の角度の平均と見なすことはできません。これらは、対応する単位ベクトルの平均にすぎません。ただし、ackbが提案する解決策は、数学的に正しいようには見えません。

以下は、(angle [i]-avgAngle)^ 2(必要に応じて差が修正される)を最小化するという目標から数学的に導出されたソリューションであり、角度の真の算術平均になります。

まず、角度の違いが通常の数値の違いとどのケースが異なるかを正確に調べる必要があります。角度xとyを考えます。y> = x-180であり、y <= x + 180である場合、差(xy)を直接使用できます。それ以外の場合、最初の条件が満たされない場合、計算ではyではなく(y + 360)を使用する必要があります。対応して、2番目の条件が満たされない場合は、yの代わりに(y-360)を使用する必要があります。曲線の方程式は、これらの不等式がtrueからfalseに、またはその逆に変化するポイントでの変化のみを最小化しているため、[0,360)の全範囲をこれらのポイントで区切られた一連のセグメントに分離できます。次に、これらの各セグメントの最小値を見つけ、次に各セグメントの最小値の最小値、つまり平均を見つけるだけです。

次の画像は、角度差の計算で問題が発生する場所を示しています。xが灰色の領域にある場合、問題があります。

角度比較

変数を最小化するには、曲線に応じて、最小化したいものの導関数を取得し、転換点(導関数= 0)を見つけます。

ここでは、平方差を最小化するという考え方を適用して、一般的な算術平均式sum(a [i])/ nを導出します。曲線y = sum((a [i] -x)^ 2)は、次の方法で最小化できます。

y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2

dy\dx = -2*sum(a[i]) + 2*n*x

for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n

次に、調整した差のある曲線にそれを適用します。

b =正しい(角度の)差があるaのサブセットa [i] -xc =正しい(角度の)差があるaのサブセット(a [i] -360)-x cn = cdのサイズ= aのサブセット正しい(角度の)差(a [i] +360)-x dn = dのサイズ

y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
  + sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
  + sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
  + sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
  + sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
  - 2*x*(360*dn - 360*cn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*sum(x[i])
  - 2*x*360*(dn - cn)
  + n*x^2

dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)

for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n

これだけでは最小値を得るには十分ではありませんが、制限のないセットを持つ通常の値に対しては機能するため、結果は確実にセットの範囲内にあり、したがって有効です。(セグメントで定義された)範囲内の最小値が必要です。最小値がセグメントの下限よりも小さい場合、そのセグメントの最小値は下限になければなりません(2次曲線には1つの転換点しかないため)。最小値がセグメントの上限よりも大きい場合、セグメントの最小値は上界。各セグメントの最小値を取得したら、最小化する値の最小値を持つセグメントを見つけます(sum((b [i] -x)^ 2)+ sum(((c [i] -360 )-b)^ 2)+ sum((((d [i] +360)-c)^ 2))。

これは曲線のイメージで、x =(a [i] +180)%360のポイントでどのように変化するかを示しています。問題のデータセットは{65,92,230,320,250}です。

曲線

これは、いくつかの最適化を含む、Javaでのアルゴリズムの実装です。その複雑さはO(nlogn)です。比較ベースのソートを基数ソートなどの非比較ベースのソートで置き換えると、O(n)に削減できます。

static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            + 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            - 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}

static double[] averageAngles(double[] _angles)
{
    double sumAngles;
    double sumSqrAngles;

    double[] lowerAngles;
    double[] upperAngles;

    {
        List<Double> lowerAngles_ = new LinkedList<Double>();
        List<Double> upperAngles_ = new LinkedList<Double>();

        sumAngles = 0;
        sumSqrAngles = 0;
        for(double angle : _angles)
        {
            sumAngles += angle;
            sumSqrAngles += angle*angle;
            if(angle < 180)
                lowerAngles_.add(angle);
            else if(angle > 180)
                upperAngles_.add(angle);
        }


        Collections.sort(lowerAngles_);
        Collections.sort(upperAngles_,Collections.reverseOrder());


        lowerAngles = new double[lowerAngles_.size()];
        Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
        for(int i = 0; i < lowerAngles_.size(); i++)
            lowerAngles[i] = lowerAnglesIter.next();

        upperAngles = new double[upperAngles_.size()];
        Iterator<Double> upperAnglesIter = upperAngles_.iterator();
        for(int i = 0; i < upperAngles_.size(); i++)
            upperAngles[i] = upperAnglesIter.next();
    }

    List<Double> averageAngles = new LinkedList<Double>();
    averageAngles.add(180d);
    double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);

    double lowerBound = 180;
    double sumLC = 0;
    for(int i = 0; i < lowerAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle > lowerAngles[i]+180)
            testAverageAngle = lowerAngles[i];

        if(testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        lowerBound = lowerAngles[i];
        sumLC += lowerAngles[i];
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
        //minimum is inside segment range
        //we will test average 0 (360) later
        if(testAverageAngle < 360 && testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double upperBound = 180;
    double sumUC = 0;
    for(int i = 0; i < upperAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle < upperAngles[i]-180)
            testAverageAngle = upperAngles[i];

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        upperBound = upperAngles[i];
        sumUC += upperBound;
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
        //minimum is inside segment range
        //we test average 0 (360) now           
        if(testAverageAngle < 0)
            testAverageAngle = 0;

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double[] averageAngles_ = new double[averageAngles.size()];
    Iterator<Double> averageAnglesIter = averageAngles.iterator();
    for(int i = 0; i < averageAngles_.length; i++)
        averageAngles_[i] = averageAnglesIter.next();


    return averageAngles_;
}

一連の角度の算術平均は、平均がどうあるべきかという直感的な考えと一致しない場合があります。たとえば、セット{179,179,0,181,181}の算術平均は216(および144)です。すぐに考える答えはおそらく180ですが、算術平均がエッジ値に大きく影響されることはよく知られています。また、角度はベクトルではないことも覚えておく必要があります。角度を扱うときに見られるのと同じくらい魅力的です。

もちろん、このアルゴリズムは、時刻など、モジュラー演算(最小限の調整)に従うすべての数量にも適用されます。

また、これは角度の真の平均ですが、ベクトルソリューションとは異なり、必ずしも使用するソリューションであるとは限らないことを強調しておきます。対応する単位ベクトルの平均は実際の値である可能性があります。使用する必要があります。


Mitsutaメソッドは、実際には開始角度+開始角度からの回転の平均を与えます。したがって、同様の方法を取得するには、測定誤差を考慮に入れて、発生する回転を調べ、それらの誤差を推定する必要があります。回転の誤差を推定するには、回転の分布が必要になると思います。
Nimble

6

平均をより正確に定義する必要があります。2つの角度の特定のケースでは、2つの異なるシナリオを考えることができます。

  1. 「真の」平均、つまり(a + b)/ 2%360。
  2. 同じ半円に留まりながら他の2つの「中間」を指す角度。たとえば、355と5の場合、これは180ではなく0になります。これを行うには、2つの角度の差が180より大きいかどうかを確認する必要があります。か否か。その場合は、上の式を使用する前に、小さい方の角度を360だけ増やします。

ただし、2つ以上の角度の場合に2番目の選択肢を一般化する方法はわかりません。


質問は角度に関するものですが、それは平均方向として考えた方がよく、一般的なナビゲーションの問題です。
SmacL 2009年

良い点、デビッド。たとえば、180度の角度と540度の角度の平均はどうでしょうか。360ºですか、それとも180ºですか?
Baltimark 2009年

3
@Baltimark、それはあなたが何をしているかに依存すると思います。そのナビゲーションであれば、おそらく後者です。ファンシースノーボードジャンプの場合、おそらく前者です;)
SmacL 2009年

したがって、1と359の「真の」平均は(360/2)%360 = 180 ?? 私はそうは思いません。
にSenteで死ぬ

1
@Die in Sente:数値的に言えば、間違いなく。たとえば、角度が方向ではなく方向転換を表す場合、359と1の平均は確かに180です。これはすべて解釈の問題です。
David Hanak

4

すべての平均と同様に、答えはメトリックの選択に依存します。特定のメトリックMの場合、[1、N]のkに対する[-pi、pi]のいくつかの角度a_kの平均は、2乗距離の合計d ^ 2_M(a_M、a_k)を最小にする角度a_Mです。加重平均の場合、合計に加重w_k(sum_k w_k = 1など)を含めるだけです。あれは、

a_M = arg min_x sum_k w_k d ^ 2_M(x、a_k)

メトリックの2つの一般的な選択は、フロベニウスメトリックとリーマンメトリックです。フロベニウスメトリックの場合、循環統計における平均方位の通常の概念に対応する直接的な式が存在します。詳細については、「ローテーションのグループにおける平均と平均化」、Maher Moakher、SIAM Journal on Matrix Analysis and Applications、Volume 24、Issue 1を参照してください。
http://link.aip.org/link/?SJMAEL/24/1/1

計算を行うGNU Octave 3.2.4の関数を次に示します。

function ma=meanangleoct(a,w,hp,ntype)
%   ma=meanangleoct(a,w,hp,ntype) returns the average of angles a
%   given weights w and half-period hp using norm type ntype
%   Ref: "Means and Averaging in the Group of Rotations",
%   Maher Moakher, SIAM Journal on Matrix Analysis and Applications,
%   Volume 24, Issue 1, 2002.

if (nargin<1) | (nargin>4), help meanangleoct, return, end 
if isempty(a), error('no measurement angles'), end
la=length(a); sa=size(a); 
if prod(sa)~=la, error('a must be a vector'); end
if (nargin<4) || isempty(ntype), ntype='F'; end
if ~sum(ntype==['F' 'R']), error('ntype must be F or R'), end
if (nargin<3) || isempty(hp), hp=pi; end
if (nargin<2) || isempty(w), w=1/la+0*a; end
lw=length(w); sw=size(w); 
if prod(sw)~=lw, error('w must be a vector'); end
if lw~=la, error('length of w must equal length of a'), end
if sum(w)~=1, warning('resumming weights to unity'), w=w/sum(w); end

a=a(:);     % make column vector
w=w(:);     % make column vector
a=mod(a+hp,2*hp)-hp;    % reduce to central period
a=a/hp*pi;              % scale to half period pi
z=exp(i*a); % U(1) elements

% % NOTA BENE:
% % fminbnd can get hung up near the boundaries.
% % If that happens, shift the input angles a
% % forward by one half period, then shift the
% % resulting mean ma back by one half period.
% X=fminbnd(@meritfcn,-pi,pi,[],z,w,ntype);

% % seems to work better
x0=imag(log(sum(w.*z)));
X=fminbnd(@meritfcn,x0-pi,x0+pi,[],z,w,ntype);

% X=real(X);              % truncate some roundoff
X=mod(X+pi,2*pi)-pi;    % reduce to central period
ma=X*hp/pi;             % scale to half period hp

return
%%%%%%

function d2=meritfcn(x,z,w,ntype)
x=exp(i*x);
if ntype=='F'
    y=x-z;
else % ntype=='R'
    y=log(x'*z);
end
d2=y'*diag(w)*y;
return
%%%%%%

% %   test script
% % 
% % NOTA BENE: meanangleoct(a,[],[],'R') will equal mean(a) 
% % when all abs(a-b) < pi/2 for some value b
% % 
% na=3, a=sort(mod(randn(1,na)+1,2)-1)*pi;
% da=diff([a a(1)+2*pi]); [mda,ndx]=min(da);
% a=circshift(a,[0 2-ndx])    % so that diff(a(2:3)) is smallest
% A=exp(i*a), B1=expm(a(1)*[0 -1; 1 0]), 
% B2=expm(a(2)*[0 -1; 1 0]), B3=expm(a(3)*[0 -1; 1 0]),
% masimpl=[angle(mean(exp(i*a))) mean(a)]
% Bsum=B1+B2+B3; BmeanF=Bsum/sqrt(det(Bsum)); 
% % this expression for BmeanR should be correct for ordering of a above
% BmeanR=B1*(B1'*B2*(B2'*B3)^(1/2))^(2/3);
% mamtrx=real([[0 1]*logm(BmeanF)*[1 0]' [0 1]*logm(BmeanR)*[1 0]'])
% manorm=[meanangleoct(a,[],[],'F') meanangleoct(a,[],[],'R')]
% polar(a,1+0*a,'b*'), axis square, hold on
% polar(manorm(1),1,'rs'), polar(manorm(2),1,'gd'), hold off

%     Meanangleoct Version 1.0
%     Copyright (C) 2011 Alphawave Research, robjohnson@alphawaveresearch.com
%     Released under GNU GPLv3 -- see file COPYING for more info.
%
%     Meanangle is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or (at
%     your option) any later version.
%
%     Meanangle is distributed in the hope that it will be useful, but
%     WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%     General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see `http://www.gnu.org/licenses/'.

4

浮動小数点や三角法の機能を持たないマイクロコントローラーで使用した方法を共有したいと思います。変動を滑らかにするために、ベアリングの生の読みを10個平均化する必要がありました。

  1. 最初の方位が270度から360度または0度から90度の範囲であるかどうかを確認します(2つの象限の北)
  2. ある場合は、これと以降のすべての読み取り値を180度回転させ、すべての値を0 <=方位<360の範囲に保ちます。それ以外の場合は、読み取った値をそのまま使用します。
  3. 10回の読み取りが行われたら、ラップアラウンドがないと仮定して数値平均を計算します
  4. 180度の回転が有効であった場合は、計算された平均を180度回転させて、「真の」方位に戻します。

それは理想的ではありません。壊れる可能性があります。この場合、デバイスの回転が非常に遅いため、この問題を回避しました。他の誰かが同様の制限の下で働いていることがわかった場合に備えて、私はそれを公開します。


3

英語で:

  1. すべての角度を180度ずらした2番目のデータセットを作成します。
  2. 両方のデータセットの分散をとります。
  3. 分散が最小のデータセットの平均を取ります。
  4. この平均がシフトされたセットからのものである場合、回答を再度180シフトします。

Pythonでは:

角度の#numpy NX1配列

if np.var(A) < np.var((A-180)%360):
    average = np.average(A)

else:
    average = (np.average((A-180)%360)+180)%360

これは、トリガー関数なしで最終結果を達成するための優れた方法であり、シンプルで簡単に実装できます。
Ian Mercer

これはあらゆる範囲の循環データで機能します。ちょうど円形範囲の半分だけシフトします。すばらしい答えです!
キャプテンファンタスティック

3

これが完全なソリューションです:(入力は方位(0-360)での方位の配列です)

public static int getAvarageBearing(int[] arr)
{
    double sunSin = 0;
    double sunCos = 0;
    int counter = 0;

    for (double bearing : arr)
    {
        bearing *= Math.PI/180;

        sunSin += Math.sin(bearing);
        sunCos += Math.cos(bearing);
        counter++; 
    }

    int avBearing = INVALID_ANGLE_VALUE;
    if (counter > 0)
    {
        double bearingInRad = Math.atan2(sunSin/counter, sunCos/counter);
        avBearing = (int) (bearingInRad*180f/Math.PI);
        if (avBearing<0)
            avBearing += 360;
    }

    return avBearing;
}

この問題はしばらくの間私を困惑させました、あなたの解決策は機能します(Arduinoを使用しているので、コードにいくつかの変更を加えますが、ほとんど何もしません)、私はコンパスの読み取りを示し、50msごとに読み取りを行い、16 x読み取り配列に保存しています。上記の関数で、0-360ラップアラウンドの問題が解決しました!おかげで:)
Andology 2015

3

Pythonでは、角度は[-180、180)

def add_angles(a, b):
  return (a + b + 180) % 360 - 180

def average_angles(a, b):
  return add_angles(a, add_angles(-a, b)/2)

詳細:

2つの角度の平均は、180°離れた2つの平均がありますが、より近い平均が必要な場合があります。

視覚的に、青(平均B)及び緑色()収率TEAL点:

元の

角度は「ラップアラウンド」します(たとえば、355 + 10 = 5)。ただし、標準の計算ではこの分岐点は無視されます。ただし、角度bが分岐点と反対の場合、(b + g)/ 2が最も近い平均であるティールポイントになります。

任意の2つの角度について、問題を回転させて、角度の1つが分岐点と反対になるようにし、標準的な平均化を実行してから、元に戻します。

回転戻ってきた


2

私は複素数を使ってベクトルの道を行くでしょう。私の例は、組み込みの複素数を持つPythonです。

import cmath # complex math

def average_angle(list_of_angles):

    # make a new list of vectors
    vectors= [cmath.rect(1, angle) # length 1 for each vector
        for angle in list_of_angles]

    vector_sum= sum(vectors)

    # no need to average, we don't care for the modulus
    return cmath.phase(vector_sum)

Pythonはベクターの一時的な新しいリストを作成する必要がないことに注意してください。上記のすべてを1つのステップで実行できます。他の言語にも適用できる擬似コードを概算するために、私はこの方法を選びました。


2

以下は完全なC ++ソリューションです。

#include <vector>
#include <cmath>

double dAngleAvg(const vector<double>& angles) {
    auto avgSin = double{ 0.0 };
    auto avgCos = double{ 0.0 };
    static const auto conv      = double{ 0.01745329251994 }; // PI / 180
    static const auto i_conv    = double{ 57.2957795130823 }; // 180 / PI
    for (const auto& theta : angles) {
        avgSin += sin(theta*conv);
        avgCos += cos(theta*conv);
    }
    avgSin /= (double)angles.size();
    avgCos /= (double)angles.size();
    auto ret = double{ 90.0 - atan2(avgCos, avgSin) * i_conv };
    if (ret<0.0) ret += 360.0;
    return fmod(ret, 360.0);
}

角度をdoubleのベクトルの形式で取り、単純にdoubleとして平均を返します。角度は度単位でなければなりません。もちろん、平均も度単位です。


avgCosはxコンポーネントavgSinの平均で、はyコンポーネントの平均です。逆正接関数のパラメーターはatan2( y, x )です。だから、あなたのコードではなくてはならない: atan2( avgSin, avgCos ) ??
マイクフィンチ

私はこのアルゴリズムをどこかから入手しましたが、自分で考え出したわけではないので、正しい方法だと思います。さらに、正しい結果も得られます。
adam10603 2017

2

アルニタックの答えに基づいて、私は複数の角度の平均を計算するためのJavaメソッドを書きました:

角度がラジアンの場合:

public static double averageAngleRadians(double... angles) {
    double x = 0;
    double y = 0;
    for (double a : angles) {
        x += Math.cos(a);
        y += Math.sin(a);
    }

    return Math.atan2(y, x);
}

角度が度単位の場合:

public static double averageAngleDegrees(double... angles) {
    double x = 0;
    double y = 0;
    for (double a : angles) {
        x += Math.cos(Math.toRadians(a));
        y += Math.sin(Math.toRadians(a));
    }

    return Math.toDegrees(Math.atan2(y, x));
}

1

アイデアは次のとおりです。重みを維持しながら、常に最も近い角度の平均を常に計算することにより、平均を繰り返し作成します。

別のアイデア:与えられた角度間の最大のギャップを見つける。それを2等分する点を見つけ、円の反対側の点を参照ゼロとして選択して、平均を計算します。


私の回答はお勧めしませんが、starblueの高いランクの回答です。そこにある重要な観察は、コンパスの中心を0,0点と考えることです。
ワッフル付きのジョン、

1

これらの角度を円周上の点で表現しましょう。

これらすべての点が円の同じ半分にあると仮定できますか?(それ以外の場合、「平均角度」を定義する明確な方法はありません。たとえば、0度と180度のように、直径上の2つの点を考えてください。平均は90度または270度ですか?3つ以上あるとどうなりますか?ポイントを均等に広げますか?)

この仮定を使用して、その半円上の任意の点を「原点」として選択し、この原点を基準にして指定された角度のセットを測定します(これを「相対角度」と呼びます)。相対角度の絶対値は、厳密には180度未満です。最後に、これらの相対角度の平均をとって、目的の平均角度を取得します(もちろん、起点に対して)。


1

単一の「正解」はありません。徹底的な分析については、本「KV Mardia and PE Jupp、Directional Statistics」(Wiley、1999)を読むことをお勧めします。


1

(推定理論または統計的推論から私の視点を共有したいだけです)

Nimbleの試用は一連の角度のMMSE ^推定値を取得することですが、「平均化された」方向を見つけるための選択肢の1つです。MMAE ^の推定値、または「平均化された」方向である他の推定値を見つけることもできます。これは、方向の誤差を定量化するメトリックに依存します。より一般的には、推定理論では、コスト関数の定義。

^ MMSE / MMAEは、最小二乗平均/絶対誤差に対応します。

ackbは、「平均角度phi_avgにはsum_i | phi_avg-phi_i | ^ 2が最小になるという特性があるはずです...角度ではなく何かを平均化します」

----平均二乗の意味でエラーを定量化しますが、これは最も一般的な方法の1つですが、唯一の方法ではありません。ここでほとんどの人が好む答え(つまり、単位ベクトルの合計と結果の角度を取得する)は、実際には合理的な解決策の1つです。ベクトルの方向がフォンミーゼス分布としてモデル化されている場合、必要な「平均化された」方向として機能するのはML推定量です(証明できます)。この分布は派手ではなく、単に2Dグアシアンから定期的にサンプリングされた分布です。Eqnを参照してください。(2.179)Bishopの本「パターン認識と機械学習」。繰り返しになりますが、「平均的な」方向を表すのはこれが唯一の最良の方法ではありませんが、理論的に十分な正当化と単純な実装の両方を備えた非常に合理的な方法です。

ニンブル氏は、「これらのベクトルベースのソリューションは、角度の真の平均とは見なせず、単位ベクトルの平均の平均にすぎないことは正しい」と述べた。

- - 本当じゃない。「対応する単位ベクトル」は、ベクトルの方向の情報を明らかにします。角度は、ベクトルの長さを考慮しない量であり、単位ベクトルは、長さが1であるという追加情報を持つものです。「単位」ベクトルを長さ2として定義できますが、実際には重要ではありません。


1

これは、移動平均を使用し、値を正規化するように注意する完全な算術ソリューションです。すべての角度が円の片側にある場合(互いに180°以内)、高速で正しい答えを提供します。

これは、値を範囲(0、180)にシフトするオフセットを追加し、平均を計算してからオフセットを差し引くことと数学的に同等です。

コメントは、特定の値がいつでも取り得る範囲を説明しています

// angles have to be in the range [0, 360) and within 180° of each other.
// n >= 1
// returns the circular average of the angles int the range [0, 360).
double meanAngle(double* angles, int n)
{
    double average = angles[0];
    for (int i = 1; i<n; i++)
    {
        // average: (0, 360)
        double diff = angles[i]-average;
        // diff: (-540, 540)

        if (diff < -180)
            diff += 360;
        else if (diff >= 180)
            diff -= 360;
        // diff: (-180, 180)

        average += diff/(i+1);
        // average: (-180, 540)

        if (average < 0)
            average += 360;
        else if (average >= 360)
            average -= 360;
        // average: (0, 360)
    }
    return average;
}

1

さて、私はパーティーに非常に遅れていますが、決定的な答えを本当に見つけることができなかったので、2セント相当を追加すると思いました。最終的に、次のJavaバージョンのMitsutaメソッドを実装しました。これは、シンプルで堅牢なソリューションを提供することを期待しています。特に、標準偏差は測定値の分散を提供し、sd == 90の場合、入力角度が不明確な平均をもたらすことを示します。

編集:実際に、他の回答で行われているすべての会話と三角法を考えると、実際には私の元の実装はさらに単純化できることに気付きました。

/**
 * The Mitsuta method
 *
 * @param angles Angles from 0 - 360
 * @return double array containing
 * 0 - mean
 * 1 - sd: a measure of angular dispersion, in the range [0..360], similar to standard deviation.
 * Note if sd == 90 then the mean can also be its inverse, i.e. 360 == 0, 300 == 60.
 */
public static double[] getAngleStatsMitsuta(double... angles) {
    double sum = 0;
    double sumsq = 0;
    for (double angle : angles) {
        if (angle >= 180) {
            angle -= 360;
        }
        sum += angle;
        sumsq += angle * angle;
    }

    double mean = sum / angles.length;
    return new double[]{mean <= 0 ? 360 + mean: mean, Math.sqrt(sumsq / angles.length - (mean * mean))};
}

...そして、あなた(Java)のすべてのオタクにとって、上記のアプローチを使用して1行の平均角度を取得できます。

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;

みつだメソッドから何かを逃したと思います。LIORコーガンので掲示答えを見てみてくださいstackoverflow.com/a/1828222/9265852を
kykzk46

0

Alnitakには適切なソリューションがあります。Nick Fortescueのソリューションは機能的には同じです。

場所の特別な場合

(sum(x_component)= 0.0 && sum(y_component)= 0.0)//たとえば、10度と190度の2つの角度ea。

合計として0.0度を使用

atan2(0。、0.)は未定義であり、エラーを生成するため、計算上、このケースをテストする必要があります。


glibcでは 'atan2'が(0、0)に対して定義されています
Alnitak

0

平均角度phi_avgには、sum_i | phi_avg-phi_i | ^ 2が最小になるという特性が必要です。この場合、差は[-Pi、Pi)でなければなりません(逆に移動すると短い可能性があるためです)。これは、すべての入力値を[0、2Pi)に正規化し、移動平均phi_runを維持し、正規化を選択することで簡単に実現できます| phi_i-phi_run | [-Pi、Pi)に(2Piを加算または減算して)。上記のほとんどの提案 は、その最小限のプロパティを持たない別のことを実行します。つまり、平均ではなく、角度ではありません。


0

@David_Hanakからの回答を利用して問題を解決しました。彼が述べるように:

同じ半円に留まりながら他の2つの「中間」を指す角度。たとえば、355と5の場合、これは180ではなく0になります。これを行うには、2つの角度の差が180より大きいかどうかを確認する必要があります。か否か。その場合は、上記の式を使用する前に、小さい方の角度を360だけ増やします。

だから私がしたことはすべての角度の平均を計算することでした。次に、これよりも小さいすべての角度を360度増やします。次に、それらをすべて追加し、長さで割って平均を再計算します。

        float angleY = 0f;
        int count = eulerAngles.Count;

        for (byte i = 0; i < count; i++)
            angleY += eulerAngles[i].y;

        float averageAngle = angleY / count;

        angleY = 0f;
        for (byte i = 0; i < count; i++)
        {
            float angle = eulerAngles[i].y;
            if (angle < averageAngle)
                angle += 360f;
            angleY += angle;
        }

        angleY = angleY / count;

完璧に動作します。


0

Python関数:

from math import sin,cos,atan2,pi
import numpy as np
def meanangle(angles,weights=0,setting='degrees'):
    '''computes the mean angle'''
    if weights==0:
         weights=np.ones(len(angles))
    sumsin=0
    sumcos=0
    if setting=='degrees':
        angles=np.array(angles)*pi/180
    for i in range(len(angles)):
        sumsin+=weights[i]/sum(weights)*sin(angles[i])
        sumcos+=weights[i]/sum(weights)*cos(angles[i])
    average=atan2(sumsin,sumcos)
    if setting=='degrees':
        average=average*180/pi
    return average

0

この関数はMatlabで使用できます。

function retVal=DegreeAngleMean(x) 

len=length(x);

sum1=0; 
sum2=0; 

count1=0;
count2=0; 

for i=1:len 
   if x(i)<180 
       sum1=sum1+x(i); 
       count1=count1+1; 
   else 
       sum2=sum2+x(i); 
       count2=count2+1; 
   end 
end 

if (count1>0) 
     k1=sum1/count1; 
end 

if (count2>0) 
     k2=sum2/count2; 
end 

if count1>0 && count2>0 
   if(k2-k1 >= 180) 
       retVal = ((sum1+sum2)-count2*360)/len; 
   else 
       retVal = (sum1+sum2)/len; 
   end 
elseif count1>0 
    retVal = k1; 
else 
    retVal = k2; 
end 

アルゴリズムは機能しているように見えますが、実際には、現実世界では惨めに失敗する可能性があります。与えられた角度の反対方向にある角度値を与える。
tothphu 2016年

0

すべてのプログラミング言語について、次のリンクで解決策と簡単な説明を見ることができます。 https //rosettacode.org/wiki/Averages/Mean_angle

たとえば、C ++ソリューション

#include<math.h>
#include<stdio.h>

double
meanAngle (double *angles, int size)
{
  double y_part = 0, x_part = 0;
  int i;

  for (i = 0; i < size; i++)
    {
      x_part += cos (angles[i] * M_PI / 180);
      y_part += sin (angles[i] * M_PI / 180);
    }

  return atan2 (y_part / size, x_part / size) * 180 / M_PI;
}

int
main ()
{
  double angleSet1[] = { 350, 10 };
  double angleSet2[] = { 90, 180, 270, 360};
  double angleSet3[] = { 10, 20, 30};

  printf ("\nMean Angle for 1st set : %lf degrees", meanAngle (angleSet1, 2));
  printf ("\nMean Angle for 2nd set : %lf degrees", meanAngle (angleSet2, 4));
  printf ("\nMean Angle for 3rd set : %lf degrees\n", meanAngle (angleSet3, 3));
  return 0;
}

出力:

Mean Angle for 1st set : -0.000000 degrees
Mean Angle for 2nd set : -90.000000 degrees
Mean Angle for 3rd set : 20.000000 degrees

またはMatlabソリューション

function u = mean_angle(phi)
    u = angle(mean(exp(i*pi*phi/180)))*180/pi;
end

 mean_angle([350, 10])
ans = -2.7452e-14
 mean_angle([90, 180, 270, 360])
ans = -90
 mean_angle([10, 20, 30])
ans =  20.000

0

starblueの答えは平均単位ベクトルの角度を示しますが、0〜2 * pi(または0°〜 360°)。たとえば、0°と180°の平均は、90°と270°のどちらでもかまいません。

算術平均には、入力値までの距離の二乗の最小合計を持つ単一の値であるという特性があります。2つの単位ベクトル間の単位円に沿った距離は、それらの内積の逆余弦として簡単に計算できます。ベクトルと各入力単位ベクトルの内積の逆コサインの二乗の合計を最小化することによって単位ベクトルを選択すると、同等の平均が得られます。繰り返しになりますが、例外的なケースでは2つ以上の最小値が存在する可能性があることに注意してください。

単位球に沿った距離は、単位円に沿った距離(2つの単位ベクトルの内積の逆余弦)とまったく同じ方法で計算できるため、この概念は任意の次元に拡張できます。

円については、さまざまな方法でこの平均を解くことができますが、次のO(n ^ 2)アルゴリズムを提案します(角度はラジアンであり、単位ベクトルの計算は避けます)。

var bestAverage = -1
double minimumSquareDistance
for each a1 in input
    var sumA = 0;
    for each a2 in input
        var a = (a2 - a1) mod (2*pi) + a1
        sumA += a
    end for
    var averageHere = sumA / input.count
    var sumSqDistHere = 0
    for each a2 in input
        var dist = (a2 - averageHere + pi) mod (2*pi) - pi // keep within range of -pi to pi
        sumSqDistHere += dist * dist
    end for
    if (bestAverage < 0 OR sumSqDistHere < minimumSquareDistance) // for exceptional cases, sumSqDistHere may be equal to minimumSquareDistance at least once. In these cases we will only find one of the averages
        minimumSquareDistance = sumSqDistHere
        bestAverage = averageHere
    end if
end for
return bestAverage

すべての角度が互いに180°以内の場合、より単純なO(n)+ O(sort)アルゴリズムを使用できます(これもラジアンを使用し、単位ベクトルの使用を回避します)。

sort(input)
var largestGapEnd = input[0]
var largestGapSize = (input[0] - input[input.count-1]) mod (2*pi)
for (int i = 1; i < input.count; ++i)
    var gapSize = (input[i] - input[i - 1]) mod (2*pi)
    if (largestGapEnd < 0 OR gapSize > largestGapSize)
        largestGapSize = gapSize
        largestGapEnd = input[i]
    end if
end for
double sum = 0
for each angle in input
    var a2 = (angle - largestGapEnd) mod (2*pi) + largestGapEnd
    sum += a2
end for
return sum / input.count

度を使用するには、単にpiを180に置き換えます。より多くの次元を使用する場合は、平均を解くために反復法を使用する必要がある可能性があります。


0

問題は非常に簡単です。1.すべての角度が-180〜180度であることを確認します。2. aすべての負でない角度を追加し、それらの平均を取り、その数をカウントします2. b。すべての負の角度を追加し、それらの平均を取り、その数をCOUNTします。3. pos_average-neg_averageの差を取得します。差が180より大きい場合、差を360-差に変更します。それ以外の場合は、違いの符号を変更するだけです。差は常に負ではないことに注意してください。Average_Angleは、pos_averageと差に「重み」を掛けた値に等しく、負の数を負の数と正の数の合計で割った値


0

ここに、角度を平均化するJavaコードがあります。それはかなり堅牢です。

public static double getAverageAngle(List<Double> angles)
{
    // r = right (0 to 180 degrees)

    // l = left (180 to 360 degrees)

    double rTotal = 0;
    double lTotal = 0;
    double rCtr = 0;
    double lCtr = 0;

    for (Double angle : angles)
    {
        double norm = normalize(angle);
        if (norm >= 180)
        {
            lTotal += norm;
            lCtr++;
        } else
        {
            rTotal += norm;
            rCtr++;
        }
    }

    double rAvg = rTotal / Math.max(rCtr, 1.0);
    double lAvg = lTotal / Math.max(lCtr, 1.0);

    if (rAvg > lAvg + 180)
    {
        lAvg += 360;
    }
    if (lAvg > rAvg + 180)
    {
        rAvg += 360;
    }

    double rPortion = rAvg * (rCtr / (rCtr + lCtr));
    double lPortion = lAvg * (lCtr / (lCtr + rCtr));
    return normalize(rPortion + lPortion);
}

public static double normalize(double angle)
{
    double result = angle;
    if (angle >= 360)
    {
        result = angle % 360;
    }
    if (angle < 0)
    {
        result = 360 + (angle % 360);
    }
    return result;
}

-3

私は、@ Starblueとは異なる方法を使用して、上記の角度のいくつかに対して「正しい」答えを出します。例えば:

  • angle_avg([350,10])= 0
  • angle_avg([-90,90,40])= 13.333
  • angle_avg([350,2])= 356

連続する角度の差の合計を使用します。コード(Matlab):

function [avg] = angle_avg(angles)
last = angles(1);
sum = angles(1);
for i=2:length(angles)
    diff = mod(angles(i)-angles(i-1)+ 180,360)-180
    last = last + diff;
    sum = sum + last;
end
avg = mod(sum/length(angles), 360);
end

1
あなたのコードは、のために異なる答えを返す[-90,90,40][90,-90,40]、非可換平均は非常に便利なものではないと思います。
ムシフィル2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.