Ackb a raison de dire que ces solutions vectorielles ne peuvent pas être considérées comme de véritables moyennes d'angles, elles ne sont qu'une moyenne de leurs contreparties vectorielles unitaires. Cependant, la solution proposée par ackb ne semble pas mathématiquement valable.
La solution suivante est mathématiquement dérivée de l'objectif de minimiser (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 examiner dans quels cas exactement la différence entre les angles est différente de la différence entre leurs homologues en nombres normaux. Considérons les angles x et y, si y >= x - 180 et y <= x + 180, alors nous pouvons utiliser directement la différence (x-y). Sinon, si la première condition n'est pas remplie, nous devons utiliser (y+360) dans le calcul au lieu de y. De même, si la deuxième condition n'est pas remplie, nous devons utiliser (y-360) au lieu de y. Étant donné que l'équation de la courbe que nous minimisons ne change qu'aux points où ces inégalités passent de vrai à faux ou vice versa, nous pouvons séparer la plage complète [0,360] en un ensemble de segments, séparés par ces points. Il nous suffit alors de trouver le minimum de chacun de ces segments, puis le minimum du minimum de chaque segment, qui est la moyenne.
Voici une image qui montre où se situent les problèmes de calcul des différences d'angle. Si x se trouve dans la zone grise, il y aura un problème.
Pour minimiser une variable, selon la courbe, on peut prendre la dérivée de ce que l'on veut minimiser et ensuite on trouve le point d'inflexion (qui est celui où la dérivée = 0).
Nous appliquerons ici l'idée de minimiser la différence au carré pour dériver la formule commune de la moyenne arithmétique : somme(a[i])/n. La courbe y = somme((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, on l'applique aux courbes avec nos différences ajustées :
b = sous-ensemble de a où la différence (angulaire) correcte a[i]-x c = sous-ensemble de a où la différence (angulaire) correcte (a[i]-360)-x cn = taille de c d = sous-ensemble de a où la 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 ne suffit pas pour obtenir le minimum, alors que cela fonctionne pour les valeurs normales, qui ont un ensemble non limité, donc le résultat sera certainement dans la gamme de l'ensemble et est donc valide. Nous avons besoin du minimum dans un intervalle (défini par le segment). Si le minimum est inférieur à la limite inférieure de notre segment, alors le minimum de ce segment doit être à la limite inférieure (car les courbes quadratiques n'ont qu'un seul point de retournement) et si le minimum est supérieur à la limite supérieure de notre segment, alors le minimum du segment est à la limite supérieure. Une fois que nous avons le minimum de chaque segment, nous trouvons simplement celui qui a la valeur la plus basse pour ce que nous minimisons (somme((b[i]-x)^2) + somme(((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, incluant quelques optimisations, sa complexité est O(nlogn). Elle peut être réduite à O(n) si vous remplacez le tri basé sur la comparaison par un tri non basé sur la comparaison, tel que le tri radix.
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 des angles. Vous devez également vous rappeler que les angles ne sont pas des vecteurs, aussi séduisant que cela puisse paraître lorsqu'on traite parfois des angles.
Cet algorithme s'applique bien sûr aussi à toutes les quantités qui obéissent à l'arithmétique modulaire (avec un ajustement minimal), comme l'heure du jour.
Je tiens également à souligner que même s'il s'agit d'une véritable moyenne d'angles, contrairement aux solutions vectorielles, cela ne signifie pas nécessairement que c'est la solution que vous devez utiliser, la moyenne des vecteurs unitaires correspondants peut très bien être la valeur que vous devez utiliser.
2 votes
Par angle moyen, je suppose que vous voulez en fait un roulement moyen. Un angle existe entre deux lignes, un relèvement est la direction d'une seule ligne. Dans ce cas, Starblue a raison.
0 votes
@Nick Fortescue : pouvez-vous mettre à jour votre question pour être plus précis : voulez-vous dire des angles ou un roulement ?
1 votes
En fait, je voulais quelque chose de légèrement plus compliqué (mais qui est analogue aux roulements) et j'essayais de simplifier pour rendre la question plus facile, et comme d'habitude, je l'ai rendue plus compliquée. J'ai trouvé la réponse que je voulais à catless.ncl.ac.uk/Risques/7.44.html#subj4 . Je vais rééditer la qn.
0 votes
La réponse de Risques est fondamentalement ce que je propose, sauf qu'elle peut rencontrer des problèmes lorsque le dénominateur est 0.
0 votes
Article intéressant sur la signification des angles : twistedoakstudios.com/blog/?p=938
0 votes
C'est également la technique nécessaire pour calculer la teinte moyenne des pixels. (Ce n'est pas la même chose de faire la moyenne des pixels en RVB et de la convertir ensuite en HSB/HSL).
0 votes
La direction d'une ligne unique sera toujours relative à un certain axe (c'est-à-dire une autre ligne).
0 votes
@dbliss, distinction extrêmement importante. Par définition, les relèvements partagent un axe commun qui est généralement le nord de la grille, ce qui n'est pas le cas des directions. Un théodolite, par exemple, mesure les directions horizontales par rapport à un axe arbitraire, qui se transforme en un nouvel axe arbitraire chaque fois que vous déplacez le théodolite. Après plus de trois décennies de travail dans le domaine de l'arpentage, je dois encore régulièrement expliquer aux gens la différence entre les angles, les relèvements par cercle entier, les relèvements par quadrant et les directions horizontales.
0 votes
@ShaneMacLaughlin vous êtes très à fond dans le jargon et les distinctions qui ne sont "extrêmement importantes" que dans votre domaine apparemment très étroit. le terme général pour le type de données sur lequel l'auteur de la question s'interroge est "données circulaires", et non roulements. les données circulaires englobent les temps sur une horloge, les directions (telles qu'elles sont normalement comprises, et non pas ce dont vous parlez), les couleurs, les orientations, etc.
0 votes
@dbliss, vous venez de modifier la question et de remplacer le terme angles par données circulaires pour qu'elle corresponde mieux à votre propre commentaire ! Vraiment ???? Pour clarifier, la question originale a déjà été éditée par l'OP pour lire "pour résoudre toute la confusion, quand je me réfère à des angles, vous pouvez supposer que je veux dire des roulements". Si vous pensez honnêtement que la mesure des angles et des roulements est un domaine très étroit, puis-je supposer que vous n'avez jamais étudié la trigonométrie à l'école, que vous n'avez pas de GPS sur votre téléphone, et que vous n'avez jamais utilisé Google Earth
0 votes
Pour clarifier davantage la différence entre les relèvements et les angles dans le contexte donné d'un cadran d'horloge, si je regarde l'horloge à midi, les deux aiguilles ont un relèvement de 0 degré. Si je regarde l'horloge un jour plus tard, elles ont toujours une orientation de 0 degré, mais la grande aiguille a parcouru 720 degrés dans le sens horaire. L'orientation moyenne correspondrait donc à l'heure moyenne de la journée, tandis que l'angle moyen correspondrait au temps moyen passé. À mon avis, il s'agit d'une distinction cruciale dans le contexte de la question initiale.
0 votes
Cette question : stackoverflow.com/questions/1158909/ . a été fermée car elle était censée être trop similaire à cette question. A mon avis, elle n'est pas similaire. Peut-on voter pour la réouverture de cette autre question ?