Je veux calculer la moyenne d’un dataset circulaires. Par exemple, je pourrais avoir plusieurs échantillons de la lecture d’une boussole. Le problème, bien sûr, est de savoir comment faire face au problème. Le même algorithme pourrait être utile pour un cadran d’horloge.
La question réelle est plus compliquée – que signifient les statistiques sur une sphère ou dans un espace algébrique qui “enveloppe”, par exemple le groupe additif mod n. La réponse peut ne pas être unique, par exemple la moyenne de 359 degrés et 1 degré pourrait être de 0 ou 180, mais statistiquement 0 semble meilleur.
C’est un vrai problème de programmation pour moi et j’essaie de faire en sorte que cela ne ressemble pas à un problème mathématique.
Calculer les vecteurs unitaires à partir des angles et prendre l’angle de leur moyenne.
Cette question est examinée en détail dans le livre: “Statistics On Spheres”, Geoffrey S. Watson, notes de cours de l’Université d’Arkansas en sciences mathématiques, 1983 John Wiley & Sons, Inc., tel que mentionné à l’ adresse http: //catless.ncl. ac.uk/Risks/7.44.html#subj4 par Bruce Karsh.
Un bon moyen d’estimer un angle moyen, A, à partir d’un ensemble de mesures d’angle a [i] 0 <= i
sum_i_from_1_to_N sin(a[i]) a = arctangent --------------------------- sum_i_from_1_to_N cos(a[i])
La méthode donnée par starblue est équivalente en termes de calcul, mais ses raisons sont plus claires et probablement plus efficaces du sharepoint vue programmatique, et fonctionnent également bien dans le cas zéro, donc bravo à lui.
Le sujet est maintenant exploré plus en détail sur Wikipedia , et avec d’autres utilisations, comme les parties fractionnaires.
Je vois le problème – par exemple, si vous avez un angle de 45 ‘et un angle de 315’, la moyenne “naturelle” serait de 180 ‘, mais la valeur que vous voulez est en fait 0’.
Je pense que Starblue est sur quelque chose. Calculez simplement les coordonnées cartésiennes (x, y) pour chaque angle et ajoutez les vecteurs résultants ensemble. Le décalage angular du vecteur final doit être le résultat souhaité.
x = y = 0 foreach angle { x += cos(angle) y += sin(angle) } average_angle = atan2(y, x)
J’ignore pour le moment qu’un cap compas commence au nord et va dans le sens des aiguilles d’une montre, alors que les coordonnées cartésiennes “normales” commencent par zéro le long de l’axe X, puis vont dans le sens inverse des aiguilles d’une montre. Les maths devraient fonctionner de la même manière, indépendamment des autres.
POUR LE CAS SPÉCIAL DE DEUX ANGLES:
La réponse ((a + b) mod 360) / 2 est incorrecte . Pour les angles 350 et 2, le point le plus proche est 356 et non 176.
Le vecteur unitaire et les solutions sortinggonomésortingques peuvent être trop coûteux.
Ce que je retiens d’un peu de bricolage est:
diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180 angle = (360 + b + ( diff / 2 ) ) mod 360
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 contreparties vectorielles unitaires. Cependant, la solution proposée par ackb ne semble pas être mathématiquement rationnelle.
Ce qui suit est une solution mathématiquement dérivée de l’objective 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.
Premièrement, nous devons examiner exactement 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, il faut utiliser (y + 360) dans le calcul plutôt que y. Correspondant, 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 minimise uniquement les changements aux points où ces inégalités changent 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. Ensuite, il suffit de trouver le minimum de chacun de ces segments, puis le 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 situe dans la zone grise, il y aura un problème.
Pour minimiser une variable, en fonction de la courbe, on peut prendre la dérivée de ce que l’on veut minimiser et ensuite on trouve le tournant (qui est la dérivée = 0).
Nous appliquerons ici l’idée de minimiser la différence au carré pour obtenir la formule de la 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, en l’appliquant aux courbes avec nos différences ajustées:
b = sous-ensemble d’un où la différence correcte (angular) a [i] -xc = sous-ensemble d’un où la différence correcte (angular) (a [i] -360) -x cn = taille de cd = sous-ensemble d’un où le différence (angular) 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 ne suffit pas à obtenir le minimum, alors que cela fonctionne pour les valeurs normales, cela a un ensemble illimité, donc le résultat se situera définitivement 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 limite inférieure de notre segment, le minimum de ce segment doit être à la limite inférieure (car les courbes quadratiques ne comportent que 1 sharepoint retournement) et si le minimum est supérieur à la limite supérieure du segment limite supérieure Une fois que nous avons le minimum pour chaque segment, nous trouvons simplement celui qui a la valeur la plus faible pour ce que nous minimisons (sum ((b [i] -x) ^ 2) + sum (((c [i] -360 ) -b) ^ 2) + sum (((d [i] +360) -c) ^ 2)).
Voici une image de la courbe, qui montre comment elle change aux points où x = (a [i] +180)% 360. Le fichier 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). Il peut être réduit à O (n) si vous remplacez le sorting basé sur la comparaison par un sorting non basé sur une comparaison, tel que le sorting de 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 lowerAngles_ = new LinkedList (); List upperAngles_ = new LinkedList (); 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 lowerAnglesIter = lowerAngles_.iterator(); for(int i = 0; i < lowerAngles_.size(); i++) lowerAngles[i] = lowerAnglesIter.next(); upperAngles = new double[upperAngles_.size()]; Iterator upperAnglesIter = upperAngles_.iterator(); for(int i = 0; i < upperAngles_.size(); i++) upperAngles[i] = upperAnglesIter.next(); } List averageAngles = new LinkedList (); 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 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 influencée par les valeurs des arêtes. Vous devez également vous rappeler que les angles ne sont pas des vecteurs, aussi attrayants que cela puisse paraître lorsque vous traitez des angles parfois.
Cet algorithme s’applique bien sûr à toutes les quantités qui obéissent à l’arithmétique modulaire (avec un ajustement minimal), comme l’heure du jour.
Je voudrais également souligner que, même si la moyenne des angles est différente de celle des solutions vectorielles, cela ne signifie pas forcément que vous devriez utiliser la solution, mais la moyenne des vecteurs unitaires correspondants pourrait bien être la valeur réelle. devrait être utiliser.
Vous devez définir la moyenne plus précisément. Pour le cas spécifique de deux angles, je peux penser à deux scénarios différents:
Je ne vois pas comment la deuxième alternative peut être généralisée pour le cas de plus de deux angles, cependant.
Comme toutes les moyennes, la réponse dépend du choix de la mésortingque. Pour une mésortingque donnée M, la moyenne de certains angles a_k dans [-pi, pi] pour k dans [1, N] est cet angle a_M qui minimise la sum des distances au carré d ^ 2_M (a_M, a_k). Pour une moyenne pondérée, on inclut simplement dans la sum les poids w_k (tels que sum_k w_k = 1). C’est,
a_M = arg min_x sum_k w_k d ^ 2_M (x, a_k)
Les mésortingques Frobenius et Riemann sont deux choix courants de mésortingque. Pour la mésortingque de Frobenius, il existe une formule directe qui correspond à la notion habituelle de portance moyenne dans les statistiques circulaires. Voir «Moyens et moyenne dans le groupe des rotations», Maher Moakher, Revue SIAM sur l’parsing masortingcielle et ses applications, volume 24, numéro 1, 2002, pour plus de détails.
http://link.aip.org/link/?SJMAEL/24/1/1
Voici une fonction pour GNU Octave 3.2.4 qui fait le calcul:
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 Masortingx 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=xz; 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(ab) < pi/2 for some value b % % % na=3, a=sort(mod(randn(1,na)+1,2)-1)*pi; % da=diff([aa(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, robjohnson@alphawaveresearch.com % 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/'.
Je voudrais partager une méthode que j’ai utilisée avec un microcontrôleur qui n’avait pas de capacités en virgule flottante ou en sortinggonomésortinge. J’avais encore besoin de “faire la moyenne” de 10 lectures de roulements bruts afin de lisser les variations.
Ce n’est pas idéal ça peut casser Je m’en suis sorti dans ce cas car l’appareil ne tourne que très lentement. Je vais le mettre là au cas où quelqu’un d’autre se retrouverait à travailler sous des ressortingctions similaires.
Voici la solution complète: (l’entrée est un tableau de relèvement en degrés (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; }
J’irais de la manière vectorielle en utilisant des nombres complexes. Mon exemple est en Python, qui a des nombres complexes intégrés:
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)
Notez que Python n’a pas besoin de créer une nouvelle liste de vecteurs temporaire, tout ce qui précède peut être fait en une seule étape; Je viens de choisir cette façon d’approcher le pseudo-code applicable à d’autres langues.
Voici une solution complète en C ++:
#include #include double dAngleAvg(const vector& 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); }
Il prend les angles sous la forme d'un vecteur de doubles et renvoie la moyenne simplement sous la forme d'un double. Les angles doivent être en degrés et, bien sûr, la moyenne est également en degrés.
En python, avec des angles entre [-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)
Détails:
Pour la moyenne de deux angles, il y a deux moyennes séparées de 180 °, mais on peut vouloir la moyenne la plus proche.
Visuellement, la moyenne du bleu ( b ) et du vert ( a ) donne le sharepoint sarcelle:
Les angles «enveloppent» (par exemple 355 + 10 = 5), mais l’arithmétique standard ignorera ce sharepoint twigment. Cependant, si l’angle b est opposé au sharepoint twigment, alors ( b + g ) / 2 donne la moyenne la plus proche: le point sarcelle.
Pour deux angles, nous pouvons faire pivoter le problème afin que l’un des angles soit opposé au sharepoint twigment, effectuer une moyenne standard, puis faire pivoter vers l’arrière.
Voici une idée: construisez la moyenne itérativement en calculant toujours la moyenne des angles les plus rapprochés, en gardant un poids.
Une autre idée: trouver le plus grand écart entre les angles donnés. Recherchez le point qui le coupe en deux, puis sélectionnez le point opposé sur le cercle comme zéro de référence à partir duquel calculer la moyenne.
Représentons ces angles avec des points sur la circonférence du cercle.
Peut-on supposer que tous ces points tombent sur la même moitié du cercle? (Sinon, il n’y a pas de moyen évident de définir “l’angle moyen”. Pensez à deux points sur le diamètre, par exemple 0 ° et 180 °, la moyenne est-elle de 90 ° ou 270 °? répartir uniformément les points?)
Avec cette hypothèse, nous choisissons un point arbitraire sur ce demi-cercle comme “origine” et mesurons l’ensemble donné d’angles par rapport à cette origine (appelons cela “l’angle relatif”). Notez que l’angle relatif a une valeur absolue ssortingctement inférieure à 180 degrés. Enfin, prenez la moyenne de ces angles relatifs pour obtenir l’angle moyen souhaité (par rapport à notre origine bien sûr).
Il n’y a pas de “bonne réponse”. Je recommande la lecture du livre, KV Mardia et PE Jupp, “Directional Statistics”, (Wiley, 1999), pour une parsing approfondie.
Alnitak a la bonne solution. La solution de Nick Fortescue est fonctionnellement identique.
Pour le cas particulier de où
(sum (x_component) = 0.0 && sum (y_component) = 0.0) // par exemple 2 angles de 10. et 190. degrés ea.
utiliser 0.0 degré comme sum
Sur le plan informatique, vous devez tester ce cas car atan2 (0, 0.) est indéfini et générera une erreur.
L’angle moyen phi_avg devrait avoir la propriété que sum_i | phi_avg-phi_i | ^ 2 devienne minimal, où la différence doit être dans [-Pi, Pi) (parce qu’il pourrait être plus court d’aller dans l’autre sens!). Ceci est facilement réalisé en normalisant toutes les valeurs d’entrée à [0, 2Pi], en conservant une moyenne de phi_run courante et en choisissant la normalisation | phi_i-phi_run | à [-Pi, Pi) (en ajoutant ou en soustrayant 2Pi). La plupart des suggestions ci-dessus font autre chose qui n’a pas cette propriété minimale, c’est-à-dire qu’elles font une moyenne, mais pas des angles.
En anglais:
En python:
Un tableau d’angles #numpy NX1
if np.var(A) < np.var((A-180)%360): average = np.average(A) else: average = (np.average((A-180)%360)+180)%360
(Je veux juste partager mon sharepoint vue avec la théorie des estimations ou l’inférence statistique)
L’essai de Nimble consiste à obtenir l’estimation du MMSE ^ d’un ensemble d’angles, mais c’est l’un des choix pour trouver une direction “moyennée”; on peut également trouver une estimation MMAE ^, ou une autre estimation comme étant la direction “moyennée”, et cela dépend de votre erreur de quantification de la mésortingque; ou plus généralement dans la théorie des estimations, la définition de la fonction de coût.
^ MMSE / MMAE correspond à l’erreur quadratique / absolue moyenne minimale.
ackb a dit “L’angle moyen phi_avg devrait avoir la propriété que sum_i | phi_avg-phi_i | ^ 2 devienne minimal … ils font une moyenne, mais pas des angles”
—- vous quantifiez les erreurs au sens quadratique moyen et c’est l’une des manières les plus courantes, cependant, pas la seule. La réponse privilégiée par la plupart des gens ici (c’est-à-dire la sum des vecteurs unitaires et l’obtention de l’angle du résultat) est en fait l’une des solutions raisonnables. Il est (peut être prouvé) l’estimateur de ML qui sert de direction “moyenne” que nous voulons, si les directions des vecteurs sont modélisées comme la dissortingbution de von Mises. Cette dissortingbution n’est pas sophistiquée et n’est qu’une dissortingbution périodiquement échantillonnée à partir d’un Guassian 2D. Voir Eqn. (2.179) dans le livre de Bishop “Reconnaissance de formes et apprentissage automatique”. Encore une fois, ce n’est en aucun cas le seul moyen de représenter la direction «moyenne», mais il est tout à fait raisonnable d’avoir à la fois une bonne justification théorique et une mise en œuvre simple.
Nimble a déclaré que “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 contreparties vectorielles unitaires”
—-ce n’est pas vrai. Les “homologues de vecteur unitaire” révèlent les informations sur la direction d’un vecteur. L’angle est une quantité sans tenir compte de la longueur du vecteur, et le vecteur unitaire contient des informations supplémentaires sur la longueur 1. Vous pouvez définir votre vecteur “unité” comme étant de longueur 2, cela n’a pas vraiment d’importance.
J’ai résolu le problème à l’aide de la réponse de @David_Hanak. Comme il le dit:
L’angle qui pointe “entre” les deux autres en restant dans le même demi-cercle, par exemple pour 355 et 5, ce serait 0, pas 180. Pour ce faire, vous devez vérifier si la différence entre les deux angles est supérieure à 180 ou pas. Si c’est le cas, incrémentez le plus petit angle de 360 avant d’utiliser la formule ci-dessus.
Donc, ce que j’ai fait, c’est calculer la moyenne de tous les angles. Et puis, tous les angles inférieurs à celui-ci, augmentez-les de 360. Puis recalculez la moyenne en les ajoutant tous et en les divisant par leur longueur.
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;
Fonctionne parfaitement.
Fonction 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
Vous pouvez utiliser cette fonction dans 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
You can see a solution and a little explanation in the following link, for ANY programming language: https://rosettacode.org/wiki/Averages/Mean_angle
For instance, C++ solution :
#include #include 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; }
Sortie:
Mean Angle for 1st set : -0.000000 degrees Mean Angle for 2nd set : -90.000000 degrees Mean Angle for 3rd set : 20.000000 degrees
Or Matlab solution :
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
While starblue’s answer gives the angle of the average unit vector, it is possible to extend the concept of the arithmetic mean to angles if you accept that there may be more than one answer in the range of 0 to 2*pi (or 0° to 360°). For example, the average of 0° and 180° may be either 90° or 270°.
The arithmetic mean has the property of being the single value with the minimum sum of squared distances to the input values. The distance along the unit circle between two unit vectors can be easily calculated as the inverse cosine of their dot product. If we choose a unit vector by minimizing the sum of the squared inverse cosine of the dot product of our vector and each input unit vector then we have an equivalent average. Again, keep in mind that there may be two or more minimums in exceptional cases.
This concept could be extended to any number of dimensions, since the distance along the unit sphere can be calculated in the exact same way as the distance along the unit circle–the inverse cosine of the dot product of two unit vectors.
For circles we could solve for this average in a number of ways, but I propose the following O(n^2) algorithm (angles are in radians, and I avoid calculating the unit vectors):
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
If all the angles are within 180° of each other, then we could use a simpler O(n)+O(sort) algorithm (again using radians and avoiding use of unit vectors):
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
To use degrees, simply replace pi with 180. If you plan to use more dimensions then you will most likely have to use an iterative method to solve for the average.
Here is a completely arithmetic solution using moving averages and taking care to normalize values. It is fast and delivers correct answers if all angles are on one side of the circle (within 180° of each other).
It is mathimatically equivalent to adding the offset which shifts the values into the range (0, 180), calulating the mean and then subtracting the offset.
The comments describe what range a specific value can take on at any given time
// 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= 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; }
Based on Alnitak’s answer , I’ve written a Java method for calculating the average of multiple angles:
If your angles are in radians:
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); }
If your angles are in degrees:
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)); }
The problem is extremely simple. 1. Make sure all angles are between -180 and 180 degrees. 2. a Add all non-negative angles, take their average, and COUNT how many 2. b.Add all negative angles, take their average and COUNT how many. 3. Take the difference of pos_average minus neg_average If difference is greater than 180 then change difference to 360 minus difference. Otherwise just change the sign of difference. Note that difference is always non-negative. The Average_Angle equals the pos_average plus difference times the “weight”, negative count divided by the sum of negative and positive count
Here is some java code to average angles, I think it’s reasonably robust.
public static double getAverageAngle(List 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; }
I have a different method than @Starblue that gives “correct” answers to some of the angles given above. Par exemple:
It uses a sum over the differences between consecutive angles. The code (in Matlab):
function [avg] = angle_avg(angles) last = angles(1); sum = angles(1); for i=2:length(angles) diff = mod(angles(i)-angles(i-1)+ 180,360)-180 last = last + diff; sum = sum + last; end avg = mod(sum/length(angles), 360); end