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

147

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 entorno. 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.

Nick Fortescue
fuente
1
Por ángulo promedio, supongo que realmente quieres una demora media. Existe un ángulo entre dos líneas, un rumbo es la dirección de una sola línea. En este caso, Starblue tiene razón.
SmacL
@Nick Fortescue: ¿puedes actualizar tu pregunta para que sea más específica: quieres decir ángulos o un rumbo?
Mitch Wheat
1
En realidad, quería algo un poco más complicado (pero es análogo a los rodamientos) y estaba tratando de simplificar para hacer la pregunta más fácil, y como de costumbre lo hizo más complicado. Encontré la respuesta que quería en catless.ncl.ac.uk/Risks/7.44.html#subj4 . Reeditaré el qn.
Nick Fortescue
Los riesgos respuesta es básicamente lo que estoy proponiendo, excepto que puede ejecutar en problemas cuando el denominador es 0.
starblue
Artículo interesante sobre el significado de los ángulos: twistedoakstudios.com/blog/?p=938
starblue

Respuestas:

99

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

starblue
fuente
8
Eso no funciona si los vectores se cancelan entre sí. El promedio aún podría ser significativo en este caso, dependiendo de su definición exacta.
David Hanak
21
@David, la dirección promedio de dos rodamientos a 180 grados está indefinida. Esto no hace que la respuesta de starblue sea incorrecta, es solo un caso excepcional, como ocurre en muchos problemas geomtericos.
SmacL
55
@smacl: Estoy de acuerdo, si los ángulos representan direcciones. Pero si piensa en números complejos, por ejemplo, y define el promedio como "cuál es el argumento de c, tal que c c == a b", donde a y b tienen un módulo de 1, entonces el promedio de 0 y 180 es 90.
David Hanak
55
@PierreBdR: Si doy dos pasos en la dirección 0deg y uno en la dirección 90deg, me habré movido en la dirección 26.56 grados con respecto a donde comencé. En este sentido, 26.56 tiene mucho más sentido como la dirección promedio de {0,0,90} grados que 30 grados. El promedio algebraico es solo uno de los muchos promedios posibles (ver en.wikipedia.org/wiki/Mean ), y parece bastante irrelevante con el propósito de promediar direcciones (tal como lo hace para muchos otros).
Janus
60

Esta pregunta se examina en detalle en el libro: "Estadísticas sobre esferas", Geoffrey S. Watson, Notas de la Conferencia de la Universidad de Arkansas en 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 manera de estimar un ángulo promedio, A, a partir de un conjunto de mediciones 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 fraccionarias.

Nick Fortescue
fuente
8
que también es muy similar al algoritmo que publiqué al mismo tiempo que usted. Usted tendría que utilizar atan2 en lugar de un atan llano, sin embargo, pues de lo contrario no se puede saber qué cuadrante está en la respuesta.
Alnitak
Todavía puedes terminar con algunas respuestas indeterminadas. Como en la muestra 0, 180. Por lo tanto, aún debe verificar los casos límite. Además, generalmente hay una función atan2 disponible que podría ser más rápida en su caso.
Loki el
50

Veo el problema: por ejemplo, si tiene un ángulo de 45 'y un ángulo de 315', el promedio "natural" sería 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 sume esos 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 el sentido de las agujas del reloj, 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.

Alnitak
fuente
13
Su biblioteca de matemáticas probablemente usa radianes para los ángulos. Recuerda convertir.
Martin Beckett el
2
Tal vez sea demasiado tarde en la noche, pero usando esta lógica, obtengo un ángulo promedio de 341.8947 ... en lugar de 342 para ángulos de [320, 330, 340, 350, 10,]. Alguien ve mi error?
Alex Robinson
1
@AlexRobinson no es un error tipográfico, es porque el ángulo final es simplemente el ángulo eventual obtenido al tomar un conjunto de pasos de cada uno de esos ángulos individualmente.
Alnitak
1
@AlexRobinson, para ser más específicos: cos(), sin()y atan2()dan aproximaciones (buenos, pero todavía fuera por 1 o 2 ULP) así que cuanto más promedio, el más errores que incluyen.
Matthieu
23

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 caras.

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 en sentido horario 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 también se invierten correctamente)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359.5
  • 180, 180 -> 180
Darron
fuente
Esto podría optimizarse aún más mediante el uso de BAMS: stackoverflow.com/questions/1048945/…
darron
No está mal. La primera línea calcula el ángulo relativo de a con respecto a b en el rango [-180, 179], la segunda calcula el ángulo medio a partir de eso. Usaría b + diff / 2 en lugar de a - diff / 2 para mayor claridad.
starblue el
1
¿Me estoy perdiendo de algo? tengo 295.
darron
Ah .. lo entiendo. El operador mod de Matlab ajusta -10 a 350. Cambiaré el código. Es un simple 360 ​​adicional.
darron
Otra buena característica de este método es que es fácil implementar un promedio ponderado de los dos ángulos. En la segunda línea, multiplique diff por el peso del primer ángulo y reemplace el 2 en el denominador con la suma de los pesos. ángulo = (360 + b + (PESO [a] * diff / (PESO [a] + PESO [b]))) mod 360
oosterwal
14

ackb tiene razón en que estas soluciones basadas en vectores no pueden considerarse promedios reales de ángulos, son solo un promedio de las contrapartidas de vectores unitarios. Sin embargo, la solución sugerida de ackb no parece ser matemáticamente sólida.

La siguiente es una solución matemáticamente derivada 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 qué casos la diferencia entre ángulos es diferente a la diferencia entre sus contrapartes numéricas normales. Considere los ángulos x e y, si y> = x - 180 e y <= 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 consecuencia, si no se cumple la segunda condición, 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 ángulo

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: suma (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 curvas con nuestras diferencias ajustadas:

b = subconjunto de a donde la diferencia (angular) correcta a [i] -xc = subconjunto de a donde la diferencia (angular) correcta (a [i] -360) -x cn = tamaño de cd = subconjunto de a donde diferencia (angular) correcta (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, aunque funciona para valores normales, que tienen un conjunto ilimitado, por lo que el resultado definitivamente estará 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, entonces el mínimo del segmento está en 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 con una ordenación no basada en la comparación, como la ordenación 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 coincidir con su 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 piensa de inmediato es probablemente 180, sin embargo, es bien sabido que la media aritmética se ve muy afectada por los valores de borde. También debe recordar que los ángulos no son vectores, a pesar de lo atractivo que puede parecer a veces cuando se trata de ángulos.

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 enfatizar que a pesar de que este es un promedio real de ángulos, a diferencia de las soluciones vectoriales, 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 debería estar usando

Ágil
fuente
El método Mitsuta en realidad da el ángulo inicial + el promedio de las rotaciones desde el ángulo inicial. Por lo tanto, para obtener un método similar, teniendo en cuenta el error de medición, entonces debería observar las rotaciones que ocurren y estimar el error para esas. Creo que necesitaría una distribución para las rotaciones para estimar un error para ellas.
Nimble
6

Tienes que definir el promedio con mayor precisión. 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, 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 se puede generalizar la segunda alternativa para el caso de más de dos ángulos.

David Hanak
fuente
Si bien la pregunta se refiere a los ángulos, se considera mejor como dirección media y es un problema de navegación común.
SmacL
Buenos puntos, David. Por ejemplo, ¿cuál es el promedio de un ángulo de 180º y un ángulo de 540º? ¿Es 360º o 180º?
Baltimark
3
@ Baltimark, supongo que depende de lo que estés haciendo. Si es navegación, probablemente la última. Si es un elegante salto de snowboard, quizás el primero;)
SmacL
¿Entonces el promedio "verdadero" de 1 y 359 es (360/2)% 360 = 180 ?? Yo creo que no.
Muere en Sente
1
@Die in Sente: numéricamente hablando, definitivamente. Por ejemplo, si los ángulos representan giros, no direcciones, entonces el promedio de 359 y 1 seguramente es 180. Todo es cuestión de interpretación.
David Hanak
4

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 al cuadrado 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 habitual de demora promedio en estadísticas circulares. Consulte "Medios y promedios en el grupo de rotaciones", Maher Moakher, SIAM Journal on 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/'.
Rob Johnson
fuente
4

Me gustaría compartir un método que utilicé con un microcontrolador que no tenía capacidades de trigonometría o coma flotante. Todavía necesitaba "promediar" 10 lecturas de rodamiento sin procesar para suavizar las variaciones.

  1. Compruebe si el primer rumbo es el rango 270-360 o 0-90 grados (dos cuadrantes del norte)
  2. Si es así, gire esta y todas las lecturas posteriores en 180 grados, manteniendo todos los valores en el rango 0 <= teniendo <360. De lo contrario, tome las lecturas a medida que vienen.
  3. Una vez que se hayan tomado 10 lecturas, calcule el promedio numérico suponiendo que no haya habido un ajuste global
  4. Si la rotación de 180 grados hubiera estado vigente, gire el promedio calculado 180 grados para volver a un rodamiento "verdadero".

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

thombles
fuente
3

En inglés:

  1. Haga 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
Jason
fuente
Esta es una excelente manera de lograr el resultado final sin funciones trigonométricas, es simple y fácil de implementar.
Ian Mercer
esto funciona para cualquier rango de datos circulares; simplemente cambie a la mitad el rango circular; ¡gran respuesta!
Capitán Fantastic
3

Aquí está la solución completa: (la entrada es una matriz de demora 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;
}
DuduArbel
fuente
Este problema me ha desconcertado por un tiempo, su solución funciona (usando Arduino, por lo que algunos cambios en su código pero nada), estoy mostrando la lectura de la brújula y tomando lecturas cada 50 ms y almacenando en una matriz de lectura de 16 x, que luego uso en su función anterior, problema de envoltura 0-360 resuelto! gracias :)
Andología
3

En pitón, 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 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 ángulos se ajustan (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 cualquiera de los dos ángulos, podemos rotar el problema para que uno de los ángulos esté opuesto al punto de ramificación, realizar un promedio estándar y luego girar hacia atrás.

rotadoregresado

Brad Saund
fuente
2

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

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; Acabo de elegir esta forma de aproximar el pseudocódigo aplicable a otros idiomas también.

tzot
fuente
2

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 está en grados.

adam10603
fuente
avgCoses el promedio de los componentes x, y avgSines el promedio de los componentes y. Los parámetros para la función arcotangente son atan2( y, x ). Entonces, ¿no debería su código ser: atan2( avgSin, avgCos ) ??
Mike Finch
Obtuve este algoritmo de alguna parte, no se me ocurrió, así que supongo que es correcto de la forma en que está. Además, también da resultados correctos.
adam10603
2

Basado en la respuesta de Alnitak , he escrito un método Java para calcular el promedio de ángulos múltiples:

Si sus ángulos están en radianes:

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

Si tus ángulos están en grados:

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));
}
Stevoisiak
fuente
1

He aquí una idea: construya el promedio de forma iterativa calculando siempre el promedio de los ángulos más cercanos entre sí, manteniendo un peso.

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

John con gofres
fuente
No recomiendo mi respuesta, sino la respuesta altamente clasificada de starblue. La observación clave es pensar en el centro de la brújula como el punto 0,0.
John con gofres
1

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

¿Podemos suponer que todos estos puntos caen en la misma mitad del círculo? (De lo contrario, no hay una forma 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? puntos distribuidos uniformemente?)

Con esta suposición, elegimos un punto arbitrario en ese semicírculo como el "origen", y medimos el conjunto de ángulos dado con respecto a este origen (llamemos 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 (en relación con nuestro origen, por supuesto).

Zach Scrivena
fuente
1

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

cffk
fuente
1

(Solo quiero compartir mi punto de vista desde la teoría de la estimación o la 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 para ser la dirección "promediada", y depende de su error de cuantificación métrico de dirección; o más generalmente en la teoría de la estimación, la definición de función de costo.

^ MMSE / MMAE corresponde a la media mínima al cuadrado / error absoluto.

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"

---- cuantifica los errores en sentido 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 las personas aquí (es decir, la suma de los vectores unitarios y obtener el ángulo del resultado) es en realidad una de las soluciones razonables. Es (se puede probar) el estimador de ML que sirve como la dirección "promedio" 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 ec. (2.179) en el libro de Bishop "Reconocimiento de patrones y aprendizaje automático". Nuevamente, de ninguna manera es la única 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 que "ackb tiene razón en que estas soluciones basadas en vectores no pueden considerarse promedios reales de ángulos, son solo un promedio de las contrapartidas de vectores unitarios"

----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 tener en cuenta la longitud del vector, y el vector unitario es algo con información adicional de que la longitud es 1. Puede definir que su vector "unidad" sea de longitud 2, en realidad no importa.

mundo acuático
fuente
1

Aquí hay una solución completamente aritmética que usa promedios móviles y se encarga de normalizar los valores. Es rápido y ofrece respuestas correctas si todos los ángulos están en un lado del círculo (dentro de 180 ° entre sí).

Es matemáticamente equivalente a sumar el desplazamiento que desplaza los valores al rango (0, 180), calculando la media y luego restando el desplazamiento.

Los comentarios describen qué rango puede tomar un valor específico en un momento dado

// 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;
}
bgp2000
fuente
1

Bueno, llego muy tarde a la fiesta, pero pensé en agregar mis 2 centavos de dólar, ya que realmente no pude encontrar ninguna respuesta definitiva. Al final, implementé la siguiente versión Java del método Mitsuta que, espero, proporcione una solución simple y robusta. Particularmente porque la desviación estándar proporciona una dispersión de medida y, si sd == 90, indica que los ángulos de entrada dan como resultado una media ambigua.

EDITAR: En realidad, me di cuenta de que mi implementación original se puede simplificar aún más, de hecho, es preocupantemente simple teniendo en cuenta toda la conversación y la trigonometría en las otras respuestas.

/**
 * The Mitsuta method
 *
 * @param angles Angles from 0 - 360
 * @return double array containing
 * 0 - mean
 * 1 - sd: a measure of angular dispersion, in the range [0..360], similar to standard deviation.
 * Note if sd == 90 then the mean can also be its inverse, i.e. 360 == 0, 300 == 60.
 */
public static double[] getAngleStatsMitsuta(double... angles) {
    double sum = 0;
    double sumsq = 0;
    for (double angle : angles) {
        if (angle >= 180) {
            angle -= 360;
        }
        sum += angle;
        sumsq += angle * angle;
    }

    double mean = sum / angles.length;
    return new double[]{mean <= 0 ? 360 + mean: mean, Math.sqrt(sumsq / angles.length - (mean * mean))};
}

... y para todos los geeks (Java) que hay, puede usar el enfoque anterior para obtener el ángulo medio en una línea.

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;
neilireson
fuente
Creo que te perdiste algo del método Mitsuda. Por favor, eche un vistazo a la respuesta publicada por Lior Kogan stackoverflow.com/a/1828222/9265852
kykzk46
0

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.

usar 0.0 grados como la suma

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

jeffD
fuente
en glibc 'atan2' se define para (0, 0) - el resultado es 0
Alnitak
0

El ángulo promedio phi_avg debería tener la propiedad de que sum_i | phi_avg-phi_i | ^ 2 se vuelve mínimo, donde la diferencia debe estar en [-Pi, Pi) (¡porque podría ser más corto al revés!). Esto se logra fácilmente normalizando todos los valores de entrada a [0, 2Pi), manteniendo un promedio de ejecución phi_run 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.

ackb
fuente
0

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, 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 sean menores que este, increméntelos en 360. Luego, vuelva a calcular el promedio al sumarlos todos y dividirlos 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.

konsnos
fuente
0

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
E.Rooijen
fuente
0

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 
Martin006
fuente
El algoritmo parece funcionar, pero en realidad podría fallar miserablemente en el mundo real. Dándole valores de ángulo que están en la dirección opuesta de los ángulos dados.
tothphu
0

Puede ver una solución y una pequeña explicación en el siguiente enlace, para CUALQUIER lenguaje de programación: https://rosettacode.org/wiki/Averages/Mean_angle

Por ejemplo, la solución C ++ :

#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

O la solución de Matlab :

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
Gines Hidalgo
fuente
0

Si bien la respuesta de starblue da el ángulo del vector unitario promedio, es posible extender el concepto de la media aritmética a los ángulos si acepta que puede haber más de una respuesta en el rango de 0 a 2 * pi (o 0 ° a 360 °). Por ejemplo, el promedio de 0 ° y 180 ° puede ser 90 ° o 270 °.

La media aritmética tiene la propiedad de ser el valor único con la suma mínima de distancias cuadradas a los valores de entrada. La distancia a lo largo del círculo unitario entre dos vectores unitarios se puede calcular fácilmente como el coseno inverso de su producto escalar. Si elegimos un vector unitario minimizando la suma del coseno inverso cuadrado del producto punto de nuestro vector y cada vector unitario de entrada, entonces tenemos un promedio equivalente. Nuevamente, tenga en cuenta que puede haber dos o más mínimos en casos excepcionales.

Este concepto podría extenderse a cualquier cantidad de dimensiones, ya que la distancia a lo largo de la esfera unitaria se puede calcular exactamente de la misma manera que la distancia a lo largo del círculo unitario: el coseno inverso del producto escalar de dos vectores unitarios.

Para los círculos podríamos resolver este promedio de varias maneras, pero propongo el siguiente algoritmo O (n ^ 2) (los ángulos están en radianes, y evito calcular los vectores unitarios):

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

Si todos los ángulos están dentro de 180 ° entre sí, entonces podríamos usar un algoritmo más simple de O (n) + O (ordenar) (nuevamente usando radianes y evitando el uso de vectores unitarios):

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

Para usar grados, simplemente reemplace pi con 180. Si planea usar más dimensiones, lo más probable es que tenga que usar un método iterativo para resolver el promedio.

John Thoits
fuente
0

El problema es extremadamente simple. 1. Asegúrese de que todos los ángulos estén entre -180 y 180 grados. 2. a Suma todos los ángulos no negativos, toma su promedio y CUENTA cuántos 2. b. Agrega todos los ángulos negativos, toma su promedio y CUENTA cuántos. 3. Tome la diferencia de pos_average menos neg_average Si la diferencia es mayor que 180, cambie la diferencia a 360 menos la diferencia. De lo contrario, simplemente cambie el signo de la diferencia. Tenga en cuenta que la diferencia siempre es no negativa. Average_Angle es igual al pos_average más la diferencia multiplicada por el "peso", recuento negativo dividido por la suma del recuento negativo y positivo

DynamicChart
fuente
0

Aquí hay un código de Java para ángulos promedio, creo que es razonablemente robusto.

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;
}
Robert Sutton
fuente
-3

Tengo un método diferente a @Starblue que da respuestas "correctas" a algunos de los ángulos dados anteriormente. 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
Barak Schiller
fuente
1
Su código devuelve diferentes respuestas para [-90,90,40]y [90,-90,40]; No creo que un promedio no conmutativo sea muy útil.
musiphil