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

147

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. Le problème, bien sûr, est de savoir comment gérer le bouclage. Le même algorithme peut être utile pour une horloge.

La vraie question est plus compliquée - que signifient les statistiques sur une sphère ou dans un espace algébrique qui "s'enroule", par exemple le groupe additif mod n. La réponse n'est peut-être pas unique, par exemple, la moyenne de 359 degrés et 1 degré pourrait être de 0 degrés ou 180, mais statistiquement 0 semble mieux.

C'est un vrai problème de programmation pour moi et j'essaie de faire en sorte que cela ne ressemble pas à un problème mathématique.

Nick Fortescue
la source
1
Par angle moyen, je suppose que vous voulez vraiment un relèvement moyen. Un angle existe entre deux lignes, un relèvement est la direction d'une seule ligne. Dans ce cas, starblue a raison.
SmacL
@Nick Fortescue: pouvez-vous mettre à jour votre question pour être plus précise: voulez-vous dire des angles ou un relèvement?
Mitch Wheat
1
En fait, je voulais quelque chose d'un peu plus compliqué (mais qui ressemble aux roulements) et j'essayais de simplifier pour rendre la question plus facile, et comme d'habitude, je l'ai rendue plus compliquée. J'ai trouvé la réponse que je voulais sur catless.ncl.ac.uk/Risks/7.44.html#subj4 . Je rééditerai le qn.
Nick Fortescue le
La réponse aux risques est essentiellement ce que je propose, sauf que cela peut rencontrer des problèmes lorsque le dénominateur est 0.
starblue
Article intéressant sur la signification des angles: twistedoakstudios.com/blog/?p=938
starblue

Réponses:

99

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

starblue
la source
8
Cela ne fonctionne pas si les vecteurs s'annulent. La moyenne pourrait encore être significative dans ce cas, en fonction de sa définition exacte.
David Hanak le
21
@David, la direction moyenne de deux roulements à 180 degrés vers l'extérieur n'est pas définie. Cela ne rend pas la réponse de starblue fausse, c'est juste un cas exceptionnel, comme cela se produit dans de nombreux problèmes géomériques.
SmacL
5
@smacl: Je suis d'accord, si les angles représentent des directions. Mais si vous pensez aux nombres complexes, par exemple, et définissez la moyenne comme "quel est l'argument de c, tel que c c == a b", où a et b ont un module de 1, alors la moyenne de 0 et 180 est 90.
David Hanak
5
@PierreBdR: Si je fais deux pas dans la direction 0deg et un dans la direction 90deg, je me serai déplacé dans la direction 26,56 degrés par rapport à l'endroit où j'ai commencé. En ce sens, 26,56 a beaucoup plus de sens que la direction moyenne de {0,0,90} deg à 30 deg. La moyenne algébrique n'est qu'une des nombreuses moyennes possibles (voir en.wikipedia.org/wiki/Mean ) - et elle semble tout à fait hors de propos pour calculer la moyenne des directions (comme c'est le cas pour beaucoup d'autres).
Janus
60

Cette question est examinée en détail dans le livre: "Statistics On Spheres", Geoffrey S. Watson, University of Arkansas Lecture Notes in the Mathematical Sciences, 1983 John Wiley & Sons, Inc. comme mentionné sur http: //catless.ncl. ac.uk/Risks/7.44.html#subj4 par Bruce Karsh.

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

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

La méthode donnée par starblue est équivalente en calcul, mais ses raisons sont plus claires et probablement plus efficaces par programme, et fonctionnent également bien dans le cas zéro, alors bravo à lui.

Le sujet est maintenant exploré plus en détail sur Wikipédia , et avec d'autres utilisations, comme les parties fractionnaires.

Nick Fortescue
la source
8
qui est également à peu près le même que l'algorithme que j'ai publié en même temps que vous. Vous auriez besoin d'utiliser atan2 plutôt qu'un atan ordinaire, car sinon vous ne pouvez pas dire dans quel quadrant se trouve la réponse.
Alnitak
Vous pouvez toujours vous retrouver avec des réponses indéterminées. Comme dans l'échantillon 0, 180. Vous devez donc toujours vérifier les cas extrêmes. En outre, il existe généralement une fonction atan2 disponible qui pourrait être plus rapide dans votre cas.
Loki le
50

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

Je pense que Starblue est sur quelque chose. Calculez simplement les coordonnées cartésiennes (x, y) pour chaque angle et ajoutez les vecteurs résultants ensemble. Le décalage angulaire du vecteur final doit être le résultat souhaité.

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

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

Alnitak
la source
13
Votre bibliothèque mathématique utilise probablement des radians pour les angles. N'oubliez pas de convertir.
Martin Beckett
2
Peut-être qu'il est trop tard la nuit, mais en utilisant cette logique, j'obtiens un angle moyen de 341,8947 ... au lieu de 342 pour des angles de [320, 330, 340, 350, 10,]. Quelqu'un voit ma faute de frappe?
Alex Robinson
1
@AlexRobinson ce n'est pas une faute de frappe, c'est parce que l'angle final est simplement l'angle éventuel obtenu en prenant un ensemble de pas de chacun de ces angles individuellement.
Alnitak
1
@AlexRobinson, pour être plus précis: cos(), sin()et atan2()donner des approximations (bons, mais toujours au large par 1 ou 2 ulps) donc plus vous en moyenne, plus les erreurs que vous comprennent.
Matthieu
23

POUR LE CAS PARTICULIER DES DEUX ANGLES:

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

Le vecteur unitaire et les solutions trigonométriques peuvent être trop chers.

Ce que j'ai obtenu 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
Darron
la source
Cela pourrait encore être optimisé par l'utilisation de BAMS: stackoverflow.com/questions/1048945/…
darron
Pas mal. La première ligne calcule l'angle relatif de a par rapport à b dans la plage [-180, 179], la seconde calcule l'angle médian à partir de cela. J'utiliserais b + diff / 2 au lieu de a - diff / 2 pour plus de clarté.
starblue
1
Est-ce que je manque quelque chose? Je FAIS obtenir 295.
Darron
Ah ... je comprends. L'opérateur de mod de Matlab encapsule -10 à 350. Je vais changer le code. C'est un simple 360 ​​supplémentaire.
darron
Une autre caractéristique intéressante de cette méthode est qu'il est facile d'implémenter une moyenne pondérée des deux angles. Dans la deuxième ligne, multipliez diff par le poids du premier angle et remplacez le 2 du dénominateur par la somme des poids. angle = (360 + b + (POIDS [a] * diff / (POIDS [a] + POIDS [b]))) mod 360
oosterwal
14

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

Ce qui suit est une solution dérivée mathématiquement 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.

Tout d'abord, nous devons regarder exactement dans quels cas la différence entre les angles est différente de la différence entre leurs homologues en nombre normal. Considérons les angles x et y, si y> = x - 180 et y <= x + 180, alors nous pouvons utiliser la différence (xy) directement. Sinon, si la première condition n'est pas remplie, 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. Puisque l'équation de la courbe que nous minimisons les changements seulement aux points où ces inégalités changent de vrai à faux ou vice versa, nous pouvons séparer la gamme 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 produisent dans le calcul des différences d'angle. Si x se trouve dans la zone grise, il y aura un problème.

Comparaisons d'angle

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 (qui est où la dérivée = 0).

Ici, nous appliquerons l'idée de minimiser la différence au carré pour dériver la formule de moyenne arithmétique commune: sum (a [i]) / n. La courbe y = sum ((a [i] -x) ^ 2) peut être minimisée de cette manière:

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

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

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

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

b = sous-ensemble de a où la différence (angulaire) correcte a [i] -xc = sous-ensemble de a où la différence (angulaire) correcte (a [i] -360) -x cn = taille de cd = sous-ensemble de a où le 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 n'est pas tout à fait suffisant pour obtenir le minimum, alors que cela fonctionne pour des valeurs normales, qui ont un ensemble illimité, donc le résultat se situera 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 borne inférieure de notre segment, alors le minimum de ce segment doit être à la borne inférieure (car les courbes quadratiques n'ont qu'un seul point de retournement) et si le minimum est supérieur à la borne supérieure de notre segment, alors le minimum du segment est au 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}.

Courbe

Voici une implémentation de l'algorithme en Java, avec 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 par 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 bord. Vous devez également vous rappeler que les angles ne sont pas des vecteurs, aussi attrayants que cela puisse paraître lorsqu'il s'agit parfois d'angles.

Cet algorithme s'applique bien entendu également à toutes les grandeurs qui obéissent à l'arithmétique modulaire (avec un ajustement minimal), comme l'heure de la journée.

Je voudrais également souligner que même s'il s'agit d'une vraie moyenne d'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 être la valeur que vous devrait utiliser.

Agile
la source
La méthode Mitsuta donne en fait l'angle de départ + la moyenne des rotations à partir de l'angle de départ. Donc, pour obtenir une méthode similaire, en tenant compte de l'erreur de mesure, vous devez examiner les rotations en cours et estimer l'erreur pour celles-ci. Je pense que vous auriez besoin d'une distribution pour les rotations afin d'estimer une erreur pour elles.
Agile le
6

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

  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 exemple pour 355 et 5, ce serait 0, pas 180. Pour ce faire, il faut vérifier si la différence entre les deux angles est supérieure à 180 ou pas. Si tel est le cas, augmentez l'angle le plus petit 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, cependant.

David Hanak
la source
Bien que la question se réfère aux angles, elle est mieux perçue comme une direction moyenne et constitue un problème de navigation courant.
SmacL
Bons points, David. Par exemple, quelle est la moyenne d'un angle de 180 ° et d'un angle de 540 °? Est-ce 360 ​​° ou 180 °?
Baltimark le
3
@Baltimark, je suppose que cela dépend de ce que vous faites. Si sa navigation, probablement cette dernière. Si c'est un saut de snowboard sophistiqué, peut-être le premier;)
SmacL
Donc, la "vraie" moyenne de 1 et 359 est (360/2)% 360 = 180 ?? Je crois que non.
Die in Sente le
1
@Die in Sente: numériquement, définitivement. Par exemple, si les angles représentent des virages, pas des directions, alors la moyenne de 359 et 1 est sûrement 180. Tout est une question d'interprétation.
David Hanak
4

Comme toutes les moyennes, la réponse dépend du choix de la métrique. Pour une métrique M donnée, la moyenne de quelques angles a_k dans [-pi, pi] pour k dans [1, N] est cet angle a_M qui minimise la somme des carrés des distances 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 somme_k w_k d ^ 2_M (x, a_k)

Deux choix courants de métrique 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 en statistique circulaire. Voir «Moyennes et moyennes dans le groupe de rotations», Maher Moakher, SIAM Journal on Matrix Analysis and 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/'.
Rob Johnson
la source
4

J'aimerais partager une méthode que j'ai utilisée avec un microcontrôleur qui n'avait pas de capacités de virgule flottante ou de trigonométrie. J'avais encore besoin de "faire la moyenne" de 10 lectures brutes de roulement afin de lisser les variations.

  1. Vérifiez si le premier relèvement est dans la plage 270-360 ou 0-90 degrés (deux quadrants nord)
  2. Si tel est le cas, faites pivoter cette lecture et toutes les lectures suivantes de 180 degrés, en gardant toutes les valeurs dans la plage 0 <= relèvement <360. Sinon, prenez les lectures au fur et à mesure.
  3. Une fois que 10 lectures ont été prises, calculez la moyenne numérique en supposant qu'il n'y a pas eu de bouclage
  4. Si la rotation de 180 degrés avait été en vigueur, faites pivoter la moyenne calculée de 180 degrés pour revenir à un «vrai» cap.

Ce n'est pas idéal; ça peut casser. Je m'en suis tiré dans ce cas car l'appareil ne tourne que très lentement. Je vais le mettre là-bas au cas où quelqu'un d'autre se retrouverait à travailler sous des restrictions similaires.

coups de poing
la source
3

En anglais:

  1. Créez un deuxième jeu de données avec tous les angles décalés de 180.
  2. Prenez la variance des deux ensembles de données.
  3. Prenez 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 à 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
Jason
la source
C'est un excellent moyen d'obtenir le résultat final sans fonctions trigonométriques, c'est simple et facile à mettre en œuvre.
Ian Mercer
cela fonctionne pour toute gamme de données circulaires; décale simplement de moitié la plage circulaire; très bonne réponse!
Captain Fantastic
3

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

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

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

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

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

    return avBearing;
}
DuduArbel
la source
Ce problème m'a dérouté pendant un certain temps, votre solution fonctionne (en utilisant Arduino, donc quelques changements dans votre code mais rien de grand), je montre la lecture de la boussole et prend des lectures toutes les 50 ms et les stocke dans un tableau de lecture 16 x, que j'utilise ensuite dans votre fonction ci-dessus, problème de 0-360 wrap around résolu! merci :)
Andology
3

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

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

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

Détails:

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

Visuellement, la moyenne du bleu ( b ) et du vert ( a ) donne le point sarcelle:

Original

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

Pour deux angles quelconques, nous pouvons faire pivoter le problème afin que l'un des angles soit opposé au point de branchement, effectuer un moyennage standard, puis effectuer une rotation arrière.

tournérevenu

Brad Saund
la source
2

J'irais la voie vectorielle en utilisant des nombres complexes. Mon exemple est en Python, qui a des nombres complexes intégrés:

import cmath # complex math

def average_angle(list_of_angles):

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

    vector_sum= sum(vectors)

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

Notez que Python n'a pas besoin de créer une nouvelle liste temporaire de vecteurs, tout ce qui précède peut être fait en une seule étape; J'ai juste choisi cette façon d'approximer le pseudo-code applicable à d'autres langues également.

tzot
la source
2

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 comme un double. Les angles doivent être en degrés, et bien sûr la moyenne est également en degrés.

adam10603
la source
avgCosest la moyenne des x composantes et avgSinest la moyenne des y composantes. Les paramètres de la fonction arc tangente sont atan2( y, x ). Donc, votre code ne devrait-il pas être: atan2( avgSin, avgCos ) ??
Mike Finch
J'ai eu cet algorithme quelque part, je ne l'ai pas inventé moi-même, donc je suppose qu'il est correct tel qu'il est. De plus, cela donne également des résultats corrects.
adam10603
2

Sur la base de 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));
}
Stevoisiak
la source
1

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.

Autre idée: trouver le plus grand écart entre les angles donnés. Trouvez le point qui le coupe en deux, puis choisissez le point opposé sur le cercle comme zéro de référence pour calculer la moyenne.

John avec gaufre
la source
Je ne recommande pas ma réponse, mais plutôt la réponse hautement classée de starblue. L'observation clé ici est de considérer le centre de la boussole comme le point 0,0.
John avec gaufre le
1

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é et 180 degrés --- est-ce que la moyenne est de 90 degrés ou 270 degrés? Que se passe-t-il lorsque 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 donné d'angles 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).

Zach Scrivena
la source
1

Il n'y a pas une seule «bonne réponse». Je recommande de lire le livre, KV Mardia et PE Jupp, "Directional Statistics", (Wiley, 1999), pour une analyse approfondie.

cffk
la source
1

(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 MMSE ^ d'un ensemble d'angles, mais c'est l'un des choix pour trouver une direction "moyennée"; on peut également trouver une estimation MMAE ^, ou une autre estimation comme étant la direction "moyennée", et cela dépend de votre erreur de quantification métrique 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 / absolue moyenne minimale.

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

---- vous quantifiez les erreurs au sens carré moyen et c'est l'un des moyens les plus courants, mais pas le seul. La réponse favorisée par la plupart des gens ici (c'est-à-dire la somme des vecteurs unitaires et obtenir l'angle du résultat) est en fait l'une des solutions raisonnables. C'est (peut être prouvé) l'estimateur 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 périodiquement échantillonnée à partir d'un Guassien 2D. Voir Eqn. (2.179) dans le livre de Bishop's "Pattern Recognition and Machine Learning". Encore une fois, ce n'est en aucun cas le seul meilleur pour représenter la direction «moyenne», cependant, il est tout à fait raisonnable qui a à la fois une bonne justification théorique et une mise en œuvre simple.

Nimble a déclaré que "ackb a raison de dire que ces solutions vectorielles ne peuvent pas être considérées comme de vraies moyennes d'angles, elles ne sont qu'une moyenne des homologues des vecteurs unitaires"

----ce n'est pas vrai. Les "homologues de vecteur unitaire" révèlent les informations de 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 que la longueur est 1. Vous pouvez définir votre vecteur "unité" comme étant de longueur 2, cela n'a pas vraiment d'importance.

monde de l'eau
la source
1

Voici une solution entièrement 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 (à moins de 180 ° les uns des autres).

C'est mathématiquement équivalent à ajouter le décalage qui décale les valeurs dans la plage (0, 180), en calculant la moyenne puis en soustrayant le décalage.

Les commentaires décrivent la plage qu'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;
}
bgp2000
la source
1

Eh bien, je suis extrêmement en retard à la fête, mais j'ai pensé ajouter mes 2 cents car je ne pouvais pas vraiment trouver de réponse définitive. Au final, j'ai implémenté la version Java suivante de la méthode Mitsuta qui, je l'espère, fournit une solution simple et robuste. D'autant plus 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, j'ai réalisé que ma mise en œuvre originale peut être encore plus 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;
Neilireson
la source
Je crois que vous avez manqué quelque chose de la méthode Mitsuda. Veuillez consulter la réponse publiée par Lior Kogan stackoverflow.com/a/1828222/9265852
kykzk46 le
0

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) // par exemple 2 angles de 10. et 190. degrés ea.

utiliser 0,0 degré comme somme

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

JeffD
la source
sur la glibc 'atan2' est défini pour (0, 0) - le résultat est 0
Alnitak
0

L'angle moyen phi_avg devrait avoir la propriété que sum_i | phi_avg-phi_i | ^ 2 devient minime, où la différence doit être dans [-Pi, Pi) (car il peut être plus court d'aller dans l'autre sens!). Ceci est facilement réalisé en normalisant toutes les valeurs d'entrée à [0, 2Pi), en conservant une moyenne courante de phi_run et en choisissant de normaliser | phi_i-phi_run | à [-Pi, Pi) (en ajoutant ou soustractine 2Pi). La plupart des suggestions ci-dessus font autre chose qui n'a pas cette propriété minimale, c'est-à-dire qu'elles font la moyenne de quelque chose , mais pas des angles.

ackb
la source
0

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

L'angle qui pointe "entre" les deux autres tout en restant dans le même demi-cercle, par exemple pour 355 et 5, ce serait 0, pas 180. Pour ce faire, il faut vérifier si la différence entre les deux angles est supérieure à 180 ou pas. Si tel est le cas, augmentez l'angle le plus petit 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 à cela, augmentez-les de 360. Puis recalculez 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;

Fonctionne parfaitement.

Konsnos
la source
0

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

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 
Martin006
la source
L'algorithme semble juste fonctionner, mais en réalité, il pourrait échouer lamentablement dans le monde réel. Vous donnant des valeurs d'angle qui sont dans la direction opposée des angles donnés.
tothphu
0

Vous pouvez voir une solution et une petite explication dans le lien suivant, pour TOUT langage 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;
}

Production:

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
Gines Hidalgo
la source
0

Alors que la réponse de starblue donne l'angle du vecteur unitaire moyen, il est possible d'étendre le concept de la moyenne arithmétique aux angles si vous acceptez qu'il peut y avoir plus d'une réponse dans la plage de 0 à 2 * pi (ou de 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 carrés des distances aux valeurs d'entrée. La distance le long du cercle unitaire entre deux vecteurs unitaires peut être facilement calculée comme le 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 d'entrée, nous avons 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 à n'importe quel nombre 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 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 ° les uns des autres, alors nous pourrions utiliser un algorithme O (n) + O (tri) plus simple (à nouveau en utilisant des radians et en é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 les degrés, remplacez simplement pi par 180. Si vous prévoyez d'utiliser plus de dimensions, vous devrez probablement utiliser une méthode itérative pour résoudre la moyenne.

John Thoits
la source
0

Le problème est extrêmement simple. 1. Assurez-vous que tous les angles sont compris entre -180 et 180 degrés. 2. a Additionnez tous les angles non négatifs, prenez leur moyenne et COMPTEZ combien 2. b.Ajoutez tous les angles négatifs, prenez leur moyenne et COMPTEZ combien. 3. Prenez 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. Le Average_Angle est égal à pos_average plus la différence multipliée par le "poids", le nombre négatif divisé par la somme des nombres négatifs et positifs

DynamicChart
la source
0

Voici du code java aux angles moyens, je pense que c'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;
}
Robert Sutton
la source
-3

J'ai une méthode différente de @Starblue qui donne des réponses «correctes» à certains des angles donnés ci-dessus. Par exemple:

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

Il utilise une somme sur les différences entre les angles consécutifs. Le code (dans 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
la source
1
Votre code renvoie des réponses différentes pour [-90,90,40]et [90,-90,40]; Je ne pense pas qu'une moyenne non commutative soit très utile.
musiphil