wake-up-neo.net

Wie berechnen Sie den Durchschnitt einer Reihe von Kreisdaten?

Ich möchte den Durchschnitt eines Satzes kreisförmiger Daten berechnen. Ich könnte zum Beispiel mehrere Beispiele aus dem Lesen eines Kompasses haben. Das Problem ist natürlich, wie mit dem Wraparound umzugehen ist. Derselbe Algorithmus kann für eine Uhr hilfreich sein.

Die eigentliche Frage ist komplizierter - was bedeuten Statistiken auf einer Kugel oder in einem algebraischen Raum, der "umschlingt", z. die Zusatzgruppe mod n. Die Antwort kann nicht eindeutig sein, z. Der Durchschnitt von 359 Grad und 1 Grad könnte 0 Grad oder 180 sein, statistisch gesehen sieht 0 jedoch besser aus.

Dies ist ein echtes Programmierproblem für mich und ich versuche, es nicht wie ein mathematisches Problem aussehen zu lassen.

130
Nick Fortescue

Berechnen Sie Einheitsvektoren aus den Winkeln und nehmen Sie den Winkel ihres Durchschnitts.

84
starblue

Diese Frage wird ausführlich in dem Buch untersucht: "Statistics On Spheres", Geoffrey S. Watson, University of Arkansas Lecture Hinweise in den Mathematical Sciences, 1983 John Wiley & Sons, Inc., wie unter http://catless.ncl.ac.uk/Risks/7.44.html#subj4 von Bruce Karsh.

Ein guter Weg, um einen Durchschnittswinkel A aus einer Reihe von Winkelmessungen abzuschätzen. __ a [i] 0 <= i

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

Die Methode von starblue ist rechnerisch äquivalent, aber seine Gründe sind klarer und wahrscheinlich programmatisch effizienter und funktionieren auch im Nullfall gut, so ein Lob an ihn.

Das Thema wird jetzt genauer auf in Wikipedia und zu anderen Verwendungen wie Bruchteilen untersucht.

52
Nick Fortescue

Ich sehe das Problem - wenn Sie beispielsweise einen Winkel von 45 'und einen Winkel von 315' haben, wäre der "natürliche" Durchschnitt 180 ', der gewünschte Wert ist jedoch 0'.

Ich glaube, Starblue hat etwas vor. Berechnen Sie einfach die kartesischen (x, y) -Koordinaten für jeden Winkel und addieren Sie die resultierenden Vektoren zusammen. Der Winkelversatz des endgültigen Vektors sollte Ihr gewünschtes Ergebnis sein.

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

Ich ignoriere im Moment, dass ein Kompasskurs im Norden beginnt und im Uhrzeigersinn verläuft, während "normale" kartesische Koordinaten entlang der X-Achse mit Null beginnen und dann gegen den Uhrzeigersinn gehen. Die Mathematik sollte trotzdem auf die gleiche Weise funktionieren.

47
Alnitak

FÜR DEN BESONDEREN FALL VON ZWEI WINKELN:

Die Antwort ((a + b) mod 360)/2 istFALSCH. Für die Winkel 350 und 2 ist der nächstgelegene Punkt 356 und nicht 176.

Die Einheitsvektor- und Auslöselösungen sind möglicherweise zu teuer.

Was ich von einem kleinen Basteln habe, ist:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90 (zwei Antworten dafür: Diese Gleichung nimmt die Antwort im Uhrzeigersinn von a an)
  • 180, 0 -> 270 (siehe oben)
  • 180, 1 -> 90,5
  • 1, 180 → 90,5
  • 20, 350 → 5
  • 350, 20 -> 5 (alle folgenden Beispiele werden auch richtig umgekehrt)
  • 10, 20 -> 15
  • 350, 2 → 356
  • 359, 0 → 359,5
  • 180, 180 -> 180
17
darron

ackb hat recht, dass diese vektorbasierten Lösungen nicht als echte Mittelwerte der Winkel betrachtet werden können, sondern nur einen Durchschnitt der Gegenstücke des Einheitsvektors. Die von ackb vorgeschlagene Lösung scheint jedoch nicht mathematisch einwandfrei zu sein.

Das Folgende ist eine Lösung, die mathematisch aus dem Ziel der Minimierung (Winkel [i] - avgAngle) ^ 2 (wo die Differenz gegebenenfalls korrigiert wird) abgeleitet wird, was es zu einem wahren arithmetischen Mittel der Winkel macht.

Zuerst müssen wir uns genau anschauen, in welchen Fällen sich die Differenz zwischen den Winkeln von der Differenz zwischen ihren normalen Gegenstücken unterscheidet. Betrachten Sie die Winkel x und y, wenn y> = x - 180 und y <= x + 180, dann können wir die Differenz (x-y) direkt verwenden. Andernfalls müssen wir (y + 360) anstelle von y verwenden, wenn die erste Bedingung nicht erfüllt ist. Wenn die zweite Bedingung nicht erfüllt ist, müssen wir (y-360) anstelle von y verwenden. Da die Gleichung der Kurve, die wir minimieren, nur Änderungen an den Punkten minimiert, an denen sich diese Ungleichungen von wahr nach falsch oder umgekehrt ändern, können wir den gesamten [0,360) -Bereich in eine durch diese Punkte getrennte Menge von Segmenten aufteilen. Dann müssen wir nur das Minimum jedes dieser Segmente ermitteln und dann das Minimum des Minimums jedes Segments, das ist der Durchschnitt.

Hier ist ein Bild, das zeigt, wo die Probleme bei der Berechnung von Winkeldifferenzen auftreten. Wenn x im grauen Bereich liegt, liegt ein Problem vor.

 Angle comparisons

Um eine Variable zu minimieren, können wir abhängig von der Kurve die Ableitung dessen nehmen, was wir minimieren möchten, und dann den Wendepunkt finden (wo die Ableitung = 0 ist).

Hier wenden wir die Idee der Minimierung der quadrierten Differenz an, um die Formel des allgemeinen arithmetischen Mittels abzuleiten: sum (a [i])/n. Die Kurve y = Summe ((a [i] -x) ^ 2) kann auf diese Weise minimiert werden:

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

Wenden Sie es jetzt mit unseren angepassten Unterschieden auf Kurven an:

b = Teilmenge von a, wobei die korrekte (Winkeldifferenz) a [i] -x c = Teilmenge von a ist, wobei die korrekte (Winkeldifferenz) (a [i] -360) -x cn = Größe von ist c d = Teilmenge von a wobei die richtige (Winkeldifferenz) (a [i] +360) -x dn = Größe von d ist

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

Dies allein reicht nicht aus, um das Minimum zu erhalten, während es für normale Werte funktioniert, die eine unbegrenzte Menge haben. Das Ergebnis liegt also definitiv im eingestellten Bereich und ist daher gültig. Wir brauchen das Minimum innerhalb eines Bereichs (definiert durch das Segment). Wenn das Minimum geringer als die untere Grenze unseres Segments ist, muss das Minimum dieses Segments an der unteren Grenze liegen (da quadratische Kurven nur einen Wendepunkt haben), und wenn das Minimum größer ist als die obere Grenze unseres Segments, dann liegt das Minimum des Segments bei obere Grenze. Nachdem wir für jedes Segment das Minimum haben, suchen wir einfach denjenigen mit dem niedrigsten Wert für das, was wir minimieren (sum ((b [i] -x) ^ 2) + sum (((c [i] -360) ) -b) ^ 2) + Summe (((d [i] +360) -c) ^ 2)).

Hier ist ein Bild der Kurve, das zeigt, wie es sich an den Punkten ändert, an denen x = (a [i] +180)% 360 ist. Der fragliche Datensatz ist {65,92,230,320,250}.

 Curve

Hier ist eine Implementierung des Algorithmus in Java, einschließlich einiger Optimierungen, deren Komplexität O (nlogn) ist. Sie kann auf O(n) reduziert werden, wenn Sie die vergleichsbasierte Sortierung durch eine nicht vergleichbasierte Sortierung wie z. B. Radix-Sortierung ersetzen.

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_;
}

Das arithmetische Mittel einer Reihe von Winkeln stimmt möglicherweise nicht mit Ihrer intuitiven Vorstellung davon überein, wie der Durchschnitt aussehen soll. Zum Beispiel ist das arithmetische Mittel der Menge {179,179,0,181,181} 216 (und 144). Die Antwort, an die Sie sofort denken, ist wahrscheinlich 180, es ist jedoch bekannt, dass das arithmetische Mittel stark von Edge-Werten beeinflusst wird. Sie sollten auch bedenken, dass Winkel keine Vektoren sind, so ansprechend, wie es manchmal bei Winkeln aussieht.

Dieser Algorithmus gilt natürlich auch für alle Größen, die der modularen Arithmetik (mit minimaler Anpassung), wie zum Beispiel der Tageszeit, gehorchen.

Ich möchte auch betonen, dass, obwohl dies ein echter Durchschnitt der Winkel ist, im Gegensatz zu den Vektorlösungen nicht unbedingt die Lösung, die Sie verwenden sollten, der Durchschnitt der entsprechenden Einheitsvektoren der tatsächliche Wert sein kann sollte verwenden.

14
Nimble

Sie müssen Durchschnitt genauer definieren. Für den speziellen Fall von zwei Winkeln fallen mir zwei verschiedene Szenarien ein:

  1. Der "wahre" Durchschnitt, d. H. (A + b)/2% 360.
  2. Der Winkel, der zwischen den beiden anderen "zeigt", während er in demselben Halbkreis bleibt, z. für 355 und 5 wäre dies 0 und nicht 180. Dazu müssen Sie prüfen, ob der Unterschied zwischen den beiden Winkeln größer als 180 ist oder nicht. Wenn ja, erhöhen Sie den kleineren Winkel um 360, bevor Sie die obige Formel verwenden.

Ich sehe nicht, wie die zweite Alternative für mehr als zwei Winkel verallgemeinert werden kann.

6
David Hanak

Wie bei allen Durchschnittswerten hängt die Antwort von der Wahl der Metrik ab. Für eine gegebene Metrik M ist der Durchschnitt einiger Winkel a_k in [-pi, pi] für k in [1, N] der Winkel a_M, der die Summe der quadratischen Abstände d ^ 2_M (a_M, a_k) minimiert. Für ein gewichtetes Mittel enthält man einfach die Gewichte w_k in der Summe (so dass sum_k w_k = 1 ist). Das ist,

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

Zwei gängige Wahlmöglichkeiten für Metriken sind die Frobenius- und die Riemann-Metrik. Für die Frobenius-Metrik gibt es eine direkte Formel, die dem in der Zirkelstatistik gebräuchlichen Begriff der durchschnittlichen Peilung entspricht. Für Einzelheiten siehe "Mittelwerte und Mittelwertbildung in der Rotationsgruppe", Maher Moakher, SIAM-Journal für Matrixanalyse und Anwendungen, Band 24, Ausgabe 1, 2002.
http://link.aip.org/link/?SJMAEL/24/1/1

Hier ist eine Funktion für GNU Octave 3.2.4, die die Berechnung durchführt:

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

Hier ist die vollständige Lösung: (Die Eingabe ist ein Array mit Peilung in Grad (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

Ich möchte eine Methode teilen, die ich mit einem Mikrocontroller verwendet habe, der keine Gleitkomma- oder Trigonometrie-Funktionen hatte. Ich musste immer noch 10 Rohlagerwerte "mitteln", um Abweichungen auszugleichen.

  1. Prüfen Sie, ob die erste Peilung im Bereich von 270 bis 360 oder 0 bis 90 Grad liegt (zwei nördliche Quadranten).
  2. Wenn dies der Fall ist, drehen Sie diese und alle nachfolgenden Messwerte um 180 Grad, wobei alle Werte im Bereich 0 <= Peilung <360 bleiben. Andernfalls nehmen Sie die Messwerte so, wie sie kommen.
  3. Nach 10 Messwerten berechnen Sie den numerischen Durchschnitt unter der Annahme, dass kein Wraparound vorliegt
  4. Wenn die 180-Grad-Drehung wirksam war, drehen Sie den berechneten Durchschnitt um 180 Grad, um wieder in eine "echte" Peilung zu gelangen.

Es ist nicht ideal; es kann brechen. Ich bin in diesem Fall damit durchgekommen, weil sich das Gerät nur sehr langsam dreht. Ich werde es dort veröffentlichen, falls jemand anderes unter ähnlichen Einschränkungen arbeitet.

3
Tom

Ich würde den Vektorweg mit komplexen Zahlen gehen. Mein Beispiel ist in Python, das komplexe Zahlen enthält:

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)

Beachten Sie, dass Python keine benötigt, um eine temporäre neue Liste von Vektoren zu erstellen. Alle oben genannten Funktionen können in einem Schritt ausgeführt werden. Ich habe diesen Weg gewählt, um den Pseudo-Code für andere Sprachen anzunähern.

2
tzot

In Python mit Winkeln zwischen [-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)

Einzelheiten:

Für den Durchschnitt von zwei Winkeln gibt es zwei Durchschnittswerte im Abstand von 180 °, aber wir möchten vielleicht den engeren Durchschnitt. 

Visuell ergibt sich aus dem Durchschnitt der blauen (b) und der grünen (a) der Teal-Punkt:

 Original

Winkel "umlaufen" (z. B. 355 + 10 = 5), aber die Standardarithmetik ignoriert diesen Verzweigungspunkt . Wenn jedoch der Winkel b dem Verzweigungspunkt entgegengesetzt ist, dann (b + g)/2 ergibt den nächsten Durchschnitt: den Teal-Punkt.

Für zwei beliebige Winkel können wir das Problem so drehen, dass einer der Winkel dem Verzweigungspunkt entgegengesetzt ist, eine Standardmittelung durchführen und dann zurückdrehen. 

 rotated  returned

2
Brad Saund

Hier ist eine komplette C++ - Lösung:

#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);
}

Sie nimmt die Winkel in Form eines Vektors von Doubles an und gibt den Durchschnitt einfach als Double zurück. Die Winkel müssen in Grad sein, und der Durchschnitt ist natürlich auch in Grad.

2
adam10603

Hier ist eine Idee: Erstellen Sie den Durchschnitt iterativ, indem Sie immer den Durchschnitt der Winkel berechnen, die am nächsten aneinander liegen, und dabei ein Gewicht beibehalten. 

Eine andere Idee: Finden Sie den größten Abstand zwischen den angegebenen Winkeln. Suchen Sie den Punkt, der ihn halbiert, und wählen Sie dann den gegenüberliegenden Punkt auf dem Kreis als Referenznullpunkt aus, um den Durchschnitt daraus zu berechnen. 

1

Lassen Sie uns diese Winkel mit Punkten auf dem Umfang des Kreises darstellen.

Können wir davon ausgehen, dass alle diese Punkte auf dieselbe Hälfte des Kreises fallen? (Ansonsten gibt es keinen offensichtlichen Weg, den "durchschnittlichen Winkel" zu definieren.) Denken Sie an zwei Punkte auf dem Durchmesser, z. B. 0 Grad und 180 Grad - ist der Durchschnitt 90 Grad oder 270 Grad? Punkte gleichmäßig verteilen?)

Mit dieser Annahme wählen wir einen beliebigen Punkt auf diesem Halbkreis als "Ursprung" aus und messen den gegebenen Winkelsatz in Bezug auf diesen Ursprung (nennen Sie diesen "relativen Winkel"). Beachten Sie, dass der relative Winkel absolut unter 180 ° liegt. Nehmen Sie schließlich den Mittelwert dieser relativen Winkel, um den gewünschten Durchschnittswinkel zu erhalten (relativ zu unserem Ursprung natürlich). 

1
Zach Scrivena

Es gibt keine "richtige Antwort". Ich empfehle das Buch zu lesen, K. V. Mardia und P. E. Jupp, "Directional Statistics", (Wiley, 1999),. Für eine eingehende Analyse.

1
cffk

Nun, ich bin sehr spät auf der Party, dachte aber, ich würde meinen Wert von 2 Cent hinzufügen, da ich keine endgültige Antwort finden konnte. Am Ende habe ich die folgende Java-Version der Mitsuta-Methode implementiert, die hoffentlich eine einfache und robuste Lösung bietet. Insbesondere da die Standardabweichung sowohl eine Messstreuung liefert als auch, wenn sd == 90, zeigen die Eingabewinkel einen mehrdeutigen Mittelwert an. 

EDIT: Eigentlich wurde mir klar, dass meine ursprüngliche Implementierung noch weiter vereinfacht werden kann, in Anbetracht der Konversation und Trigonometrie, die in den anderen Antworten vor sich geht. 

/**
 * 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))};
}

... und für alle, die (Java) Geeks da draußen sind, können Sie den obigen Ansatz verwenden, um den mittleren Winkel in einer Zeile zu erhalten.

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

Hier ist eine vollständig arithmetische Lösung, die gleitende Durchschnitte verwendet und die Werte normalisiert. Es ist schnell und liefert korrekte Antworten, wenn sich alle Winkel auf einer Seite des Kreises befinden (innerhalb von 180 °).

Es ist mathematisch äquivalent zum Addieren des Offsets, der die Werte in den Bereich (0, 180) verschiebt, den Mittelwert berechnet und dann den Offset subtrahiert.

In den Kommentaren wird beschrieben, welchen Bereich ein bestimmter Wert zu einem bestimmten Zeitpunkt annehmen kann

// 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

Das Problem ist extrem einfach .. 1. Stellen Sie sicher, dass alle Winkel zwischen -180 und 180 Grad liegen. __ 2. a Fügen Sie alle nicht negativen Winkel hinzu, berechnen Sie ihren Durchschnitt und COUNT wie viele 2. Fügen Sie alle negativen Winkel hinzu, nehmen Sie ihren Durchschnitt und ZÄHLEN Sie, wie viele. 3. Nehmen Sie die Differenz von pos_average minus neg_average Wenn die Differenz größer als 180 ist, ändern Sie die Differenz auf 360 minus Differenz. Andernfalls ändern Sie einfach das Vorzeichen der Differenz. Beachten Sie, dass die Differenz immer nicht negativ ist .. _ Der Average_Angle entspricht dem pos_average plus der Differenz mal der "Gewichtung", der negativen Anzahl geteilt durch die Summe der negativen und positiven Anzahl

0
DynamicChart

Basierend auf Alnitaks Antwort habe ich eine Java-Methode zur Berechnung des Durchschnitts von mehreren Winkeln geschrieben:

Wenn Ihre Winkel im Bogenmaß sind:

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);
}

Wenn Ihre Winkel in Grad sind:

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));
}

Unter dem folgenden Link finden Sie eine Lösung und eine kleine Erklärung für JEDE Programmiersprache: https://rosettacode.org/wiki/Averages/Mean_angle

Zum Beispiel C++ - Lösung :

#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;
}

Ausgabe:

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

Oder Matlab-Lösung :

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-Funktion:

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

Auf Englisch:

  1. Erstellen Sie einen zweiten Datensatz, wobei alle Winkel um 180 verschoben sind.
  2. Nehmen Sie die Varianz beider Datensätze an.
  3. Nimm den Durchschnitt des Datensatzes mit der kleinsten Varianz.
  4. Wenn dieser Durchschnitt aus dem verschobenen Satz stammt, verschieben Sie die Antwort erneut um 180.

In Python:

Ein #numpy NX1-Array von Winkeln

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

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

Während die Antwort von Starblue den Winkel des durchschnittlichen Einheitsvektors angibt, ist es möglich, das Konzept des arithmetischen Mittels auf Winkel auszudehnen, wenn Sie akzeptieren, dass es mehr als eine Antwort im Bereich von 0 bis 2 * pi (oder 0 ° bis 0 °) geben kann 360 °). Zum Beispiel kann der Durchschnitt von 0 ° und 180 ° entweder 90 ° oder 270 ° sein.

Das arithmetische Mittel hat die Eigenschaft, der einzige Wert mit der minimalen Summe der quadrierten Abstände zu den Eingabewerten zu sein. Der Abstand entlang des Einheitskreises zwischen zwei Einheitsvektoren kann leicht als der inverse Kosinus ihres Punktprodukts berechnet werden. Wenn wir einen Einheitsvektor wählen, indem wir die Summe des quadrierten inversen Cosinus des Punktprodukts unseres Vektors und jeden Eingabeeinheitsvektors minimieren, haben wir einen äquivalenten Durchschnitt. Denken Sie auch daran, dass es in Ausnahmefällen zwei oder mehr Minimums geben kann.

Dieses Konzept kann auf eine beliebige Anzahl von Dimensionen erweitert werden, da der Abstand entlang der Einheitskugel genauso berechnet werden kann wie der Abstand entlang des Einheitskreises - dem inversen Cosinus des Punktprodukts zweier Einheitsvektoren.

Für Kreise könnten wir diesen Durchschnitt auf verschiedene Weise lösen, aber ich schlage den folgenden O (n ^ 2) -Algorithmus vor (Winkel sind im Bogenmaß, und ich vermeide es, die Einheitsvektoren zu berechnen):

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

Wenn sich alle Winkel innerhalb eines Winkels von 180 ° befinden, könnten wir einen einfacheren O (n) + O (Sortier) -Algorithmus verwenden (wiederum mit Radiant und ohne Verwendung von Einheitsvektoren):

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

Um Grad zu verwenden, ersetzen Sie einfach pi durch 180. Wenn Sie mehr Dimensionen verwenden möchten, müssen Sie höchstwahrscheinlich eine iterative Methode verwenden, um den Durchschnitt zu ermitteln.

0
John Thoits

Hier ist ein bisschen Java-Code für durchschnittliche Winkel.

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

Ich habe das Problem mit Hilfe der Antwort von @David_Hanak gelöst . Wie er sagt:

Der Winkel, der zwischen den beiden anderen "zeigt", während er in demselben Halbkreis bleibt, z. für 355 und 5 wäre dies 0 und nicht 180. Dazu müssen Sie prüfen, ob der Unterschied zwischen den beiden Winkeln größer als 180 ist oder nicht. Wenn ja, erhöhen Sie den kleineren Winkel um 360, bevor Sie die obige Formel verwenden.

Also habe ich den Durchschnitt aller Winkel berechnet. Dann erhöhen Sie alle Winkel, die kleiner sind als diese, um 360. Berechnen Sie dann den Durchschnitt neu, indem Sie alle addieren und durch ihre Länge teilen.

        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;

Funktioniert perfekt.

0
konsnos

Der Durchschnittswinkel phi_avg sollte die Eigenschaft haben, dass sum_i | phi_avg-phi_i | ^ 2 minimal wird, wobei die Differenz in [-Pi, Pi) sein muss (weil es kürzer sein kann, anders herum zu verfahren!). Dies kann leicht erreicht werden, indem alle Eingangswerte auf [0, 2Pi] normiert werden, ein laufender Mittelwert phi_run beibehalten und | phi_i-phi_run | als Normalisierung gewählt wird zu [-Pi, Pi) (durch Addition oder Subtraktion von 2Pi). Die meisten obigen Vorschläge machen etwas anderes, was nicht diese minimale Eigenschaft hat, d. H. Sie mitteln etwas, aber keine Winkel.

0
ackb

(Ich möchte nur meinen Standpunkt aus der Schätztheorie oder der statistischen Inferenz mitteilen.)

Der Versuch von Nimble besteht darin, die MMSE-Schätzung eines Satzes von Winkeln zu erhalten, aber es ist eine von Entscheidungen, eine "gemittelte" Richtung zu finden; Sie können auch eine MMAE-Schätzung oder eine andere Schätzung als "gemittelte" Richtung finden, und diese hängt von Ihrem metrischen Quantifizierungsfehler der Richtung ab. oder allgemeiner in der Schätztheorie die Definition der Kostenfunktion.

^ MMSE/MMAE entspricht dem minimalen mittleren quadratischen/absoluten Fehler.

ackb sagte "Der durchschnittliche Winkel phi_avg sollte die Eigenschaft haben, dass sum_i | phi_avg-phi_i | ^ 2 minimal wird ... sie mitteln etwas, aber keine Winkel"

---- Sie quantifizieren Fehler im Mittelwert, und dies ist einer der meist verbreiteten, jedoch nicht der einzige Weg. Die hier von den meisten Menschen favorisierte Antwort (d. H. Die Summe der Einheitsvektoren und der Winkel des Ergebnisses) ist eine der vernünftigen Lösungen. Es ist (kann bewiesen werden) der ML-Schätzer, der als die "gemittelte" Richtung dient, die wir wollen, wenn die Richtungen der Vektoren als von Mises-Verteilung modelliert werden. Diese Verteilung ist nichts Besonderes und ist nur eine periodisch erfasste Verteilung eines 2D-Guassian. Siehe Gl. (2.179) in Bishop's Buch "Pattern Recognition and Machine Learning". Wiederum ist es keineswegs die einzige, die die "durchschnittliche" Richtung darstellt, es ist jedoch eine vernünftige, die sowohl eine gute theoretische Begründung als auch eine einfache Implementierung hat.

Flink sagte, dass "ackb richtig ist, dass diese vektorbasierten Lösungen nicht als echte Mittelwerte der Winkel betrachtet werden können, sondern nur Mittelwerte der Gegenvektoren des Einheitsvektors sind".

----das ist nicht wahr. Die "Einheitsvektor-Gegenstücke" enthüllen die Informationen über die Richtung eines Vektors. Der Winkel ist eine Größe, ohne die Länge des Vektors zu berücksichtigen, und der Einheitsvektor enthält die zusätzliche Information, dass die Länge 1 ist. Sie können Ihren "Einheit" -Vektor als Länge 2 definieren, es spielt keine Rolle.

0
waterworld

Alnitak hat die richtige Lösung. Die Lösung von Nick Fortescue ist funktionell die gleiche. 

Für den speziellen Fall wo

(Summe (x_Komponente) = 0,0 && Summe (Y_Komponente) = 0,0) // z. 2 Winkel von 10 und 190 Grad e.

verwenden Sie 0,0 Grad als Summe

Rechentechnisch muss für diesen Fall getestet werden, da atan2 (0., 0.) undefiniert ist und einen Fehler generiert.

0
jeffD

Sie können diese Funktion in Matlab verwenden:

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