una sacar promedio grafico grafica como calcular barras algorithm math geometry

algorithm - sacar - como calcular la media en un grafico de barras



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

(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 ángulos, 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 ángulos"

---- 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 suma 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 ángulos, 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.

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 programación real para mí y estoy tratando de que no se vea como un problema matemático.


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 ángulos de 10. y 190. grados ea.

use 0.0 grados como la suma

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


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; }


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

Otra idea: encontrar el espacio más grande entre los ángulos 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.


Aquí hay una solución completa de C ++:

#include <vector> #include <cmath> double dAngleAvg(const vector<double>& 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 ángulos en forma de un vector de dobles, y devuelve el promedio simplemente como un doble. Los ángulos deben estar en grados, y por supuesto el promedio también es en grados.


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


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 ángulos a_k en [-pi, pi] para k en [1, N] es ese ángulo a_M que minimiza la suma de las distancias cuadradas d ^ 2_M (a_M, a_k). Para una media ponderada, uno simplemente incluye en la suma 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=x-z; 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(a-b) < pi/2 for some value b % % % na=3, a=sort(mod(randn(1,na)+1,2)-1)*pi; % da=diff([a a(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, [email protected] % 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/''.


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] (sumando 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 ángulos.


En inglés:

  1. Cree un segundo conjunto de datos con todos los ángulos 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 ángulos #numpy NX1

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


En python, con ángulos 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 ángulos, 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:

Los ángulos 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 ángulos, podemos rotar el problema de modo que uno de los ángulos sea opuesto al punto de ramificación, realice un promedio estándar y luego gire hacia atrás.


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 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 ángulos 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.


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


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.


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


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


Representemos estos ángulos 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 ángulos 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 ángulos relativos para obtener el ángulo promedio deseado (relativo a nuestro origen, por supuesto).


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 ángulos 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 ángulos. Y luego, todos los ángulos 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.


Tienes que definir el promedio más exactamente. Para el caso específico de dos ángulos, 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 ángulos 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 ángulos.


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.


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.


ackb tiene razón en que estas soluciones basadas en vectores no pueden considerarse promedios reales de ángulos, 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 objetivo 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 ángulos.

Primero, necesitamos ver exactamente en qué casos la diferencia entre ángulos es diferente a la diferencia entre sus contrapartes de números normales. Considere los ángulos 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.

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 = suma ((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 (suma ((b [i] -x) ^ 2) + suma (((c [i] -360 ) -b) ^ 2) + suma (((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}.

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<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 media aritmética de un conjunto de ángulos 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 ángulos no son vectores, tan atractivos como eso puede parecer al tratar con ángulos 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 ángulos, 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.


PARA EL CASO ESPECIAL DE DOS ÁNGULOS:

La respuesta ((a + b) mod 360) / 2 es INCORRECTA . Para los ángulos 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

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)); }


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<n; i++) { // average: (0, 360) double diff = angles[i]-average; // diff: (-540, 540) if (diff < -180) diff += 360; else if (diff >= 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; }


Here is some java code to average angles, I think it''s reasonably robust.

public static double getAverageAngle(List<Double> 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; }


Tengo un método diferente de @Starblue que da respuestas "correctas" a algunos de los ángulos que se indican arriba. Por ejemplo:

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

Utiliza una suma sobre las diferencias entre ángulos consecutivos. El código (en 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


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


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.


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<math.h> #include<stdio.h> 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