¿Cómo se calcula el promedio de un conjunto de datos circulares?

Quiero calcular el promedio de un conjunto de datos circulares. Por ejemplo, podría tener varias muestras de la lectura de una brújula. El problema, por supuesto, es cómo lidiar con el envolvente. El mismo algoritmo podría ser útil para una esfera de reloj.

La pregunta real es más complicada: qué significan las estadísticas en una esfera o en un espacio algebraico que se “envuelve”, por ejemplo, el grupo aditivo mod n. La respuesta puede no ser única, por ejemplo, el promedio de 359 grados y 1 grado podría ser 0 grados o 180, pero estadísticamente 0 se ve mejor.

Este es un problema de progtwigción real para mí y estoy tratando de que no se vea como un problema matemático.

Calcule los vectores unitarios desde los angularjs y tome el ángulo de su promedio.

Esta pregunta se examina en detalle en el libro: “Statistics On Spheres”, Geoffrey S. Watson, Notas de la Conferencia de la Universidad de Arkansas en las Ciencias Matemáticas, 1983 John Wiley & Sons, Inc. como se menciona en http: //catless.ncl. ac.uk/Risks/7.44.html#subj4 por Bruce Karsh.

Una buena forma de estimar un ángulo promedio, A, a partir de un conjunto de medidas de angularjs a [i] 0 <= i

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

El método dado por starblue es computacionalmente equivalente, pero sus razones son más claras y, probablemente, programáticamente más eficientes, y también funcionan bien en el caso cero, así que felicitaciones a él.

El tema ahora se explora con más detalle en Wikipedia , y con otros usos, como partes fraccionales.

Veo el problema, por ejemplo, si tiene un ángulo de 45 ‘y un ángulo de 315’, el promedio ‘natural’ sería de 180 ‘, pero el valor que desea es en realidad 0’.

Creo que Starblue está en algo. Simplemente calcule las coordenadas cartesianas (x, y) para cada ángulo y añada los vectores resultantes. El desplazamiento angular del vector final debe ser el resultado requerido.

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

Estoy ignorando por ahora que el rumbo de una brújula comienza en el norte, y va en sentido horario, mientras que las coordenadas cartesianas “normales” comienzan con cero a lo largo del eje X, y luego van en sentido antihorario. Las matemáticas deberían funcionar de la misma manera independientemente.

PARA EL CASO ESPECIAL DE DOS ÁNGULOS:

La respuesta ((a + b) mod 360) / 2 es INCORRECTA . Para los angularjs 350 y 2, el punto más cercano es 356, no 176.

El vector unitario y las soluciones trigonométricas pueden ser demasiado costosas.

Lo que tengo de un pequeño retoque es:

 diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180 angle = (360 + b + ( diff / 2 ) ) mod 360 
  • 0, 180 -> 90 (dos respuestas para esto: esta ecuación toma la respuesta a la derecha de a)
  • 180, 0 -> 270 (ver arriba)
  • 180, 1 -> 90.5
  • 1, 180 -> 90.5
  • 20, 350 -> 5
  • 350, 20 -> 5 (todos los ejemplos siguientes se invierten correctamente también)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359.5
  • 180, 180 -> 180

ackb tiene razón en que estas soluciones basadas en vectores no pueden considerarse promedios reales de angularjs, solo son un promedio de las contrapartidas del vector unitario. Sin embargo, la solución sugerida de ackb no parece sonar matemáticamente.

La siguiente es una solución que se deriva matemáticamente del objective de minimizar (ángulo [i] – avgAngle) ^ 2 (donde la diferencia se corrige si es necesario), lo que la convierte en una verdadera media aritmética de los angularjs.

Primero, necesitamos ver exactamente en qué casos la diferencia entre angularjs es diferente a la diferencia entre sus contrapartes de números normales. Considere los angularjs x y y, si y> = x – 180 ey <= x + 180, entonces podemos usar la diferencia (xy) directamente. De lo contrario, si no se cumple la primera condición, debemos usar (y + 360) en el cálculo en lugar de y. En correspondencia, si la segunda condición no se cumple, entonces debemos usar (y-360) en lugar de y. Dado que la ecuación de la curva estamos minimizando solo los cambios en los puntos donde estas desigualdades cambian de verdadero a falso o viceversa, podemos separar el rango completo [0,360] en un conjunto de segmentos, separados por estos puntos. Entonces, solo necesitamos encontrar el mínimo de cada uno de estos segmentos, y luego el mínimo del mínimo de cada segmento, que es el promedio.

Aquí hay una imagen que demuestra dónde ocurren los problemas al calcular las diferencias de ángulo. Si x se encuentra en el área gris, entonces habrá un problema.

Comparaciones de ángulos

Para minimizar una variable, dependiendo de la curva, podemos tomar la derivada de lo que queremos minimizar y luego encontramos el punto de inflexión (que es donde la derivada = 0).

Aquí aplicaremos la idea de minimizar la diferencia al cuadrado para derivar la fórmula de la media aritmética común: sum (a [i]) / n. La curva y = sum ((a [i] -x) ^ 2) se puede minimizar de esta manera:

 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 

Ahora aplicándolo a las curvas con nuestras diferencias ajustadas:

b = subconjunto de a donde la diferencia correcta (angular) a [i] -xc = subconjunto de a donde la diferencia correcta (angular) (a [i] -360) -x cn = tamaño de cd = subconjunto de a donde el diferencia correcta (angular) (a [i] +360) -x dn = tamaño 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 

Esto por sí solo no es suficiente para obtener el mínimo, mientras que funciona para valores normales, que tiene un conjunto ilimitado, por lo que el resultado definitivamente se encuentra dentro del rango del conjunto y, por lo tanto, es válido. Necesitamos el mínimo dentro de un rango (definido por el segmento). Si el mínimo es menor que el límite inferior de nuestro segmento, entonces el mínimo de ese segmento debe estar en el límite inferior (porque las curvas cuadráticas solo tienen 1 punto de inflexión) y si el mínimo es mayor que el límite superior de nuestro segmento, el mínimo del segmento es el límite superior. Después de tener el mínimo para cada segmento, simplemente encontramos el que tiene el valor más bajo para lo que estamos minimizando (sum ((b [i] -x) ^ 2) + sum (((c [i] -360 ) -b) ^ 2) + sum (((d [i] +360) -c) ^ 2)).

Aquí hay una imagen de la curva, que muestra cómo cambia en los puntos donde x = (a [i] +180)% 360. El conjunto de datos en cuestión es {65,92,230,320,250}.

Curva

Aquí hay una implementación del algoritmo en Java, que incluye algunas optimizaciones, su complejidad es O (nlogn). Se puede reducir a O (n) si reemplaza la ordenación basada en la comparación por una ordenación no basada en la comparación, como ordenar por 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 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 media aritmética de un conjunto de angularjs puede no estar de acuerdo con tu idea intuitiva de cuál debería ser el promedio. Por ejemplo, la media aritmética del conjunto {179,179,0,181,181} es 216 (y 144). La respuesta en la que piensas inmediatamente es probablemente de 180, sin embargo, es bien sabido que la media aritmética se ve muy afectada por los valores de los bordes. También debe recordar que los angularjs no son vectores, tan atractivos como eso puede parecer al tratar con angularjs a veces.

Por supuesto, este algoritmo también se aplica a todas las cantidades que obedecen a la aritmética modular (con un ajuste mínimo), como la hora del día.

También me gustaría hacer hincapié en que, aunque este es un verdadero promedio de angularjs, a diferencia de las soluciones de vectores, eso no significa necesariamente que sea la solución que debería usar, el promedio de los vectores unitarios correspondientes puede ser el valor que realmente tiene. debería estar usando.

Tienes que definir el promedio más exactamente. Para el caso específico de dos angularjs, puedo pensar en dos escenarios diferentes:

  1. El promedio “verdadero”, es decir (a + b) / 2% 360.
  2. El ángulo que señala “entre” los otros dos mientras permanece en el mismo semicírculo, por ejemplo, para 355 y 5, esto sería 0, no 180. Para hacer esto, debe verificar si la diferencia entre los dos angularjs es mayor que 180 o no. Si es así, incremente el ángulo más pequeño en 360 antes de usar la fórmula anterior.

Sin embargo, no veo cómo la segunda alternativa se puede generalizar para el caso de más de dos angularjs.

Como todos los promedios, la respuesta depende de la elección de la métrica. Para una métrica M dada, el promedio de algunos angularjs a_k en [-pi, pi] para k en [1, N] es ese ángulo a_M que minimiza la sum de las distancias cuadradas d ^ 2_M (a_M, a_k). Para una media ponderada, uno simplemente incluye en la sum los pesos w_k (tal que sum_k w_k = 1). Es decir,

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

Dos opciones comunes de métrica son las métricas de Frobenius y Riemann. Para la métrica de Frobenius, existe una fórmula directa que corresponde a la noción usual de promedio en las estadísticas circulares. Consulte “Medios y promedios en el grupo de rotaciones”, Maher Moakher, SIAM Journal en Matrix Analysis and Applications, Volumen 24, Número 1, 2002, para más detalles.
http://link.aip.org/link/?SJMAEL/24/1/1

Aquí hay una función para GNU Octave 3.2.4 que hace el cálculo:

 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=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/'. 

Me gustaría compartir un método que utilicé con un microcontrolador que no tenía capacidades de punto flotante o trigonometría. Todavía necesitaba “promediar” 10 lecturas de rodamientos crudos para suavizar las variaciones.

  1. Verifique si el primer rodamiento es el rango 270-360 o 0-90 grados (dos cuadrantes del norte)
  2. Si es así, gire esta y todas las lecturas subsecuentes 180 grados, manteniendo todos los valores en el rango 0 <= teniendo <360. De lo contrario, tome las lecturas tal como vienen.
  3. Una vez que se han tomado 10 lecturas, calcule el promedio numérico asumiendo que no ha habido envolvente
  4. Si la rotación de 180 grados hubiera estado en vigor, entonces gire el promedio calculado en 180 grados para volver a un rumbo “verdadero”.

No es ideal; puede romperse Me salí con la suya en este caso porque el dispositivo solo gira muy lentamente. Lo pondré allí en caso de que alguien más se encuentre trabajando bajo restricciones similares.

Aquí está la solución completa: (la entrada es una matriz de rumbo en grados (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; } 

Yo iría por el camino del vector usando números complejos. Mi ejemplo está en Python, que tiene números complejos integrados:

 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) 

Tenga en cuenta que Python no necesita construir una nueva lista temporal de vectores, todo lo anterior se puede hacer en un solo paso; Solo elegí esta forma para aproximar el pseudocódigo aplicable a otros idiomas también.

Aquí hay una solución completa de 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); } 

Toma los angularjs en forma de un vector de dobles, y devuelve el promedio simplemente como un doble. Los angularjs deben estar en grados, y por supuesto el promedio también es en grados.

En python, con angularjs 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) 

Detalles:

Para el promedio de dos angularjs, hay dos promedios separados por 180 °, pero es posible que deseemos el promedio más cercano.

Visualmente, el promedio del azul ( b ) y el verde ( a ) produce el punto verde azulado:

Original

Los angularjs se envuelven (por ejemplo, 355 + 10 = 5), pero la aritmética estándar ignorará este punto de ramificación. Sin embargo, si el ángulo b es opuesto al punto de ramificación, entonces ( b + g ) / 2 da el promedio más cercano: el punto verde azulado.

Para dos angularjs, podemos rotar el problema de modo que uno de los angularjs sea opuesto al punto de ramificación, realice un promedio estándar y luego gire hacia atrás.

rotado devuelto

Aquí hay una idea: construya el promedio iterativamente calculando siempre el promedio de los angularjs más cercanos entre sí, manteniendo un peso.

Otra idea: encontrar el espacio más grande entre los angularjs dados. Encuentre el punto que lo biseca, y luego elija el punto opuesto en el círculo como el cero de referencia para calcular el promedio.

Representemos estos angularjs con puntos en la circunferencia del círculo.

¿Podemos suponer que todos estos puntos recaen en la misma mitad del círculo? (De lo contrario, no hay una manera obvia de definir el “ángulo promedio”. Piense en dos puntos en el diámetro, por ejemplo, 0 grados y 180 grados — ¿es el promedio de 90 grados o 270 grados? ¿Qué sucede cuando tenemos 3 o más? distribuir los puntos por igual?)

Con esta suposición, escogemos un punto arbitrario en ese semicírculo como el “origen”, y medimos el conjunto de angularjs dados con respecto a este origen (llamamos a esto el “ángulo relativo”). Tenga en cuenta que el ángulo relativo tiene un valor absoluto estrictamente inferior a 180 grados. Finalmente, tome la media de estos angularjs relativos para obtener el ángulo promedio deseado (relativo a nuestro origen, por supuesto).

No hay una sola “respuesta correcta”. Recomiendo leer el libro, KV Mardia y PE Jupp, “Directional Statistics”, (Wiley, 1999), para un análisis exhaustivo.

Alnitak tiene la solución correcta. La solución de Nick Fortescue es funcionalmente la misma.

Para el caso especial de donde

(sum (x_component) = 0.0 && sum (y_component) = 0.0) // ej. 2 angularjs de 10. y 190. grados ea.

use 0.0 grados como la sum

Computacionalmente debe probar este caso ya que atan2 (0., 0.) no está definido y generará un error.

El ángulo promedio phi_avg debe tener la propiedad de que sum_i | phi_avg-phi_i | ^ 2 se vuelve mínima, donde la diferencia tiene que ser en [-Pi, Pi] (¡porque podría ser más corto para ir al revés!). Esto se logra fácilmente normalizando todos los valores de entrada a [0, 2Pi), manteniendo un phi_run promedio en ejecución y eligiendo normalizar | phi_i-phi_run | a [-Pi, Pi] (sumndo o restando 2Pi). La mayoría de las sugerencias anteriores hacen algo más que no tiene esa propiedad mínima, es decir, promedian algo , pero no angularjs.

En inglés:

  1. Cree un segundo conjunto de datos con todos los angularjs desplazados 180.
  2. Tome la varianza de ambos conjuntos de datos.
  3. Tome el promedio del conjunto de datos con la varianza más pequeña.
  4. Si este promedio es del conjunto desplazado, cambie la respuesta nuevamente por 180.

En Python:

Un conjunto de angularjs #numpy NX1

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

(Solo quiero compartir mi punto de vista desde Teoría de Estimación o Inferencia Estadística)

La prueba de Nimble es obtener la estimación MMSE de un conjunto de angularjs, pero es una de las opciones para encontrar una dirección “promediada”; también se puede encontrar una estimación MMAE ^, o alguna otra estimación como la dirección “promediada”, y depende de su error de dirección de cuantificación métrica; o más generalmente en la teoría de la estimación, la definición de la función de costos.

^ MMSE / MMAE corresponde al error cuadrático medio / absoluto mínimo.

ackb dijo “El ángulo promedio phi_avg debería tener la propiedad de que sum_i | phi_avg-phi_i | ^ 2 se vuelve mínimo … promedian algo, pero no angularjs”

—- usted cuantifica los errores en el sentido del cuadrado medio y es una de las formas más comunes, sin embargo, no es la única. La respuesta preferida por la mayoría de la gente aquí (es decir, la sum de los vectores unitarios y obtener el ángulo del resultado) es en realidad una de las soluciones razonables. Es (puede demostrarse) que el estimador de ML sirve como la dirección “promediada” que queremos, si las direcciones de los vectores se modelan como distribución de von Mises. Esta distribución no es elegante, y es solo una distribución muestreada periódicamente de un Guassian 2D. Ver Eqn. (2.179) en el libro de Bishop “Reconocimiento de patrones y aprendizaje automático”. Una vez más, de ninguna manera es el único mejor para representar la dirección “promedio”, sin embargo, es bastante razonable que tenga una buena justificación teórica y una implementación simple.

Nimble dijo “ackb tiene razón en que estas soluciones basadas en vectores no pueden considerarse promedios reales de angularjs, solo son un promedio de las contrapartidas del vector unitario”

—-esto no es verdad. Las “contrapartes del vector unitario” revelan la información de la dirección de un vector. El ángulo es una cantidad sin considerar la longitud del vector, y el vector unitario es algo con información adicional de que la longitud es 1. Puedes definir que tu vector “unidad” sea de longitud 2, realmente no importa.

Resolví el problema con la ayuda de la respuesta de @David_Hanak. Como él dice:

El ángulo que señala “entre” los otros dos mientras permanece en el mismo semicírculo, por ejemplo, para 355 y 5, esto sería 0, no 180. Para hacer esto, debe verificar si la diferencia entre los dos angularjs es mayor que 180 o no. Si es así, incremente el ángulo más pequeño en 360 antes de usar la fórmula anterior.

Entonces, lo que hice fue calcular el promedio de todos los angularjs. Y luego, todos los angularjs que son menores que esto, increméntelos en 360. Luego recalcule el promedio agregándolos a todos y dividiéndolos por su longitud.

  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; 

Funciona perfectamente.

Función de 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 

Puede usar esta función en 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; } 

Salida:

 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. Por ejemplo:

  • angle_avg([350,10])=0
  • angle_avg([-90,90,40])=13.333
  • angle_avg([350,2])=356

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