ackb a raison de dire que ces solutions vectorielles ne peuvent pas être considérées comme de vraies moyennes d'angles, elles ne sont qu'une moyenne des homologues des vecteurs unitaires. Cependant, la solution suggérée par ackb ne semble pas mathématiquement valable.
Ce qui suit est une solution dérivée mathématiquement de l'objectif de minimisation (angle [i] - avgAngle) ^ 2 (où la différence est corrigée si nécessaire), ce qui en fait une véritable moyenne arithmétique des angles.
Tout d'abord, nous devons regarder exactement dans quels cas la différence entre les angles est différente de la différence entre leurs homologues en nombre normal. Considérons les angles x et y, si y> = x - 180 et y <= x + 180, alors nous pouvons utiliser la différence (xy) directement. Sinon, si la première condition n'est pas remplie, nous devons utiliser (y + 360) dans le calcul au lieu de y. En conséquence, si la deuxième condition n'est pas remplie, nous devons utiliser (y-360) au lieu de y. Puisque l'équation de la courbe que nous minimisons les changements seulement aux points où ces inégalités changent de vrai à faux ou vice versa, nous pouvons séparer la gamme complète [0,360) en un ensemble de segments, séparés par ces points. Ensuite, il suffit de trouver le minimum de chacun de ces segments, puis le minimum du minimum de chaque segment, qui est la moyenne.
Voici une image montrant où les problèmes se produisent dans le calcul des différences d'angle. Si x se trouve dans la zone grise, il y aura un problème.
Pour minimiser une variable, en fonction de la courbe, nous pouvons prendre la dérivée de ce que nous voulons minimiser puis nous trouvons le point de retournement (qui est où la dérivée = 0).
Ici, nous appliquerons l'idée de minimiser la différence au carré pour dériver la formule de moyenne arithmétique commune: sum (a [i]) / n. La courbe y = sum ((a [i] -x) ^ 2) peut être minimisée de cette manière:
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
Maintenant, appliquez-le aux courbes avec nos différences ajustées:
b = sous-ensemble de a où la différence (angulaire) correcte a [i] -xc = sous-ensemble de a où la différence (angulaire) correcte (a [i] -360) -x cn = taille de cd = sous-ensemble de a où le différence (angulaire) correcte (a [i] +360) -x dn = taille de 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
Cela seul n'est pas tout à fait suffisant pour obtenir le minimum, alors que cela fonctionne pour des valeurs normales, qui ont un ensemble illimité, donc le résultat se situera certainement dans la plage de l'ensemble et est donc valide. Nous avons besoin du minimum dans une plage (définie par le segment). Si le minimum est inférieur à la borne inférieure de notre segment, alors le minimum de ce segment doit être à la borne inférieure (car les courbes quadratiques n'ont qu'un seul point de retournement) et si le minimum est supérieur à la borne supérieure de notre segment, alors le minimum du segment est au limite supérieure. Une fois que nous avons le minimum pour chaque segment, nous trouvons simplement celui qui a la valeur la plus basse pour ce que nous minimisons (sum ((b [i] -x) ^ 2) + sum (((c [i] -360 ) -b) ^ 2) + somme (((d [i] +360) -c) ^ 2)).
Voici une image de la courbe, qui montre comment elle change aux points où x = (a [i] +180)% 360. L'ensemble de données en question est {65,92,230,320,250}.
Voici une implémentation de l'algorithme en Java, avec quelques optimisations, sa complexité est O (nlogn). Il peut être réduit à O (n) si vous remplacez le tri basé sur la comparaison par un tri non basé sur la comparaison, tel que le tri par base.
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_;
}
La moyenne arithmétique d'un ensemble d'angles peut ne pas correspondre à votre idée intuitive de ce que devrait être la moyenne. Par exemple, la moyenne arithmétique de l'ensemble {179,179,0,181,181} est 216 (et 144). La réponse à laquelle vous pensez immédiatement est probablement 180, mais il est bien connu que la moyenne arithmétique est fortement affectée par les valeurs de bord. Vous devez également vous rappeler que les angles ne sont pas des vecteurs, aussi attrayants que cela puisse paraître lorsqu'il s'agit parfois d'angles.
Cet algorithme s'applique bien entendu également à toutes les grandeurs qui obéissent à l'arithmétique modulaire (avec un ajustement minimal), comme l'heure de la journée.
Je voudrais également souligner que même s'il s'agit d'une vraie moyenne d'angles, contrairement aux solutions vectorielles, cela ne signifie pas nécessairement que c'est la solution que vous devriez utiliser, la moyenne des vecteurs unitaires correspondants peut bien être la valeur que vous devrait utiliser.