web-dev-qa-db-ja.com

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

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

実際の質問はもっと複雑です-球体または代数空間で統計が何を意味するかは、「ラップアラウンド」します。加法群mod n。答えは一意ではない場合があります。 359度と1度の平均は0度または180になりますが、統計的には0の方が見栄えが良くなります。

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

138
Nick Fortescue

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

91
starblue

この質問は本で詳しく調べられます: "Statistics On Spheres"、Geoffrey S. Watson、University of Arkansas Lecture Notes in the Mathematical Sciences、1983 John Wiley&Sons、Inc.言及されている http:// catless.ncl.ac.uk/Risks/7.44.html#subj4 byブルース・カーシュ。

角度測定値セット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によって与えられた方法は計算的には同等ですが、彼の理由はより明確で、おそらくプログラム的にはより効率的であり、ゼロの場合でもうまく機能します。

主題はより詳細に調査されています ウィキペディア上 、および小数部のような他の用途で。

54
Nick Fortescue

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

Starblueは何かに取り組んでいると思います。各角度の(x、y)デカルト座標を計算し、それらの結果のベクトルを加算します。最終ベクトルのangularオフセットは、必要な結果になるはずです。

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

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

49
Alnitak

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

答え((a + b)mod 360)/ 2[〜#〜] wrong [〜#〜] 。角度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
21
darron

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

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

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

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

Angle comparisons

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

ここでは、2乗差を最小化するという考え方を適用して、一般的な算術平均式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 [i] -360)-xがあるaのサブセット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}です。

Curve

以下は、最適化を含む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ですが、算術平均はEdge値の影響を強く受けることはよく知られています。また、角度はベクトルではないことを覚えておく必要があります。これは、角度を扱う場合に思われるほど魅力的です。

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

また、これはベクトル解とは異なり、これが角度の真の平均であっても、それが必ずしも使用すべき解であるとは限らないことを強調したいと思います。対応する単位ベクトルの平均は実際の値になる可能性があります使用する必要があります。

14
Nimble

averageをより正確に定義する必要があります。 2つの角度の特定の場合について、2つの異なるシナリオを考えることができます。

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

ただし、2つ以上の角度の場合に2番目の選択肢をどのように一般化できるかわかりません。

7
David Hanak

すべての平均と同様に、答えはメトリックの選択に依存します。与えられたメトリック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、2002年を参照してください。
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, [email protected]
%     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
Rob Johnson

完全なソリューションは次のとおりです:(入力は度(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;
}
3
DuduArbel

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

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

理想的ではありません。壊れる可能性があります。この場合、デバイスは非常にゆっくりしか回転しないので、私はそれで逃げました。他の誰かが同様の制限の下で作業していることに気付いた場合に備えて、それを公開します。

3
thombles

複素数を使用してベクトルの方法を使用します。私の例は、組み込みの複素数を持つ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
tzot

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)と緑(aの平均)ティールポイントを生成します。

Original

角度は「ラップアラウンド」(例:355 + 10 = 5)ですが、標準の算術演算ではこの分岐点は無視されます。ただし、角度bが分岐点の反対側の場合、(b +g)/ 2は、最も近い平均値:ティールポイントを示します。

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

rotatedreturned

2
Brad Saund

完全な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として平均を返します。角度は度である必要があり、もちろん平均も度です。

2
adam10603

英語で:

  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
1
Jason

ここにアイデアがあります:重みを維持しながら、常に最も近い角度の平均を常に計算することにより、平均を繰り返し構築します。

別のアイデア:与えられた角度間の最大のギャップを見つけます。それを二等分する点を見つけてから、平均を計算するための基準ゼロとして円上の反対の点を選びます。

1

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

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

この仮定では、その半円上の任意の点を「原点」として選択し、この原点に対して与えられた角度のセットを測定します(これを「相対角度」と呼びます)。相対角度の絶対値は厳密に180度未満であることに注意してください。最後に、これらの相対角度の平均を取り、目的の平均角度を取得します(もちろん、Originに対して)。

1
Zach Scrivena

単一の「正しい答え」はありません。徹底的な分析については、K。V. Mardia and P. E. Jupp、 "Directional Statistics"(Wiley、1999)を読むことをお勧めします。

1
cffk

まあ、私はパーティーに非常に遅れていますが、決定的な答えを見つけることができなかったので、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;
1
neilireson

これは、移動平均を使用し、値を正規化するように注意する完全な算術ソリューションです。すべての角度が円の片側(互いに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
bgp2000

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

0
DynamicChart

Alnitak's answer に基づいて、複数の角度の平均を計算するための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));
}
0
Stevoisiak

プログラミング言語については、次のリンクで解決策と簡単な説明を見ることができます。 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
Gines Hidalgo

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
E.Rooijen

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
John Thoits

ここにいくつかの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;
}
0
Robert Sutton

@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
konsnos

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

0
ackb

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

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

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

ackbは、「平均角度phi_avgにはsum_i | phi_avg-phi_i | ^ 2が最小になるという特性が必要です...角度は平均化されますが、角度は平均化されません」

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

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

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

0
waterworld

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

どこの特別な場合

(sum(x_component)= 0.0 && sum(y_component)= 0.0)//例10.と190.度の2つの角度。

合計として0.0度を使用します

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

0
jeffD

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 
0
Martin006