ackb hat recht, dass diese vektorbasierten Lösungen nicht als echte Durchschnittswerte von Winkeln betrachtet werden können, sondern nur ein Durchschnitt der Einheitsvektor-Gegenstücke sind. Die von ackb vorgeschlagene Lösung scheint jedoch mathematisch nicht zu klingen.
Das Folgende ist eine Lösung, die mathematisch aus dem Ziel der Minimierung (Winkel [i] - avgAngle) ^ 2 (wobei die Differenz gegebenenfalls korrigiert wird) abgeleitet wird, was sie zu einem echten arithmetischen Mittel der Winkel macht.
Zunächst müssen wir uns genau ansehen, in welchen Fällen sich der Unterschied zwischen den Winkeln von dem Unterschied zwischen ihren Gegenstücken mit normaler Anzahl unterscheidet. Betrachten Sie die Winkel x und y. Wenn y> = x - 180 und y <= x + 180, können wir die Differenz (xy) direkt verwenden. Andernfalls müssen wir (y + 360) anstelle von y in der Berechnung verwenden, wenn die erste Bedingung nicht erfüllt ist. Entsprechend müssen wir (y-360) anstelle von y verwenden, wenn die zweite Bedingung nicht erfüllt ist. Da sich die Gleichung der Kurve nur an den Punkten minimiert, an denen sich diese Ungleichungen von wahr zu falsch oder umgekehrt ändern, können wir den gesamten [0,360] -Bereich in eine Reihe von Segmenten unterteilen, die durch diese Punkte getrennt sind. Dann müssen wir nur das Minimum jedes dieser Segmente und dann das Minimum des Minimums jedes Segments finden, das der Durchschnitt ist.
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.
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 (an dem die Ableitung = 0 ist).
Hier wenden wir die Idee an, die quadratische Differenz zu minimieren, um die gemeinsame arithmetische Mittelwertformel abzuleiten: sum (a [i]) / n. Die Kurve y = Summe ((a [i] -x) ^ 2) kann auf folgende 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 nun auf Kurven mit unseren angepassten Differenzen an:
b = Teilmenge von a, wobei die richtige (Winkel-) Differenz a [i] -xc = Teilmenge von a, wo die richtige (Winkel-) Differenz (a [i] -360) -x cn = Größe von cd = Teilmenge von a, wo die korrekte (Winkel-) Differenz (a [i] +360) -x dn = Größe von 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
Dies allein reicht nicht aus, um das Minimum zu erreichen, während es für normale Werte mit einer unbegrenzten Menge funktioniert. Das Ergebnis liegt also definitiv im Bereich der Menge und ist daher gültig. Wir brauchen das Minimum innerhalb eines Bereichs (definiert durch das Segment). Wenn das Minimum kleiner als die Untergrenze unseres Segments ist, muss das Minimum dieses Segments an der Untergrenze liegen (da quadratische Kurven nur 1 Wendepunkt haben), und wenn das Minimum größer als die Obergrenze unseres Segments ist, liegt das Minimum des Segments bei obere Grenze. Nachdem wir das Minimum für jedes Segment haben, finden wir einfach dasjenige, das den niedrigsten Wert für das hat, was wir minimieren (Summe ((b [i] -x) ^ 2) + Summe (((c [i] -360)))) ) -b) ^ 2) + Summe (((d [i] +360) -c) ^ 2)).
Hier ist ein Bild der Kurve, das zeigt, wie sie sich an den Punkten ändert, an denen x = (a [i] +180)% 360 ist. Der fragliche Datensatz ist {65,92,230,320,250}.
Hier ist eine Implementierung des Algorithmus in Java, einschließlich einiger Optimierungen, seine Komplexität ist O (nlogn). Sie kann auf O (n) reduziert werden, wenn Sie die vergleichsbasierte Sortierung durch eine nicht vergleichsbasierte Sortierung wie die 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 überein, wie hoch der Durchschnitt sein sollte. 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 Kantenwerten beeinflusst wird. Sie sollten sich auch daran erinnern, dass Winkel keine Vektoren sind, so ansprechend dies auch erscheinen mag, wenn Sie manchmal mit Winkeln arbeiten.
Dieser Algorithmus gilt natürlich auch für alle Größen, die der modularen Arithmetik (mit minimaler Anpassung) entsprechen, wie z. B. die Tageszeit.
Ich möchte auch betonen, dass, obwohl dies im Gegensatz zu den Vektorlösungen ein echter Durchschnitt der Winkel ist, dies nicht unbedingt bedeutet, dass es die Lösung ist, die Sie verwenden sollten, der Durchschnitt der entsprechenden Einheitsvektoren möglicherweise der Wert ist, den Sie tatsächlich haben sollte verwendet werden.