Averaging angles... Again
Asked Answered
I

13

22

I want to calculate the average of a set of angles, which represents source bearing (0 to 360 deg) - (similar to wind-direction)

I know it has been discussed before (several times). The accepted answer was Compute unit vectors from the angles and take the angle of their average.

However this answer defines the average in a non intuitive way. The average of 0, 0 and 90 will be atan( (sin(0)+sin(0)+sin(90)) / (cos(0)+cos(0)+cos(90)) ) = atan(1/2)= 26.56 deg

I would expect the average of 0, 0 and 90 to be 30 degrees.

So I think it is fair to ask the question again: How would you calculate the average, so such examples will give the intuitive expected answer.

Edit 2014:

After asking this question, I've posted an article on CodeProject which offers a thorough analysis. The article examines the following reference problems:

  • Given time-of-day [00:00-24:00) for each birth occurred in US in the year 2000 - Calculate the mean birth time-of-day
  • Given a multiset of direction measurements from a stationary transmitter to a stationary receiver, using a measurement technique with a wrapped normal distributed error – Estimate the direction.
  • Given a multiset of azimuth estimates between two points, made by “ordinary” humans (assuming to subject to a wrapped truncated normal distributed error) – Estimate the direction.
Intermediate answered 28/11, 2009 at 19:19 Comment(13)
what is wrong about just (0+0+90)/3 ?Photomontage
what would be nice is a system where average(0, 0, 90) = 30 and average(0, 200) ≠ 100Cullum
is there a fine line between average of numbers and average of angles? Where 0+0+90 numerically would have an average of 30, but degrees would expect to be different.Bumpkin
Question what is the average of (0, 180)? 90 or 270?Bluebeard
26.56 deg is correct ;-)Bluebeard
What does the average of several angles even mean - what physical or geometrical property does it represent? The problem with "add them up and divide by n" is that addition doesn't do quite the same thing in a cyclic group as it does on an infinite line. If you can answer the question what the average is for, that might give you ideas how you want to calculate it. For instance if you're calculating expected wear on a turret bearing then you want an "average of angles", but you do want that average the naive way even if some of those numbers are greater than 180 because it went the "wrong" way.Scientism
As @Steve Jessop syas the problem is underdefined. We can think of at least 5 subtopics (all give different answers): - unsigned angles without circular distribution (average field of view of telescope) - signed angles without circular distribution (deviation of compass needle from true North) - unsigned rotations where theta < theta+2*NPI. Total rotation of turret (@Steve) - direction doesn't matter - signed rotation where theta < theta+2*NPI. Rotations of screwhead - circular distribution (average distrib of wind direction). This requires the Wikipedia formulae.Toe
+1 Interesting question. I still have no intuition what to do about it, but note that 30 = arc sin(0.5).Book
"Let's say that we are measuring wind direction. Our 3 measurements were 0, 0, and 90 degrees. Since all measurements are equivalently reliable, why shouldn't our best estimate of the wind direction be 30 degrees? setting it to 25.56 degrees is a bias toward 0" And if your measurements were, 0, 180, 0 and 180: is 90 degrees sensible? No it isn't. The goal of the averaging depends on what you're trying to do. You're trying to estimate some underlying. Means work with Gaussians. On a circle you assume von Mises probability distributions.Grettagreuze
As soon as you mention wind, what comes to mind is that wind also has a velocity, so I want to average vectors.Pires
It's not biased towards zero. Clusters of angles have more weight than you would expect if you naively average the raw angles. You could play with some scaling function on the length of the vector to try counter that "bias".Book
See also: epa.gov/scram001/guidance/met/mmgrma.pdf ams.allenpress.com/archive/1520-0450/23/9/pdf/… In short: there is no perfect solution. Use succesive calulation if your data lie within 180deg and resort to approximate solution (like Yamartino method proposed here) if the spread is larger.Fourwheeler
The discussion has helped the OP to redefine the question. This now appears (@MaR) to be "...the average of a SEQUENCE of angles where each successive addition does not differ from the running mean by more than a specified amount." The answer from @MaR's reference may be satisfactory for that question (I'm not sure) but it is some way from the OPs title.Toe
I
11

Thank you all for helping me see my problem more clearly.

I found what I was looking for. It is called Mitsuta method.

The inputs and output are in the range [0..360).

This method is good for averaging data that was sampled using constant sampling intervals.

The method assumes that the difference between successive samples is less than 180 degrees (which means that if we won't sample fast enough, a 330 degrees change in the sampled signal would be incorrectly detected as a 30 degrees change in the other direction and will insert an error into the calculation). Nyquist–Shannon sampling theorem anybody ?

Here is a c++ code:

double AngAvrg(const vector<double>& Ang)
{
    vector<double>::const_iterator iter= Ang.begin();
    
    double fD   = *iter;
    double fSigD= *iter;

    while (++iter != Ang.end())
    {
        double fDelta= *iter - fD;

             if (fDelta < -180.) fD+= fDelta + 360.;
        else if (fDelta >  180.) fD+= fDelta - 360.;
        else                     fD+= fDelta       ;

        fSigD+= fD;
    }

    double fAvrg= fSigD / Ang.size();

    if (fAvrg >= 360.) return fAvrg -360.;
    if (fAvrg <  0.  ) return fAvrg +360.;
                       return fAvrg      ;
}

It is explained on page 51 of Meteorological Monitoring Guidance for Regulatory Modeling Applications (PDF)(171 pp, 02-01-2000, 454-R-99-005)

Thank you MaR for sending the link as a comment.

If the sampled data is constant, but our sampling device has an inaccuracy with a Von Mises distribution, a unit-vectors calculation will be appropriate.

Intermediate answered 1/12, 2009 at 19:11 Comment(2)
Also read this about Mitsuta method.Hardpan
The lines fD+= fDelta + 360, etc are somewhat redundant, and obfuscate the purpose of the fD variable, making it appear to be a running sum. The value of fDelta is directly related to the old value of fD, so you could just say fD= *iter + 360, etc.Byproduct
T
20

[Note the OP's question (but not title) appears to have changed to a rather specialised question ("...the average of a SEQUENCE of angles where each successive addition does not differ from the running mean by more than a specified amount." ) - see @MaR comment and mine. My following answer addresses the OP's title and the bulk of the discussion and answers related to it.]

This is not a question of logic or intuition, but of definition. This has been discussed on SO before without any real consensus. Angles should be defined within a range (which might be -PI to +PI, or 0 to 2*PI or might be -Inf to +Inf. The answers will be different in each case.

The word "angle" causes confusion as it means different things. The angle of view is an unsigned quantity (and is normally PI > theta > 0. In that cases "normal" averages might be useful. Angle of rotation (e.g. total rotation if an ice skater) might or might not be signed and might include theta > 2PI and theta < -2PI.

What is defined here is angle = direction whihch requires vectors. If you use the word "direction" instead of "angle" you will have captured the OP's (apparent original) intention and it will help to move away from scalar quantities.

Wikipedia shows the correct approach when angles are defined circularly such that

theta = theta+2*PI*N = theta-2*PI*N

The answer for the mean is NOT a scalar but a vector. The OP may not feel this is intuitive but it is the only useful correct approach. We cannot redefine the square root of -4 to be -2 because it's more initutive - it has to be +-2*i. Similarly the average of bearings -90 degrees and +90 degrees is a vector of zero length, not 0.0 degrees.

Wikipedia (http://en.wikipedia.org/wiki/Mean_of_circular_quantities) has a special section and states (The equations are LaTeX and can be seen rendered in Wikipedia):

Most of the usual means fail on circular quantities, like angles, daytimes, fractional parts of real numbers. For those quantities you need a mean of circular quantities.

Since the arithmetic mean is not effective for angles, the following method can be used to obtain both a mean value and measure for the variance of the angles:

Convert all angles to corresponding points on the unit circle, e.g., α to (cosα,sinα). That is convert polar coordinates to Cartesian coordinates. Then compute the arithmetic mean of these points. The resulting point will lie on the unit disk. Convert that point back to polar coordinates. The angle is a reasonable mean of the input angles. The resulting radius will be 1 if all angles are equal. If the angles are uniformly distributed on the circle, then the resulting radius will be 0, and there is no circular mean. In other words, the radius measures the concentration of the angles.

Given the angles \alpha_1,\dots,\alpha_n the mean is computed by

M \alpha = \operatorname{atan2}\left(\frac{1}{n}\cdot\sum_{j=1}^n

\sin\alpha_j, \frac{1}{n}\cdot\sum_{j=1}^n \cos\alpha_j\right)

using the atan2 variant of the arctangent function, or

M \alpha = \arg\left(\frac{1}{n}\cdot\sum_{j=1}^n

\exp(i\cdot\alpha_j)\right)

using complex numbers.

Note that in the OP's question an angle of 0 is purely arbitrary - there is nothing special about wind coming from 0 as opposed to 180 (except in this hemisphere it's colder on the bicycle). Try changing 0,0,90 to 289, 289, 379 and see how the simple arithmetic no longer works.

(There are some distributions where angles of 0 and PI have special significance but they are not in scope here).

Here are some intense previous discussions which mirror the current spread of views :-)

Link

How do you calculate the average of a set of circular data?

http://forums.xkcd.com/viewtopic.php?f=17&t=22435

http://www.allegro.cc/forums/thread/595008

Toe answered 28/11, 2009 at 19:19 Comment(1)
the wikipedia method is the one that the OP rejected because it resulted in average(0, 0, 90) = 26.56Cullum
I
11

Thank you all for helping me see my problem more clearly.

I found what I was looking for. It is called Mitsuta method.

The inputs and output are in the range [0..360).

This method is good for averaging data that was sampled using constant sampling intervals.

The method assumes that the difference between successive samples is less than 180 degrees (which means that if we won't sample fast enough, a 330 degrees change in the sampled signal would be incorrectly detected as a 30 degrees change in the other direction and will insert an error into the calculation). Nyquist–Shannon sampling theorem anybody ?

Here is a c++ code:

double AngAvrg(const vector<double>& Ang)
{
    vector<double>::const_iterator iter= Ang.begin();
    
    double fD   = *iter;
    double fSigD= *iter;

    while (++iter != Ang.end())
    {
        double fDelta= *iter - fD;

             if (fDelta < -180.) fD+= fDelta + 360.;
        else if (fDelta >  180.) fD+= fDelta - 360.;
        else                     fD+= fDelta       ;

        fSigD+= fD;
    }

    double fAvrg= fSigD / Ang.size();

    if (fAvrg >= 360.) return fAvrg -360.;
    if (fAvrg <  0.  ) return fAvrg +360.;
                       return fAvrg      ;
}

It is explained on page 51 of Meteorological Monitoring Guidance for Regulatory Modeling Applications (PDF)(171 pp, 02-01-2000, 454-R-99-005)

Thank you MaR for sending the link as a comment.

If the sampled data is constant, but our sampling device has an inaccuracy with a Von Mises distribution, a unit-vectors calculation will be appropriate.

Intermediate answered 1/12, 2009 at 19:11 Comment(2)
Also read this about Mitsuta method.Hardpan
The lines fD+= fDelta + 360, etc are somewhat redundant, and obfuscate the purpose of the fD variable, making it appear to be a running sum. The value of fDelta is directly related to the old value of fD, so you could just say fD= *iter + 360, etc.Byproduct
J
3

This is incorrect on every level.

Vectors add according to the rules of vector addition. The "intuitive, expected" answer might not be that intuitive.

Take the following example. If I have one unit vector (1, 0), with origin at (0,0) that points in the +x-direction and another (-1, 0) that also has its origin at (0,0) that points in the -x-direction, what should the "average" angle be?

If I simply add the angles and divide by two, I can argue that the "average" is either +90 or -90. Which one do you think it should be?

If I add the vectors according to the rules of vector addition (component by component), I get the following:

(1, 0) + (-1, 0) = (0, 0)

In polar coordinates, that's a vector with zero magnitude and angle zero.

So what should the "average" angle be? I've got three different answers here for a simple case.

I think the answer is that vectors don't obey the same intuition that numbers do, because they have both magnitude and direction. Maybe you should describe what problem you're solving a bit better.

Whatever solution you decide on, I'd advise you to base it on vectors. It'll always be correct that way.

Janessajanet answered 28/11, 2009 at 19:33 Comment(7)
The average of x, x+180, or of x,x+120,x+240 and so on is a patological case. whether you use vectors or numbers - the answer is not defined.Intermediate
I say the only thing that makes sense is vector arithmetic, not dealing directly with angles at all. Do you agree?Janessajanet
@duffymo: It is not a question of correctness. It a question of definition. My question is how to define the circular average function such that my examples will hold.Intermediate
What is definition about besides correctness? Gotta take the mod nature into account. I'd also advise a 0 to 360 or 0 to 2&pi; scale if you must go this way.Janessajanet
"pathological"? I don't see why that answer isn't defined. If I take a vector view of things it most certainly is defined. What's your basis for saying this, besides "I said so"? A mathematical citation would be helpful.Janessajanet
Vectors do follow real number intuition, and averaging works for them. But directions represented as vectors aren't really vectors living in a space like R^N. They are points on a sphere S^N.Pustule
It's been 13 years since this question was asked and answered. I don't agree: scalars and vectors are very different.Janessajanet
F
3

What does it even mean to average source bearings? Start by answering that question, and you'll get closer to being to define what you mean by the average of angles.

In my mind, an angle with tangent equal to 1/2 is the right answer. If I have a unit force pushing me in the direction of the vector (1, 0), another force pushing me in the direction of the vector (1, 0) and third force pushing me in the direction of the vector (0, 1), then the resulting force (the sum of these forces) is the force pushing me in the direction of (1, 2). These the the vectors representing the bearings 0 degrees, 0 degrees and 90 degrees. The angle represented by the vector (1, 2) has tangent equal to 1/2.

Responding to your second edit:

Let's say that we are measuring wind direction. Our 3 measurements were 0, 0, and 90 degrees. Since all measurements are equivalently reliable, why shouldn't our best estimate of the wind direction be 30 degrees? setting it to 25.56 degrees is a bias toward 0...

Okay, here's an issue. The unit vector with angle 0 doesn't have the same mathematical properties that the real number 0 has. Using the notation 0v to represent the vector with angle 0, note that

0v + 0v = 0v

is false but

0 + 0 = 0

is true for real numbers. So if 0v represents wind with unit speed and angle 0, then 0v + 0v is wind with double unit speed and angle 0. And then if we have a third wind vector (which I'll representing using the notation 90v) which has angle 90 and unit speed, then the wind that results from the sum of these vectors does have a bias because it's traveling at twice unit speed in the horizontal direction but only unit speed in the vertical direction.

Friary answered 29/11, 2009 at 18:8 Comment(3)
I look at it this way: let's say that my measurement tool have some error, of X degrees RMS. this is why I'm getting different results for each measurement. The fact that the measurement represent physical direction is less important, as far as I can tellIntermediate
Actually, I don't measure speed. Only direction. Also, let's assume the the wind has a constant direction, but the error is only in my measurements.Intermediate
@Lior Kogan: Directions are typically represented by unit-length vectors.Friary
C
2

Edit: Equivalent, but more robust algorithm (and simpler):

  1. divide angles into 2 groups, [0-180) and [180-360)
  2. numerically average both groups
  3. average the 2 group averages with proper weighting
  4. if wraparound occurred, correct by 180˚

This works because number averaging works "logically" if all the angles are in the same hemicircle. We then delay getting wraparound error until the very last step, where it is easily detected and corrected. I also threw in some code for handling opposite angle cases. If the averages are opposite we favor the hemisphere that had more angles in it, and in the case of equal angles in both hemispheres we return None because no average would make sense.

The new code:

def averageAngles2(angles):
    newAngles = [a % 360 for a in angles];
    smallAngles = []
    largeAngles = []
    # split the angles into 2 groups: [0-180) and [180-360)
    for angle in newAngles:
        if angle < 180:
            smallAngles.append(angle)
        else:
            largeAngles.append(angle)
    smallCount = len(smallAngles)
    largeCount = len(largeAngles)
    #averaging each of the groups will work with standard averages
    smallAverage = sum(smallAngles) / float(smallCount) if smallCount else 0
    largeAverage = sum(largeAngles) / float(largeCount) if largeCount else 0
    if smallCount == 0:
        return largeAverage
    if largeCount == 0:
        return smallAverage
    average = (smallAverage * smallCount + largeAverage * largeCount) / \
        float(smallCount + largeCount)
    if largeAverage < smallAverage + 180:
        # average will not hit wraparound
        return average
    elif largeAverage > smallAverage + 180:
        # average will hit wraparound, so will be off by 180 degrees
        return (average + 180) % 360
    else:
        # opposite angles: return whichever has more weight
        if smallCount > largeCount:
            return smallAverage
        elif smallCount < largeCount:
            return largeAverage
        else:
            return None

 

>>> averageAngles2([0, 0, 90])
30.0
>>> averageAngles2([30, 350])
10.0
>>> averageAngles2([0, 200])
280.0

Here's a slightly naive algorithm:

  1. remove all oposite angles from the list
  2. take a pair of angles
  3. rotate them to the first and second quadrant and average them
  4. rotate average angle back by same amount
  5. for each remaining angle, average in same way, but with successively increasing weight to the composite angle

some python code (step 1 not implemented)

def averageAngles(angles):
    newAngles = [a % 360 for a in angles];
    average = 0
    weight = 0
    for ang in newAngles:
        theta = 0
        if 0 < ang - average <= 180:
            theta = 180 - ang
        else:
            theta = 180 - average
        r_ang = (ang + theta) % 360
        r_avg = (average + theta) % 360
        average = ((r_avg * weight + r_ang) / float(weight + 1) - theta) % 360
        weight += 1
    return average

Cullum answered 28/11, 2009 at 19:35 Comment(8)
I wonder if the result depends on the order of the angles in the list. That seems undesirable.Forgiven
only on edge cases I think, since the weighting is in place, this is still far from a perfect solution. All 3 of the test cases I have up there work for different orderings.Cullum
This is interesting. I'll run some tests... Thank you.Intermediate
Sorry, it won't work in the general case. Example: 10,170,190,270,350. The average should be 270, but it is 198.Intermediate
the problem with the original was that it didn't handle opposite angles wellCullum
I tried the new one. ((10+170)/2)*(2/5) + ((190+270+350)/3)*(3/5)= 198Intermediate
the python works though, although I just realized case 10, 170, 270 isn't idealCullum
This is crazy, you may as well write special code for every case.Bluebeard
P
2

In my opinion, this is about angles, not vectors. For that reason the average of 360 and 0 is truly 180. The average of one turn and no turns should be half a turn.

Photomontage answered 28/11, 2009 at 19:38 Comment(1)
(i) circular average of two angles is defined according to the smaller arc between them. (ii) 0=360. Average(0,360)=Average(0,0)=0.Intermediate
Z
2

Here's the answer I gave to this same question:

How do you calculate the average of a set of circular data?

It gives answers inline with what the OP says he wants, but attention should be paid to this:

"I would also like to stress that even though this is a true average of angles, unlike the vector solutions, that does not necessarily mean it is the solution you should be using, the average of the corresponding unit vectors may well be the value you actually should to be using."

Zhao answered 6/9, 2010 at 13:52 Comment(0)
P
1

You are correct that the accepted answer of using traditional average is wrong.

An average of a set of points x_1 ... x_n in a metric space X is an element x in X that minimizes the sum of distances squares to each point (See Frechet mean). If you try to find this minimum using simple calculus with regular real numbers, you will recover the standard "add up and divide by n" formula.

For an angle, our elements are actually points on the unit circle S1. Our metric isn't euclidean distance, but arc length, which is proportional to angle.

So, the average angle is the one that minimizes the square of the angle difference between each other angle. In other words, if you have a function angleBetween(a, b) you want to find the angle a such that sum over i of angleBetween(a_i, a) is minimized.

This is an optimization problem which can be solved using a numerical optimizer. Several of the answers here claim to provide simpler closed forms, or at least better approximations.

Statistics

As you point out in your article, you need to assume errors follow a Gaussian distribution to justify using least squares as the maximum likelyhood estimator. So in this application, where is the error? Is the random error in the position of two things, and the angle is just the normal of the line between them? If so, that normal will not follow a Gaussian distribution, even if the error in point position does. Taking means of angles only really makes sense if the random error is observed in the angle itself.

Pustule answered 22/8, 2022 at 4:24 Comment(0)
A
0

What is wrong with taking the set of angles as real values and just computing the arithmetic average of those numbers? Then you would get the intuitive (0+0+90)/3 = 30 deg.

Edit: Thanks for useful comments and pointing out that angles may exceed 360. I believe the answer could be the normal arithmetic average reduced "modulo" 360: we sum all the values, divide by the number of angles and then subtract/add a multiple of 360 so that the result lies in the interval [0..360).

Aeriela answered 28/11, 2009 at 19:24 Comment(7)
Then what happens if you want the average of 360 + 10 + 20, clearly the answer should not be 130..Superfine
The problem is when the sum is greater than 360. For example, the average of 30 and 350 should be 10, not 190). – Lior Kogan 0 secs agoIntermediate
Then just take the sum, modulo 360, before dividing? ((30 + 350) % 360)/2Purapurblind
@SimonJ: ((340+345+350) % 360)/3 = 105. It should be 345 of course.Intermediate
@Lior: Why not take the sum of the moduli instead of the modulus of the sum, e.g. ((340%360) + (345%360) + (350%360))/3 = 345Tetragon
@ThisSuitIsBlackNot: Fails for { 180, 180 }. (Average by any reasonable definition should be 180, but sum of moduli results in 360.)Friary
@Jason: You should have looked at my example. You take the average of the sum of the moduli, so for {180, 180}: ((180%360) + (180%360))/2 = 180Tetragon
D
0

You could do this: Say you have a set of angles in an array angle, then to compute the array first do: angle[i] = angle[i] mod 360, now perform a simple average over the array. So when you have 360, 10, 20, you are averaging 0, 10 and 20 - the results are intuitive.

Disgruntle answered 28/11, 2009 at 19:34 Comment(0)
I
0

I think the problem stems from how you treat angles greater than 180 (and those greater than 360 as well). If you reduce the angles to a range of +180 to -180 before adding them to the total, you get something more reasonable:

int AverageOfAngles(int angles[], int count)
{
    int total = 0;
    for (int index = 0; index < count; index++)
    {
        int angle = angles[index] % 360;
        if (angle > 180) { angle -= 360; }
        total += angle;
    }

    return (int)((float)total/count);
}
Irkutsk answered 28/11, 2009 at 19:41 Comment(2)
then you get the problem of average(-170, 170) = 0Cullum
I would want it to be 180 personallyCullum
R
0

Maybe you could represent angles as quaternions and take average of these quaternions and convert it back to angle.

I don't know If it gives you what you want because quaternions are rather rotations than angles. I also don't know if it will give you anything different from vector solution.

Quaternions in 2D simplify to complex numbers so I guess It's just vectors but maybe some interesting quaternion averaging algorithm like http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf when simplified to 2D will behave better than just vector average.

Razzledazzle answered 1/12, 2009 at 21:19 Comment(0)
R
0

Here you go! The reference is https://www.wxforum.net/index.php?topic=8660.0

def avgWind(directions):
    sinSum = 0
    cosSum = 0
    d2r = math.pi/180 #degree to radian
    r2d = 180/math.pi
       
    for i in range(len(directions)):
        sinSum += math.sin(directions[i]*d2r)
        cosSum += math.cos(directions[i]*d2r)
      
    return ((r2d*(math.atan2(sinSum, cosSum)) + 360) % 360)
a= np.random.randint(low=0, high=360, size=6)
print(a)
avgWind(a)
Ragin answered 6/11, 2020 at 4:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.