web-dev-qa-db-fra.com

Comment calculez-vous la moyenne d'un ensemble de données circulaires?

Je veux calculer la moyenne d'un ensemble de données circulaires. Par exemple, je pourrais avoir plusieurs échantillons de la lecture d’une boussole. Bien entendu, le problème est de savoir comment traiter le problème. Le même algorithme pourrait être utile pour un cadran d'horloge.

La question réelle est plus compliquée: que signifient les statistiques sur une sphère ou dans un espace algébrique qui «enveloppe», par exemple. le groupe additif mod n. La réponse peut ne pas être unique, par exemple. la moyenne de 359 degrés et 1 degré pourrait être de 0 à 180 degrés, mais statistiquement, le résultat est meilleur.

C'est un vrai problème de programmation pour moi et j'essaie de faire en sorte que ça ne ressemble pas à un problème de maths.

130
Nick Fortescue

Calculez les vecteurs unitaires à partir des angles et prenez l'angle de leur moyenne.

84
starblue

Cette question est examinée en détail dans le livre: "Statistics On Spheres", Geoffrey S. Watson, Conférence de l'Université de l'Arkansas Notes sur les sciences mathématiques, 1983 John Wiley & Sons, Inc., mentionné à http://catless.ncl.ac.uk/Risks/7.44.html#subj4 par Bruce Karsh.

Un bon moyen d’estimer un angle moyen, A, à partir d’un ensemble de mesures d’angle A [i] 0 <= i

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

La méthode donnée par starblue est équivalente sur le plan informatique, mais ses raisons sont plus claires et probablement plus efficaces du point de vue du programme, et fonctionnent également bien dans le cas zéro, donc félicitons-le.

Le sujet est maintenant exploré plus en détail sur Wikipedia , et avec d’autres utilisations, comme des fractions.

52
Nick Fortescue

Je vois le problème - par exemple, si vous avez un angle de 45 'et un angle de 315', la moyenne "naturelle" serait de 180 ', mais la valeur que vous souhaitez est en fait de 0'.

Je pense que Starblue est sur quelque chose. Il suffit de calculer les coordonnées cartésiennes (x, y) pour chaque angle et d’ajouter les vecteurs obtenus. Le décalage angulaire du vecteur final devrait être votre résultat souhaité.

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

J'ignore pour l'instant qu'un cap compas commence au nord et va dans le sens des aiguilles d'une montre, alors que les coordonnées cartésiennes «normales» commencent par zéro le long de l'axe des X, puis vont dans le sens contraire des aiguilles d'une montre. Les maths devraient fonctionner de la même manière peu importe.

47
Alnitak

POUR LE CAS PARTICULIER DE DEUX ANGLES:

La réponse ((a + b) mod 360)/2 estFAUX. Pour les angles 350 et 2, le point le plus proche est 356 et non 176.

Le vecteur unitaire et les solutions trig peuvent être trop coûteux.

Ce que j'ai d'un peu de bricolage, c'est:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90 (deux réponses pour ceci: cette équation prend la réponse dans le sens des aiguilles d'une montre de a)
  • 180, 0 -> 270 (voir ci-dessus)
  • 180, 1 -> 90,5
  • 1 180 -> 90,5
  • 20, 350 -> 5
  • 350, 20 -> 5 (tous les exemples suivants s'inversent également correctement)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359,5
  • 180, 180 -> 180
17
darron

ackb a raison de dire que ces solutions basées sur les vecteurs ne peuvent pas être considérées comme des moyennes vraies d’angles, elles ne sont qu’une moyenne des contreparties de vecteurs unitaires. Cependant, la solution suggérée par ackb ne semble pas mathématiquement valable.

Ce qui suit est une solution mathématiquement dérivée de l’objectif de minimisation (angle [i] - avgAngle) ^ 2 (où la différence est corrigée si nécessaire), ce qui en fait une véritable moyenne arithmétique des angles.

Premièrement, nous devons examiner exactement dans quels cas la différence entre les angles est différente de la différence entre leurs homologues normaux. Considérons les angles x et y, si y> = x - 180 et y <= x + 180, alors nous pouvons utiliser directement la différence (x-y). Sinon, si la première condition n'est pas remplie, nous devons utiliser (y + 360) dans le calcul au lieu de y. En conséquence, si la deuxième condition n'est pas remplie, nous devons utiliser (y-360) au lieu de y. Depuis l'équation de la courbe, nous minimisons uniquement les changements aux points où ces inégalités passent de true à faux ou vice versa, nous pouvons séparer la plage complète [0,360] en un ensemble de segments, séparés par ces points. Ensuite, il suffit de trouver le minimum de chacun de ces segments, puis le minimum du minimum de chaque segment, qui est la moyenne.

Voici une image montrant où les problèmes se posent lors du calcul des différences d’angle. Si x se situe dans la zone grise, il y aura un problème.

 Angle comparisons

Pour minimiser une variable, en fonction de la courbe, nous pouvons prendre la dérivée de ce que nous voulons minimiser, puis nous trouvons le point de retournement (c'est-à-dire où la dérivée = 0).

Ici, nous allons appliquer l’idée de minimiser la différence au carré pour obtenir la formule de la moyenne arithmétique commune: somme (a [i])/n. La courbe y = sum ((a [i] -x) ^ 2) peut être minimisée de cette façon:

y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2

dy\dx = -2*sum(a[i]) + 2*n*x

for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n

Maintenant, appliquez-le aux courbes avec nos différences ajustées:

b = sous-ensemble de a où la différence (angulaire) correcte a [i] -x c = sous-ensemble de a où la différence (angulaire) correcte (a [i] -360) -x cn = taille de c d = sous-ensemble de a où la différence (angulaire) correcte (a [i] +360) -x dn = taille de d

y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
  + sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
  + sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
  + sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
  + sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
  - 2*x*(360*dn - 360*cn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*sum(x[i])
  - 2*x*360*(dn - cn)
  + n*x^2

dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)

for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n

Cela seul ne suffit pas pour obtenir le minimum, alors que cela fonctionne pour les valeurs normales, qui a un ensemble illimité, de sorte que le résultat restera certainement dans la plage de l'ensemble et est donc valide. Nous avons besoin du minimum dans une plage (définie par le segment). Si le minimum est inférieur à la limite inférieure de notre segment, le minimum de ce segment doit se trouver à la limite inférieure (car les courbes quadratiques ont seulement 1 point de retournement) et si le minimum est supérieur à la limite supérieure de notre segment, le minimum du segment est au maximum limite supérieure. Une fois que nous avons le minimum pour chaque segment, nous trouvons simplement celui qui a la valeur la plus basse pour ce que nous minimisons (sum ((b [i] -x) ^ 2) + sum ((c [i] -360 ) -b) ^ 2) + somme (((d [i] +360) -c) ^ 2)).

Voici une image de la courbe, qui montre comment elle change aux points où x = (a [i] +180)% 360. L'ensemble de données en question est {65,92,230,320,250}.

 Curve

Voici une implémentation de l'algorithme en Java, incluant quelques optimisations, sa complexité est O (nlogn). Il peut être réduit à O(n) si vous remplacez le tri basé sur la comparaison par un tri non basé sur la comparaison, tel que le tri de base.

static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            + 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            - 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}

static double[] averageAngles(double[] _angles)
{
    double sumAngles;
    double sumSqrAngles;

    double[] lowerAngles;
    double[] upperAngles;

    {
        List<Double> lowerAngles_ = new LinkedList<Double>();
        List<Double> upperAngles_ = new LinkedList<Double>();

        sumAngles = 0;
        sumSqrAngles = 0;
        for(double angle : _angles)
        {
            sumAngles += angle;
            sumSqrAngles += angle*angle;
            if(angle < 180)
                lowerAngles_.add(angle);
            else if(angle > 180)
                upperAngles_.add(angle);
        }


        Collections.sort(lowerAngles_);
        Collections.sort(upperAngles_,Collections.reverseOrder());


        lowerAngles = new double[lowerAngles_.size()];
        Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
        for(int i = 0; i < lowerAngles_.size(); i++)
            lowerAngles[i] = lowerAnglesIter.next();

        upperAngles = new double[upperAngles_.size()];
        Iterator<Double> upperAnglesIter = upperAngles_.iterator();
        for(int i = 0; i < upperAngles_.size(); i++)
            upperAngles[i] = upperAnglesIter.next();
    }

    List<Double> averageAngles = new LinkedList<Double>();
    averageAngles.add(180d);
    double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);

    double lowerBound = 180;
    double sumLC = 0;
    for(int i = 0; i < lowerAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle > lowerAngles[i]+180)
            testAverageAngle = lowerAngles[i];

        if(testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        lowerBound = lowerAngles[i];
        sumLC += lowerAngles[i];
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
        //minimum is inside segment range
        //we will test average 0 (360) later
        if(testAverageAngle < 360 && testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double upperBound = 180;
    double sumUC = 0;
    for(int i = 0; i < upperAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle < upperAngles[i]-180)
            testAverageAngle = upperAngles[i];

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        upperBound = upperAngles[i];
        sumUC += upperBound;
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
        //minimum is inside segment range
        //we test average 0 (360) now           
        if(testAverageAngle < 0)
            testAverageAngle = 0;

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double[] averageAngles_ = new double[averageAngles.size()];
    Iterator<Double> averageAnglesIter = averageAngles.iterator();
    for(int i = 0; i < averageAngles_.length; i++)
        averageAngles_[i] = averageAnglesIter.next();


    return averageAngles_;
}

La moyenne arithmétique d'un ensemble d'angles peut ne pas correspondre à votre idée intuitive de ce que devrait être la moyenne. Par exemple, la moyenne arithmétique de l'ensemble {179,179,0,181,181} est 216 (et 144). La réponse à laquelle vous pensez immédiatement est probablement 180, mais il est bien connu que la moyenne arithmétique est fortement affectée par les valeurs de Edge. Vous devez également vous rappeler que les angles ne sont pas des vecteurs, aussi attrayant que cela puisse paraître lorsque vous traitez parfois avec des angles.

Bien entendu, cet algorithme s’applique également à toutes les quantités qui obéissent à l’arithmétique modulaire (avec un ajustement minimal), telles que l’heure de la journée.

Je voudrais également souligner que même s’il s’agit d’une moyenne réelle des angles, contrairement aux solutions vectorielles, cela ne signifie pas nécessairement que c’est la solution que vous devriez utiliser, la moyenne des vecteurs unitaires correspondants peut bien correspondre à la valeur réelle. devrait utiliser.

14
Nimble

Vous devez définir moyenne plus précisément. Dans le cas particulier de deux angles, je peux penser à deux scénarios différents:

  1. La "vraie" moyenne, c’est-à-dire (a + b)/2% 360.
  2. L'angle qui pointe "entre" les deux autres tout en restant dans le même demi-cercle, par ex. pour 355 et 5, il s'agira de 0, pas de 180. Pour ce faire, vous devez vérifier si la différence entre les deux angles est supérieure à 180 ou non. Si tel est le cas, incrémentez l'angle inférieur de 360 ​​avant d'utiliser la formule ci-dessus.

Je ne vois pas comment la deuxième alternative peut être généralisée pour le cas de plus de deux angles.

6
David Hanak

Comme pour toutes les moyennes, la réponse dépend du choix de la métrique. Pour une métrique donnée M, la moyenne de certains angles a_k dans [-pi, pi] pour k dans [1, N] est cet angle a_M qui minimise la somme des distances au carré d ^ 2_M (a_M, a_k). Pour une moyenne pondérée, on inclut simplement dans la somme les poids w_k (tels que sum_k w_k = 1). C'est,

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

Les choix de métrique courants sont les métriques de Frobenius et de Riemann. Pour la métrique de Frobenius, il existe une formule directe qui correspond à la notion habituelle de relèvement moyen dans les statistiques circulaires. Voir «Moyennes et moyennes dans le groupe de rotations», Maher Moakher, Journal SIAM sur l'analyse matricielle et les applications, volume 24, numéro 1, 2002, pour plus de détails.
http://link.aip.org/link/?SJMAEL/24/1/1

Voici une fonction pour GNU Octave 3.2.4 qui effectue le calcul:

function ma=meanangleoct(a,w,hp,ntype)
%   ma=meanangleoct(a,w,hp,ntype) returns the average of angles a
%   given weights w and half-period hp using norm type ntype
%   Ref: "Means and Averaging in the Group of Rotations",
%   Maher Moakher, SIAM Journal on 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/'.
4
Rob Johnson

Voici la solution complète: .__ (l'entrée est un tableau de relèvements en degrés (0-360) 

public static int getAvarageBearing(int[] arr)
{
    double sunSin = 0;
    double sunCos = 0;
    int counter = 0;

    for (double bearing : arr)
    {
        bearing *= Math.PI/180;

        sunSin += Math.sin(bearing);
        sunCos += Math.cos(bearing);
        counter++; 
    }

    int avBearing = INVALID_ANGLE_VALUE;
    if (counter > 0)
    {
        double bearingInRad = Math.atan2(sunSin/counter, sunCos/counter);
        avBearing = (int) (bearingInRad*180f/Math.PI);
        if (avBearing<0)
            avBearing += 360;
    }

    return avBearing;
}
3
DuduArbel

J'aimerais partager une méthode que j'ai utilisée avec un microcontrôleur sans capacités de virgule flottante ni de trigonométrie. Je devais encore "faire la moyenne" de 10 lectures de roulements bruts afin de lisser les variations.

  1. Vérifiez si le premier relèvement est compris entre 270 et 360 ou entre 0 et 90 degrés (deux quadrants nord)
  2. Si c'est le cas, tournez cette lecture et toutes les lectures suivantes de 180 degrés, en conservant toutes les valeurs dans la plage 0 <= relèvement <360. Sinon, prenez les lectures telles qu'elles sont.
  3. Une fois que 10 lectures ont été effectuées, calculez la moyenne numérique en supposant qu’il n’ya pas eu de remise à zéro.
  4. Si la rotation de 180 degrés avait été effective, faites pivoter la moyenne calculée de 180 degrés pour revenir à un "vrai" relèvement.

Ce n'est pas idéal. ça peut casser. Dans ce cas, je m'en suis sorti parce que l'appareil ne tourne que très lentement. Je vais le dire au cas où quelqu'un d'autre se retrouverait sous des restrictions similaires.

3
Tom

J'irais de manière vectorielle en utilisant des nombres complexes. Mon exemple est en Python, qui contient des nombres complexes intégrés:

import cmath # complex math

def average_angle(list_of_angles):

    # make a new list of vectors
    vectors= [cmath.rect(1, angle) # length 1 for each vector
        for angle in list_of_angles]

    vector_sum= sum(vectors)

    # no need to average, we don't care for the modulus
    return cmath.phase(vector_sum)

Notez que Python ne besoin de construire une nouvelle liste temporaire de vecteurs, tout ce qui précède peut être fait en une seule étape; Je viens de choisir cette façon de se rapprocher du pseudo-code applicable à d'autres langues aussi.

2
tzot

En python, avec des angles compris entre [-180 et 180)

def add_angles(a, b):
  return (a + b + 180) % 360 - 180

def average_angles(a, b):
  return add_angles(a, add_angles(-a, b)/2)

Détails:

Pour la moyenne de deux angles, il existe deux moyennes moyennes distantes de 180 °, mais nous pouvons souhaiter la moyenne la plus proche. 

Visuellement, la moyenne des couleurs bleue (b) et verte (a) donne le point bleu sarcelle:

 Original

Les angles 'enveloppent' (par exemple 355 + 10 = 5), mais l'arithmétique standard ignorera ce point de branche . Toutefois, si l'angle b est opposé au point de branche, alors (b + g)/2 donne la moyenne la plus proche: le point bleu sarcelle.

Pour deux angles quelconques, nous pouvons faire pivoter le problème afin qu’un des angles soit opposé au point de ramification, effectuer une moyenne standard, puis effectuer une rotation arrière. 

 rotated  returned

2
Brad Saund

Voici une solution C++ complète:

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

Il prend les angles sous la forme d'un vecteur de doubles et renvoie la moyenne simplement sous la forme d'un double. Les angles doivent être en degrés et, bien sûr, la moyenne est également en degrés.

2
adam10603

Voici une idée: construire la moyenne de manière itérative en calculant toujours la moyenne des angles les plus proches, en gardant un poids. 

Une autre idée: trouver le plus grand écart entre les angles donnés. Recherchez le point qui le divise en deux, puis choisissez le point opposé du cercle comme zéro de référence pour calculer la moyenne. 

1
John with waffle

Représentons ces angles avec des points sur la circonférence du cercle.

Peut-on supposer que tous ces points tombent sur la même moitié du cercle? (Sinon, il n'y a pas de moyen évident de définir "l'angle moyen". Pensez à deux points sur le diamètre, par exemple 0 degrés et 180 degrés - est la moyenne de 90 degrés ou de 270 degrés? Que se passe-t-il quand nous avons 3 ou plus répartir uniformément les points?)

Avec cette hypothèse, nous choisissons un point arbitraire sur ce demi-cercle comme "origine" et mesurons l'ensemble des angles donnés par rapport à cette origine (appelons cela "l'angle relatif"). Notez que l'angle relatif a une valeur absolue strictement inférieure à 180 degrés. Enfin, prenez la moyenne de ces angles relatifs pour obtenir l’angle moyen souhaité (par rapport à notre origine bien sûr). 

1
Zach Scrivena

Il n'y a pas une seule "bonne réponse". Je recommande de lire le livre, K. V. Mardia et P. E. Jupp, "Directional Statistics", (Wiley, 1999), Pour une analyse approfondie.

1
cffk

Eh bien, je suis extrêmement en retard pour la fête, mais je pensais ajouter 2 centimes de dollars car je ne trouvais pas vraiment de réponse définitive. Finalement, j'ai implémenté la version Java suivante de la méthode Mitsuta qui, j'espère, fournit une solution simple et robuste. D'autant que l'écart type fournit à la fois une dispersion de mesure et, si sd == 90, indique que les angles d'entrée donnent une moyenne ambiguë. 

EDIT: En fait, je me suis rendu compte que ma mise en œuvre initiale peut encore être simplifiée, en fait d’une simplicité inquiétante compte tenu de toute la conversation et de la trigonométrie en cours dans les autres réponses. 

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

... et pour tous les geeks (Java), vous pouvez utiliser l'approche ci-dessus pour obtenir l'angle moyen sur une ligne.

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;
1
neilireson

Voici une solution complètement arithmétique utilisant des moyennes mobiles et prenant soin de normaliser les valeurs. Il est rapide et fournit des réponses correctes si tous les angles sont d'un côté du cercle (à 180 ° l'un de l'autre).

Cela équivaut mathématiquement à ajouter le décalage qui décale les valeurs dans la plage (0, 180), calcule la moyenne puis soustrait le décalage.

Les commentaires décrivent quelle plage une valeur spécifique peut prendre à un moment donné

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

Le problème est extrêmement simple . 1. Assurez-vous que tous les angles sont compris entre -180 et 180 degrés . 2. a Ajoutez tous les angles non négatifs, prenez leur moyenne et COMPTEZ le nombre de 2. b.Ajoute tous les angles négatifs, prend leur moyenne et COMPTE combien de . 3. Prendre la différence de pos_average moins neg_average Si la différence est supérieure à 180, changez la différence en 360 moins la différence. Sinon, changez simplement le signe de la différence. Notez que la différence est toujours non négative . L'angle Moyen_Angle est égal à la différence pos_average multipliée par le «poids», nombre négatif divisé par la somme des nombres négatif et positif

0
DynamicChart

D'après la réponse d'Alnitak , j'ai écrit une méthode Java pour calculer la moyenne de plusieurs angles:

Si vos angles sont en 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);
}

Si vos angles sont en degrés:

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

Vous pouvez voir une solution et une petite explication dans le lien suivant, pour TOUS les langages de programmation: https://rosettacode.org/wiki/Averages/Mean_angle

Par exemple, solution 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;
}

Sortie:

Mean Angle for 1st set : -0.000000 degrees
Mean Angle for 2nd set : -90.000000 degrees
Mean Angle for 3rd set : 20.000000 degrees

Ou solution 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
0
Gines Hidalgo

Fonction Python:

from math import sin,cos,atan2,pi
import numpy as np
def meanangle(angles,weights=0,setting='degrees'):
    '''computes the mean angle'''
    if weights==0:
         weights=np.ones(len(angles))
    sumsin=0
    sumcos=0
    if setting=='degrees':
        angles=np.array(angles)*pi/180
    for i in range(len(angles)):
        sumsin+=weights[i]/sum(weights)*sin(angles[i])
        sumcos+=weights[i]/sum(weights)*cos(angles[i])
    average=atan2(sumsin,sumcos)
    if setting=='degrees':
        average=average*180/pi
    return average
0
E.Rooijen

En anglais:

  1. Créez un deuxième jeu de données avec tous les angles décalés de 180.
  2. Prendre la variance des deux ensembles de données.
  3. Prendre la moyenne de l'ensemble de données avec la plus petite variance.
  4. Si cette moyenne provient de l'ensemble décalé, décalez de nouveau la réponse de 180.

En python:

Un tableau d'angles #numpy NX1

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

else:
    average = (np.average((A-180)%360)+180)%360
0
Jason

Alors que la réponse de starblue donne l'angle du vecteur unitaire moyen, il est possible d'étendre le concept de moyenne arithmétique aux angles si vous acceptez qu'il peut y avoir plus d'une réponse dans l'intervalle 0 à 2 * pi (ou 0 ° à 360 °). Par exemple, la moyenne de 0 ° et 180 ° peut être de 90 ° ou 270 °.

La moyenne arithmétique a la propriété d'être la valeur unique avec la somme minimale des distances au carré par rapport aux valeurs d'entrée. La distance le long du cercle unitaire entre deux vecteurs unitaires peut être facilement calculée en tant que cosinus inverse de leur produit scalaire. Si nous choisissons un vecteur unitaire en minimisant la somme du cosinus inverse carré du produit scalaire de notre vecteur et de chaque vecteur unitaire en entrée, nous obtenons une moyenne équivalente. Encore une fois, gardez à l'esprit qu'il peut y avoir deux minimums ou plus dans des cas exceptionnels.

Ce concept pourrait être étendu à un nombre quelconque de dimensions, car la distance le long de la sphère unitaire peut être calculée exactement de la même manière que la distance le long du cercle unitaire - le cosinus inverse du produit scalaire de deux vecteurs unitaires.

Pour les cercles, nous pourrions résoudre cette moyenne de plusieurs façons, mais je propose l’algorithme O (n ^ 2) suivant (les angles sont exprimés en radians et j’évite de calculer les vecteurs unitaires):

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 tous les angles sont à moins de 180 ° l'un de l'autre, alors nous pourrions utiliser un algorithme plus simple O (n) + O (sort) (utilisant à nouveau des radians et évitant d'utiliser des vecteurs unitaires):

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

Pour utiliser des degrés, remplacez simplement pi par 180. Si vous envisagez d'utiliser plus de dimensions, vous devrez probablement utiliser une méthode itérative pour résoudre la moyenne.

0
John Thoits

Voici un peu de code Java aux angles moyens, je pense qu’il est raisonnablement robuste.

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

J'ai résolu le problème avec l'aide de la réponse de @David_Hanak . Comme il le dit:

L'angle qui pointe "entre" les deux autres tout en restant dans le même demi-cercle, par ex. pour 355 et 5, il s'agira de 0, pas de 180. Pour ce faire, vous devez vérifier si la différence entre les deux angles est supérieure à 180 ou non. Si tel est le cas, incrémentez l'angle inférieur de 360 ​​avant d'utiliser la formule ci-dessus.

J'ai donc calculé la moyenne de tous les angles. Et puis tous les angles inférieurs, augmentez-les de 360. Recalculez ensuite la moyenne en les additionnant tous et en les divisant par leur longueur.

        float angleY = 0f;
        int count = eulerAngles.Count;

        for (byte i = 0; i < count; i++)
            angleY += eulerAngles[i].y;

        float averageAngle = angleY / count;

        angleY = 0f;
        for (byte i = 0; i < count; i++)
        {
            float angle = eulerAngles[i].y;
            if (angle < averageAngle)
                angle += 360f;
            angleY += angle;
        }

        angleY = angleY / count;

Marche parfaitement.

0
konsnos

L'angle moyen phi_avg devrait avoir la propriété que sum_i | phi_avg-phi_i | ^ 2 devient minimal, la différence devant être en [-Pi, Pi) (car il pourrait être plus court pour faire l'inverse!). Ceci est facilement réalisé en normalisant toutes les valeurs en entrée à [0, 2Pi), en maintenant une moyenne courante de phi_run et en choisissant la normalisation | phi_i-phi_run | à [-Pi, Pi) (en ajoutant ou en soustrayant 2Pi). La plupart des suggestions ci-dessus font quelque chose d'autre qui n'est pas A cette propriété minimale, c'est-à-dire qu'elles ont une moyenne de quelque chose, mais pas des angles.

0
ackb

(Je veux juste partager mon point de vue de la théorie de l'estimation ou de l'inférence statistique)

L'essai de Nimble est d'obtenir l'estimation par le MMSE d'un ensemble d'angles, mais l'un des choix possibles est de trouver une direction "moyennée"; on peut également trouver une estimation MMAE ^, ou une autre estimation, comme étant la direction "moyennée", et cela dépend de votre métrique d'erreur de quantification de direction; ou plus généralement dans la théorie de l'estimation, la définition de la fonction de coût.

^ MMSE/MMAE correspond à l'erreur quadratique moyenne/absolue minimale.

ackb a dit "L'angle moyen phi_avg devrait avoir la propriété que sum_i | phi_avg-phi_i | ^ 2 devient minimale ... ils font la moyenne quelque chose, mais pas des angles"

---- vous quantifiez les erreurs au sens moyen du carré et c'est l'une des méthodes les plus courantes, mais pas la seule. La réponse privilégiée par la plupart des gens ici (c’est-à-dire la somme des vecteurs unitaires et l’angle du résultat) est en fait l’une des solutions raisonnables. C'est (peut être prouvé) l'estimateur de ML qui sert de direction "moyennée" que nous voulons, si les directions des vecteurs sont modélisées comme une distribution de von Mises. Cette distribution n’est pas fantaisiste et n’est qu’une distribution échantillonnée périodiquement à partir d’un Guassian 2D. Voir Eqn. (2.179) dans le livre de Bishop "Pattern Recognition and Machine Learning". Encore une fois, ce n’est certainement pas le seul meilleur moyen de représenter une direction "moyenne". Cependant, il est tout à fait raisonnable de choisir une direction qui repose à la fois sur une bonne justification théorique et sur une mise en œuvre simple.

Nimble a déclaré: "Ackb a raison de dire que ces solutions basées sur les vecteurs ne peuvent pas être considérées comme de vraies moyennes des angles, elles ne sont qu'une moyenne des contreparties de vecteurs unitaires"

----ce n'est pas vrai. Les "contreparties de vecteurs unitaires" révèlent les informations relatives à la direction d'un vecteur. L'angle est une quantité sans tenir compte de la longueur du vecteur, et le vecteur unitaire est quelque chose avec des informations supplémentaires telles que la longueur est 1. Vous pouvez définir votre vecteur "unité" comme étant de longueur 2, cela n'a pas d'importance.

0
waterworld

Alnitak a la bonne solution. La solution de Nick Fortescue est fonctionnellement la même. 

Pour le cas particulier où

(sum (x_component) = 0.0 && sum (y_component) = 0.0) // ex. 2 angles de 10. et 190. degrés ea.

utiliser 0,0 degrés comme la somme

En calcul, vous devez tester ce cas car atan2 (0., 0.) n'est pas défini et générera une erreur.

0
jeffD

Vous pouvez utiliser cette fonction dans Matlab:

function retVal=DegreeAngleMean(x) 

len=length(x);

sum1=0; 
sum2=0; 

count1=0;
count2=0; 

for i=1:len 
   if x(i)<180 
       sum1=sum1+x(i); 
       count1=count1+1; 
   else 
       sum2=sum2+x(i); 
       count2=count2+1; 
   end 
end 

if (count1>0) 
     k1=sum1/count1; 
end 

if (count2>0) 
     k2=sum2/count2; 
end 

if count1>0 && count2>0 
   if(k2-k1 >= 180) 
       retVal = ((sum1+sum2)-count2*360)/len; 
   else 
       retVal = (sum1+sum2)/len; 
   end 
elseif count1>0 
    retVal = k1; 
else 
    retVal = k2; 
end 
0
Martin006