Averages/Mean angle

From Rosetta Code
Jump to: navigation, search
Task
Averages/Mean angle
You are encouraged to solve this task according to the task description, using any language you may know.

When calculating the average or mean of an angle one has to take into account how angles wrap around so that any angle in degrees plus any integer multiple of 360 degrees is a measure of the same angle.

If one wanted an average direction of the wind over two readings where the first reading was of 350 degrees and the second was of 10 degrees then the average of the numbers is 180 degrees, whereas if you can note that 350 degrees is equivalent to -10 degrees and so you have two readings at 10 degrees either side of zero degrees leading to a more fitting mean angle of zero degrees.

To calculate the mean angle of several angles:

  1. Assume all angles are on the unit circle and convert them to complex numbers expressed in real and imaginary form.
  2. Compute the mean of the complex numbers.
  3. Convert the complex mean to polar coordinates whereupon the phase of the complex mean is the required angular mean.

(Note that, since the mean is the sum divided by the number of numbers, and division by a positive real number does not affect the angle, you can also simply compute the sum for step 2.)

You can alternatively use this formula:

Given the angles \alpha_1,\dots,\alpha_n the mean is computed by
\bar{\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)

The task is to:

  1. write a function/method/subroutine/... that given a list of angles in degrees returns their mean angle. (You should use a built-in function if you have one that does this for degrees or radians).
  2. Use the function to compute the means of these lists of angles (in degrees): [350, 10], [90, 180, 270, 360], [10, 20, 30]; and show your output here.
See Also

Contents

[edit] Ada

An implementation based on the formula using the "Arctan" (atan2) function, thus avoiding complex numbers:

with Ada.Text_IO, Ada.Numerics.Generic_Elementary_Functions;
 
procedure Mean_Angles is
 
type X_Real is digits 4; -- or more digits for improved precision
subtype Real is X_Real range 0.0 .. 360.0; -- the range of interest
type Angles is array(Positive range <>) of Real;
 
procedure Put(R: Real) is
package IO is new Ada.Text_IO.Float_IO(Real);
begin
IO.Put(R, Fore => 3, Aft => 2, Exp => 0);
end Put;
 
function Mean_Angle(A: Angles) return Real is
Sin_Sum, Cos_Sum: X_Real := 0.0; -- X_Real since sums might exceed 360.0
package Math is new Ada.Numerics.Generic_Elementary_Functions(Real);
use Math;
begin
for I in A'Range loop
Sin_Sum := Sin_Sum + Sin(A(I), Cycle => 360.0);
Cos_Sum := Cos_Sum + Cos(A(I), Cycle => 360.0);
end loop;
return Arctan(Sin_Sum / X_Real(A'Length), Cos_Sum / X_Real(A'Length),
Cycle => 360.0);
-- may raise Ada.Numerics.Argument_Error if inputs are
-- numerically instable, e.g., when Cos_Sum is 0.0
end Mean_Angle;
 
begin
Put(Mean_Angle((10.0, 20.0, 30.0))); Ada.Text_IO.New_Line; -- 20.00
Put(Mean_Angle((10.0, 350.0))); Ada.Text_IO.New_Line; -- 0.00
Put(Mean_Angle((90.0, 180.0, 270.0, 360.0))); -- Ada.Numerics.Argument_Error!
end Mean_Angles;
Output:
 20.00
  0.00

raised ADA.NUMERICS.ARGUMENT_ERROR : a-ngelfu.adb:427 instantiated at mean_angles.adb:17

[edit] AutoHotkey

Works with: AutoHotkey_L
(AutoHotkey1.1+)
Angles :=  [[350, 10], [90, 180, 270, 360], [10, 20, 30]]
MsgBox, % MeanAngle(Angles[1]) "`n"
. MeanAngle(Angles[2]) "`n"
. MeanAngle(Angles[3])
 
MeanAngle(a, x=0, y=0) {
c := ATan(1) / 45
for k, v in a
x += Cos(v * c) / a.MaxIndex()
, y += Sin(v * c) / a.MaxIndex()
return atan2(x, y) / c
}
 
atan2(x, y) {
return dllcall("msvcrt\atan2", "Double",y, "Double",x, "CDECL Double")
}
atan2() originally from here.

Output:

-0.000000
-90.000000
20.000000

[edit] AWK

#!/usr/bin/awk -f
{
PI = atan2(0,-1);
x=0.0; y=0.0;
for (i=1; i<=NF; i++) {
p = $i*PI/180.0;
x += sin(p);
y += cos(p);
}
p = atan2(x,y)*180.0/PI;
if (p<0) p += 360;
print p;
}
 echo 350 10 | ./mean_angle.awk 
360
 echo 10 20 30 | ./mean_angle.awk 
20
 echo 90 180 270 360  | ./mean_angle.awk 
270

[edit] BBC BASIC

      *FLOAT 64
DIM angles(3)
angles() = 350,10
PRINT FNmeanangle(angles(), 2)
angles() = 90,180,270,360
PRINT FNmeanangle(angles(), 4)
angles() = 10,20,30
PRINT FNmeanangle(angles(), 3)
END
 
DEF FNmeanangle(angles(), N%)
LOCAL I%, sumsin, sumcos
FOR I% = 0 TO N%-1
sumsin += SINRADangles(I%)
sumcos += COSRADangles(I%)
NEXT
= DEGFNatan2(sumsin, sumcos)
 
DEF FNatan2(y,x) : ON ERROR LOCAL = SGN(y)*PI/2
IF x>0 THEN = ATN(y/x) ELSE IF y>0 THEN = ATN(y/x)+PI ELSE = ATN(y/x)-PI
Output:
-1.61480993E-15
       -90
        20

[edit] 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;
}
Output:
Mean Angle for 1st set : -0.000000 degrees
Mean Angle for 2nd set : -90.000000 degrees
Mean Angle for 3rd set : 20.000000 degrees

[edit] Common Lisp

(defun average (list)
(/ (reduce #'+ list) (length list)))
 
(defun radians (angle)
(* pi 1/180 angle))
 
(defun degrees (angle)
(* 180 (/ 1 pi) angle))
 
(defun mean-angle (angles)
(let* ((angles (map 'list #'radians angles))
(cosines (map 'list #'cos angles))
(sines (map 'list #'sin angles)))
(degrees (atan (average sines) (average cosines)))))
 
(loop for angles in '((350 10) (90 180 270 360) (10 20 30))
do (format t "~&The mean angle of ~a is ~$°." angles (mean-angle angles)))
Output:
The mean angle of (350 10) is -0.00°.
The mean angle of (90 180 270 360) is -90.00°.
The mean angle of (10 20 30) is 20.00°.

[edit] D

import std.stdio, std.algorithm, std.complex;
import std.math: PI;
 
auto radians(T)(in T d) pure nothrow @nogc { return d * PI / 180; }
auto degrees(T)(in T r) pure nothrow @nogc { return r * 180 / PI; }
 
real meanAngle(T)(in T[] D) pure nothrow @nogc {
immutable t = reduce!((a, d) => a + d.radians.expi)(0.complex, D);
return (t / D.length).arg.degrees;
}
 
void main() {
foreach (angles; [[350, 10], [90, 180, 270, 360], [10, 20, 30]])
writefln("The mean angle of %s is: %.2f degrees",
angles, angles.meanAngle);
}
Output:
The mean angle of [350, 10] is: -0.00 degrees
The mean angle of [90, 180, 270, 360] is: 90.00 degrees
The mean angle of [10, 20, 30] is: 20.00 degrees

[edit] Erlang

The function from_degrees/1 is used to solve Averages/Mean_time_of_day. Please keep backwards compatibility when editing. Or update the other module, too.

 
-module( mean_angle ).
-export( [from_degrees/1, task/0] ).
 
from_degrees( Angles ) ->
Radians = [radians(X) || X <- Angles],
Sines = [math:sin(X) || X <- Radians],
Coses = [math:cos(X) || X <- Radians],
degrees( math:atan2( average(Sines), average(Coses) ) ).
 
task() ->
Angles = [[350, 10], [90, 180, 270, 360], [10, 20, 30]],
[io:fwrite( "Mean angle of ~p is: ~p~n", [X, erlang:round(from_degrees(X))] ) || X <- Angles].
 
 
average( List ) -> lists:sum( List ) / erlang:length( List ).
 
degrees( Radians ) -> Radians * 180 / math:pi().
 
radians( Degrees ) -> Degrees * math:pi() / 180.
 
Output:
16> mean_angle:task().
Mean angle of [350,10] is: 0
Mean angle of [90,180,270,360] is: -90
Mean angle of [10,20,30] is: 20

[edit] Euler Math Toolbox

>function meanangle (a) ...
$ z=sum(exp(rad(a)*I));
$ if z~=0 then error("Not meaningful");
$ else return deg(arg(z))
$ endfunction
>meanangle([350,10])
0
>meanangle([90,180,270,360])
Error : Not meaningful
 
Error generated by error() command
 
Error in function meanangle in line
if z~=0 then error("Not meaningful");
>meanangle([10,20,30])
20


[edit] Euphoria

Works with: OpenEuphoria
 
include std/console.e
include std/mathcons.e
 
sequence AngleList = {{350,10},{90,180,270,360},{10,20,30}}
 
function atan2(atom y, atom x)
return 2*arctan((sqrt(power(x,2)+power(y,2)) - x)/y)
end function
 
function MeanAngle(sequence angles)
atom x = 0, y = 0
integer l = length(angles)
 
for i = 1 to length(angles) do
x += cos(angles[i] * PI / 180)
y += sin(angles[i] * PI / 180)
end for
 
return atan2(y / l, x / l) * 180 / PI
end function
 
for i = 1 to length(AngleList) do
printf(1,"Mean Angle for set %d:  %3.5f\n",{i,MeanAngle(AngleList[i])})
end for
 
if getc(0) then end if
 
Output:
Mean Angle for set 1:  0.00000
Mean Angle for set 2:  -90.00000
Mean Angle for set 3:  20.00000

[edit] Fortran

Please find the example output along with the build instructions in the comments at the start of the FORTRAN 2008 source. Compiler: gfortran from the GNU compiler collection. Command interpreter: bash.

 
!-*- mode: compilation; default-directory: "/tmp/" -*-
!Compilation started at Mon Jun 3 18:07:59
!
!a=./f && make $a && OMP_NUM_THREADS=2 $a
!gfortran -std=f2008 -Wall -fopenmp -ffree-form -fall-intrinsics -fimplicit-none f.f08 -o f
! -7.80250048E-06 350 10
! 90.0000000 90 180 270 360
! 19.9999962 10 20 30
!
!Compilation finished at Mon Jun 3 18:07:59
 
program average_angles
!real(kind=8), parameter :: TAU = 6.283185307179586232 ! http://tauday.com/
!integer, dimension(13), parameter :: test_data = (/2,350,10, 4,90,180,270,360, 3,10,20,30, 0/)
!integer :: i, j, n
!complex(kind=16) :: some
!real(kind=8) :: angle
real, parameter :: TAU = 6.283185307179586232 ! http://tauday.com/
integer, dimension(13), parameter :: test_data = (/2,350,10, 4,90,180,270,360, 3,10,20,30, 0/)
integer :: i, j, n
complex :: some
real :: angle
i = 1
n = int(test_data(i))
do while (0 .lt. n)
some = 0
do j = 1, n
angle = (TAU/360)*test_data(i+j)
some = some + cmplx(cos(angle), sin(angle))
end do
some = some / n
write(6,*)(360/TAU)*atan2(aimag(some), real(some)),test_data(i+1:i+n)
i = i + n + 1
n = int(test_data(i))
end do
end program average_angles
 

[edit] Go

[edit] Complex

package main
 
import (
"fmt"
"math"
"math/cmplx"
)
 
func deg2rad(d float64) float64 { return d * math.Pi / 180 }
func rad2deg(r float64) float64 { return r * 180 / math.Pi }
 
func mean_angle(deg []float64) float64 {
sum := 0i
for _, x := range deg {
sum += cmplx.Rect(1, deg2rad(x))
}
return rad2deg(cmplx.Phase(sum))
}
 
func main() {
for _, angles := range [][]float64{
{350, 10},
{90, 180, 270, 360},
{10, 20, 30},
} {
fmt.Printf("The mean angle of %v is: %f degrees\n", angles, mean_angle(angles))
}
}
Output:
The mean angle of [350 10] is: -0.000000 degrees
The mean angle of [90 180 270 360] is: -90.000000 degrees
The mean angle of [10 20 30] is: 20.000000 degrees

[edit] Atan2

A mean_angle function that could be substituted above. Functions deg2rad and rad2deg are not used here but there is no runtime advantage either way to using them or not. Inlining should result in eqivalent code being generated. Also the Go Atan2 library function has no limits on the arguments so there is no need to divide by the number of elements.

func mean_angle(deg []float64) float64 {
var ss, sc float64
for _, x := range deg {
s, c := math.Sincos(x * math.Pi / 180)
ss += s
sc += c
}
return math.Atan2(ss, sc) * 180 / math.Pi
}

[edit] Groovy

import static java.lang.Math.*
def meanAngle = {
atan2( it.sum { sin(it * PI / 180) } / it.size(), it.sum { cos(it * PI / 180) } / it.size()) * 180 / PI
}

Test:

def verifyAngle = { angles ->
def ma = meanAngle(angles)
printf("Mean Angle for $angles: %5.2f%n", ma)
round(ma * 100) / 100.0
}
assert verifyAngle([350, 10]) == -0
assert verifyAngle([90, 180, 270, 360]) == -90
assert verifyAngle([10, 20, 30]) == 20
Output:
Mean Angle for [350, 10]: -0.00
Mean Angle for [90, 180, 270, 360]: -90.00
Mean Angle for [10, 20, 30]: 20.00

[edit] Haskell

import Data.Complex (cis, phase)
 
meanAngle = (/ pi) . (* 180) . phase . sum . map (cis . (/ 180) . (* pi))
 
main = mapM_ (\angles -> putStrLn $ "The mean angle of " ++ show angles ++ " is: " ++ show (meanAngle angles) ++ " degrees")
[[350, 10], [90, 180, 270, 360], [10, 20, 30]]
Output:
The mean angle of [350.0,10.0] is: -2.745176884498468e-14 degrees
The mean angle of [90.0,180.0,270.0,360.0] is: -90.0 degrees
The mean angle of [10.0,20.0,30.0] is: 19.999999999999996 degrees

[edit] Icon and Unicon

procedure main(A)
write("Mean angle is ",meanAngle(A))
end
 
procedure meanAngle(A)
every (sumSines := 0.0) +:= sin(dtor(!A))
every (sumCosines := 0.0) +:= cos(dtor(!A))
return rtod(atan(sumSines/*A,sumCosines/*A))
end

Sample runs:

->ama 10 350
Mean angle is -2.745176884498468e-14
->ama 90 180 270 360
Mean angle is -90.0
->ama 10 20 30
Mean angle is 20.0

[edit] J

avgAngleD=: (_1 { [: (**|)&.+.@(+/ % #)&.(*.inv) 1,.])&.(1r180p1&*)

This verb can be represented as simpler component verbs for example:

rfd=: 1r180p1&*                                          NB. convert angle to radians from degrees
toComplex=: *.inv NB. maps integer pairs as length, complex angle (in radians)
mean=: +/ % # NB. calculate arithmetic mean
roundComplex=: (* * |)&.+. NB. discard an extraneous least significant bit of precision from a complex value whose magnitude is in the vicinity of 1
avgAngleR=: _1 { [: roundComplex@mean&.toComplex 1 ,. ] NB. calculate average angle in radians
avgAngleD=: avgAngleR&.rfd
NB. calculate average angle in degrees

Example use:

   avgAngleD 10 350
0
avgAngleD 90 180 270 360 NB. result not meaningful
0
avgAngleD 10 20 30
20

Notes:

(**|)&.+. is an expression to discard an extraneous least significant bit of precision from a complex value whose magnitude is in the vicinity of 1.

(+/ % #) finds the (Pythagorean) mean

verb&.(*.inv) maps integer pairs as length,complex angle (in radians) and uses the verb in the domain of complex numbers, and then maps the result back to length,angle.

(1,.]) prefixes every value in a list with 1 (forming a two column table)

(_1 { verb) takes the last item from the result of the verb (and note that after we average our complex values and convert them back to length/angle format, we will be working with a list of two elements: length and angle -- and we do not care about length, which will usually be less than 1).

verb&.(1r180p1&*) converts its argument from degrees to radians, uses the verb in the radian domain, then converts the result of that argument back to degrees.

[edit] Java

Translation of: NetRexx
Works with: Java version 7+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
public class RAvgMeanAngle {
 
private static final List<List<Double>> samples;
 
static {
samples = new ArrayList<>();
samples.add(Arrays.asList(350.0, 10.0));
samples.add(Arrays.asList(90.0, 180.0, 270.0, 360.0));
samples.add(Arrays.asList(10.0, 20.0, 30.0));
samples.add(Arrays.asList(370.0));
samples.add(Arrays.asList(180.0));
}
 
public RAvgMeanAngle() {
 
return;
}
 
public double getMeanAngle(List<Double> sample) {
 
double x_component = 0.0;
double y_component = 0.0;
double avg_d, avg_r;
 
for (double angle_d : sample) {
double angle_r;
angle_r = Math.toRadians(angle_d);
x_component += Math.cos(angle_r);
y_component += Math.sin(angle_r);
}
x_component /= sample.size();
y_component /= sample.size();
avg_r = Math.atan2(y_component, x_component);
avg_d = Math.toDegrees(avg_r);
 
return avg_d;
}
 
public static void main(String[] args) {
 
runSample(args);
 
return;
}
 
public static void runSample(String[] args) {
 
RAvgMeanAngle main = new RAvgMeanAngle();
for (List<Double> sample : samples) {
double meanAngle = main.getMeanAngle(sample);
System.out.printf("The mean angle of %s is:%n%12.6f%n%n", sample, meanAngle);
}
 
return;
}
}
Output:
The mean angle of [350.0, 10.0] is:
   -0.000000

The mean angle of [90.0, 180.0, 270.0, 360.0] is:
  -90.000000

The mean angle of [10.0, 20.0, 30.0] is:
   20.000000

The mean angle of [370.0] is:
   10.000000

The mean angle of [180.0] is:
  180.000000

[edit] Julia

Julia has built-in functions sind and cosd to compute the sine and cosine of angles specified in degrees accurately (avoiding the roundoff errors incurred in conversion to radians), and a built-in function to convert radians to degrees (or vice versa). Using these:

meandegrees(degrees) = radians2degrees(atan2(mean(sind(degrees)), mean(cosd(degrees))))

The output is:

julia> meandegrees([350, 10])
0.0
 
julia> meandegrees([90, 180, 270, 360]])
0.0
 
julia> meandegrees([10, 20, 30]])
19.999999999999996

(Note that the mean of 90°, 180°, 270°, and 360° gives zero because of the lack of roundoff errors in the sind function, since the standard-library atan2(0,0) value is zero. Many of the other languages report an average of 90° or –90° in this case due to rounding errors.)

[edit] Liberty BASIC

global Pi
Pi =3.1415926535
 
print "Mean Angle( "; chr$( 34); "350,10"; chr$( 34); ") = "; using( "###.#", meanAngle( "350,10")); " degrees." ' 0
print "Mean Angle( "; chr$( 34); "90,180,270,360"; chr$( 34); ") = "; using( "###.#", meanAngle( "90,180,270,360")); " degrees." ' -90
print "Mean Angle( "; chr$( 34); "10,20,30"; chr$( 34); ") = "; using( "###.#", meanAngle( "10,20,30")); " degrees." ' 20
 
end
 
function meanAngle( angles$)
term =1
while word$( angles$, term, ",") <>""
angle =val( word$( angles$, term, ","))
sumSin = sumSin +sin( degRad( angle))
sumCos = sumCos +cos( degRad( angle))
term =term +1
wend
meanAngle= radDeg( atan2( sumSin, sumCos))
if abs( sumSin) +abs( sumCos) <0.001 then notice "Not Available." +_
chr$( 13) +"(" +angles$ +")" +_
chr$( 13) +"Result nearly equals zero vector." +_
chr$( 13) +"Displaying '666'.": meanAngle =666
 
end function
 
function degRad( theta)
degRad =theta *Pi /180
end function
 
function radDeg( theta)
radDeg =theta *180 /Pi
end function
 
function atan2( y, x)
if x >0 then at =atn( y /x)
if y >=0 and x <0 then at =atn( y /x) +pi
if y <0 and x <0 then at =atn( y /x) -pi
if y >0 and x =0 then at = pi /2
if y <0 and x =0 then at = 0 -pi /2
if y =0 and x =0 then notice "undefined": end
atan2 =at
end function
Output:
Mean Angle( "350,10") = 0.0 degrees.
Mean Angle( "90,180,270,360") = 666.0 degrees.
Mean Angle( "10,20,30") = 20.0 degrees.

[edit] Maple

The following procedure takes a list of numeric degrees (with attached units) such as

> [ 350, 10 ] *~ Unit(arcdeg);
[350 [arcdeg], 10 [arcdeg]]

as input. (We could use "degree" instead of "arcdeg", since "degree" is taken, by default, to mean angle measure, but it seems best to avoid the ambiguity.)

MeanAngle := proc( L )
uses Units:-Standard; # for unit-awareness
local u;
evalf( convert( argument( add( u, u = exp~( I *~ L ) ) ), 'units', 'radian', 'degree' ) )
end proc:

Applying this to the given data sets, we obtain:

> MeanAngle( [ 350, 10 ] *~ Unit(arcdeg) );
0.
 
> MeanAngle( [ 90, 180, 270, 360 ] *~ Unit(arcdeg) );
0.
 
> MeanAngle( [ 10, 20, 30 ] *~ Unit(arcdeg) );
20.00000000

[edit] Mathematica

meanAngle[data_List] := N@Arg[Mean[Exp[I data Degree]]]/Degree
Output:
meanAngle /@ {{350, 10}, {90, 180, 270, 360}, {10, 20, 30}}
{0., Interval[{-180., 180.}], 20.}

[edit] MATLAB / Octave

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

[edit] NetRexx

Translation of: C
/* NetRexx */
options replace format comments java crossref symbols nobinary
numeric digits 80
 
runSample(arg)
return
 
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method getMeanAngle(angles) private static binary
x_component = double 0.0
y_component = double 0.0
aK = int angles.words()
loop a_ = 1 to aK
angle_d = double angles.word(a_)
angle_r = double Math.toRadians(angle_d)
x_component = x_component + Math.cos(angle_r)
y_component = y_component + Math.sin(angle_r)
end a_
x_component = x_component / aK
y_component = y_component / aK
avg_r = Math.atan2(y_component, x_component)
avg_d = Math.toDegrees(avg_r)
return avg_d
 
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method runSample(arg) private static
angleSet = [ '350 10', '90 180 270 360', '10 20 30', '370', '180' ]
loop angles over angleSet
say 'The mean angle of' angles.space(1, ',') 'is:'
say ' 'getMeanAngle(angles).format(6, 6)
say
end angles
return
 
Output:
The mean angle of 350,10 is:
       0.000000

The mean angle of 90,180,270,360 is:
     -90.000000

The mean angle of 10,20,30 is:
      20.000000

The mean angle of 370 is:
      10.000000

The mean angle of 180 is:
     180.000000

[edit] Nimrod

import math, complex
 
proc rect(r, phi): TComplex = (r * cos(phi), sin(phi))
proc phase(c): float = arctan2(c.im, c.re)
 
proc radians(x): float = (x * pi) / 180.0
proc degrees(x): float = (x * 180.0) / pi
 
proc meanAngle(deg): float =
var c: TComplex
for d in deg:
c += rect(1.0, radians(d))
degrees(phase(c / float(deg.len)))
 
echo "The 1st mean angle is: ", meanAngle([350.0, 10.0]), " degrees"
echo "The 2nd mean angle is: ", meanAngle([90.0, 180.0, 270.0, 360.0]), " degrees"
echo "The 3rd mean angle is: ", meanAngle([10.0, 20.0, 30.0]), " degrees"

Output:

The 1st mean angle is: -2.745176884498468e-14 degrees
The 2nd mean angle is: -90.0 degrees
The 3rd mean angle is: 20.0 degrees

[edit] OCaml

let pi = 3.14159_26535_89793_23846_2643
 
let deg2rad d =
d *. pi /. 180.0
 
let rad2deg r =
r *. 180.0 /. pi
 
let mean_angle angles =
let rad_angles = List.map deg2rad angles in
let sum_sin = List.fold_left (fun sum a -> sum +. sin a) 0.0 rad_angles
and sum_cos = List.fold_left (fun sum a -> sum +. cos a) 0.0 rad_angles
in
rad2deg (atan2 sum_sin sum_cos)
 
let test angles =
Printf.printf "The mean angle of [%s] is: %g degrees\n"
(String.concat "; " (List.map (Printf.sprintf "%g") angles))
(mean_angle angles)
 
let () =
test [350.0; 10.0];
test [90.0; 180.0; 270.0; 360.0];
test [10.0; 20.0; 30.0];
;;

or using the Complex module:

open Complex
 
let mean_angle angles =
let sum =
List.fold_left (fun sum a -> add sum (polar 1.0 (deg2rad a))) zero angles
in
rad2deg (arg sum)
Output:
The mean angle of [350; 10] is: -2.74518e-14 degrees
The mean angle of [90; 180; 270; 360] is: -90 degrees
The mean angle of [10; 20; 30] is: 20 degrees

[edit] PARI/GP

meanAngle(v)=atan(sum(i=1,#v,sin(v[i]))/sum(i=1,#v,cos(v[i])))%(2*Pi)
meanDegrees(v)=meanAngle(v*Pi/180)*180/Pi
apply(meanDegrees,[[350, 10], [90, 180, 270, 360], [10, 20, 30]])
Output:
[360.000000, 296.565051, 20.0000000]

[edit] Perl 6

This solution refuses to return an answer when the angles cancel out to a tiny magnitude.

sub deg2rad { $^d * pi / 180 }
sub rad2deg { $^r * 180 / pi }
sub phase ($c) {
my ($mag,$ang) = $c.polar;
return NaN if $mag < 1e-16;
$ang;
}
 
sub meanAngle { rad2deg phase [+] map { cis deg2rad $_ }, @^angles }
 
say meanAngle($_).fmt("%.2f\tis the mean angle of "), $_ for
[350, 10],
[90, 180, 270, 360],
[10, 20, 30];
Output:
-0.00	is the mean angle of 350 10
NaN	is the mean angle of 90 180 270 360
20.00	is the mean angle of 10 20 30

[edit] PHP

Translation of: C
<?php
$samples = array(
'1st' => array(350, 10),
'2nd' => array(90, 180, 270, 360),
'3rd' => array(10, 20, 30)
);
 
foreach($samples as $key => $sample){
echo 'Mean angle for ' . $key . ' sample: ' . meanAngle($sample) . ' degrees.' . PHP_EOL;
}
 
function meanAngle ($angles){
$y_part = $x_part = 0;
$size = count($angles);
for ($i = 0; $i < $size; $i++){
$x_part += cos(deg2rad($angles[$i]));
$y_part += sin(deg2rad($angles[$i]));
}
$x_part /= $size;
$y_part /= $size;
return rad2deg(atan2($y_part, $x_part));
}
?>
Output:
Mean angle for 1st sample: -1.6148099320579E-15 degrees.
Mean angle for 2nd sample: -90 degrees.
Mean angle for 3rd sample: 20 degrees.

[edit] PicoLisp

(load "@lib/math.l")
 
(de meanAngle (Lst)
(*/
(atan2
(sum '((A) (sin (*/ A pi 180.0))) Lst)
(sum '((A) (cos (*/ A pi 180.0))) Lst) )
180.0 pi ) )
 
(for L '((350.0 10.0) (90.0 180.0 270.0 360.0) (10.0 20.0 30.0))
(prinl
"The mean angle of ["
(glue ", " (mapcar round L '(0 .)))
"] is: " (round (meanAngle L))) )
Output:
The mean angle of [350, 10] is: 0.000
The mean angle of [90, 180, 270, 360] is: 90.000
The mean angle of [10, 20, 30] is: 20.000

[edit] PL/I

averages: procedure options (main);             /* 31 August 2012 */
declare b1(2) fixed initial (350, 10);
declare b2(4) fixed initial (90, 180, 270, 360);
declare b3(3) fixed initial (10, 20, 30);
 
put edit ( b1) (f(7));
put edit ( ' mean=', mean(b1) ) (a, f(7) );
put skip edit ( b3) (f(7));
put edit ( ' mean=', mean(b3) ) (a, f(7) );
put skip edit ( b2) (f(7));
put edit ( ' mean=', mean(b2) ) (a, f(7) );
 
mean: procedure (a) returns (fixed);
declare a(*) float (18);
return ( atand(sum(sind(a))/hbound(a), sum(cosd(a))/hbound(a) ) );
end mean;
 
end averages;

Results (the final one brings up an error in inverse tangent):

    350     10 mean=      0
     10     20     30 mean=     20
     90    180    270    360 mean=
IBM0683I  ONCODE=1521  X or Y in ATAN(X,Y) or ATAND(X,Y) was invalid.
   At offset +000009CC in procedure with entry AVERAGES

[edit] Python

Works with: Python version 2.6+
>>> from cmath import rect, phase
>>> from math import radians, degrees
>>> def mean_angle(deg):
... return degrees(phase(sum(rect(1, radians(d)) for d in deg)/len(deg)))
...
>>> for angles in [[350, 10], [90, 180, 270, 360], [10, 20, 30]]:
... print('The mean angle of', angles, 'is:', round(mean_angle(angles), 12), 'degrees')
...
The mean angle of [350, 10] is: -0.0 degrees
The mean angle of [90, 180, 270, 360] is: -90.0 degrees
The mean angle of [10, 20, 30] is: 20.0 degrees
>>>

[edit] Racket

The formula given above can be straightforwardly transcribed into a program:

 
#lang racket
 
(define (mean-angle αs)
(radians->degrees
(mean-angle/radians
(map degrees->radians αs))))
 
(define (mean-angle/radians αs)
(define n (length αs))
(atan (* (/ 1 n) (for/sum ([α_j αs]) (sin α_j)))
(* (/ 1 n) (for/sum ([α_j αs]) (cos α_j)))))
 
(mean-angle '(350 0 10))
(mean-angle '(90 180 270 360))
(mean-angle '(10 20 30))
 
Output:
-1.0710324872062297e-15
-90.0
19.999999999999996

[edit] REXX

[edit] using ATAN2 solution

The REXX language doesn't have most of the higher mathematical functions (like sqrt), and none of the trigonometric functions, so all of them have to be included as RYO (Roll-Your-Own).

Note that the second set of angles:

  • 90 180 270 360

is the same as:

  • 90 180 -90 0
  • -270 -180 -90 -360

and other combinations.

All the trigonomentric functions use normalization before computation, and most of them use shortcuts for some exact values, so there is a minimum of near values for some common values.   The consequence of this is the trig functions may return exact values such as 0 (zero) for sin(-2π) instead of -8.154E-61.   This very small difference (almost inconsequential) makes a significant difference when that value is used for a parameter for the ATAN2 function;   in particular, the sign of the value.   There isn't much difference between -8.154E-61 and +8.154E-61 in magnitude, but the ATAN2 function treats those two numbers much differently as the angle is in different quadrants, thereby yielding a different value.   Usually this just results in an angle of   -90º   instead of   +270º   (both angles are equivalent).

Also note that the REXX subroutines are largely not commented as they provide a support structure that's normally present in other languages as BIFs   (Built-In-Functions);   to add comments and expand the REXX statements into single lines would detract from the main program.

/*REXX program computes the  mean angle  (angles expressed in degrees). */
numeric digits 50 /*use fifty digits of precision, */
showDig=10 /*··· but only display 10 digits.*/
# = 350 10  ; say showit(#, meanAngleD(#) )
# = 90 180 270 360  ; say showit(#, meanAngleD(#) )
# = 10 20 30  ; say showit(#, meanAngleD(#) )
exit /*stick a fork in it, we're done.*/
/*──────────────────────────────────MEANANGD subroutine─────────────────*/
meanAngleD: procedure; parse arg x; numeric digits digits()+digits()%4
_sin=0; _cos=0; n=words(x); do j=1 for n
xr=d2r(d2d(word(x, j) ) )
_sin = _sin + sin(xr)
_cos = _cos + cos(xr)
end /*j*/
return r2d(atan2(_sin/n, _cos/n))
/*═════════════════════════════general 1-line subroutines (~thereabouts)*/
/*The 1-line subs were broken up into multiple lines for easier reading.*/
$fuzz: return min(arg(1), max(1, digits() - arg(2) ) )
d2d: return arg(1) // 360
d2r: return r2r(d2d(arg(1)) / 180 * pi() )
r2d: return d2d((r2r(arg(1)) / pi()) * 180)
r2r: return arg(1) // (pi() * 2)
p: return word(arg(1), 1)
/*───────────────────────────────────ACOS subroutine────────────────────*/
acos: procedure; parse arg x; if x<-1 | x>1 then call $81r -1,1,x,"ACOS"
return pi()*.5 - asin(x) /* [↑] $81r sub not included here*/
/*───────────────────────────────────ASIN subroutine────────────────────*/
asin: procedure; parse arg x 1 z 1 o 1 p; a=abs(x); aa=a*a
if a>1 then call $81r -1,1,x,"ASIN" /*X arg is out of range.*/
if a>=sqrt(2)*.5 then return sign(x)*acos(sqrt(1-aa), '-ASIN')
do j=2 by 2 until p=z; p=z; o=o*aa*(j-1)/j; z=z+o/(j+1); end
return z /* [↑] compute until no noise.*/
/*───────────────────────────────────ATAN subroutine────────────────────*/
atan: parse arg atanX; if abs(atanX)=1 then return pi()*.25*sign(atanX)
return asin(atanX / sqrt(1 + atanX**2) )
/*───────────────────────────────────ATAN2 subroutine───────────────────*/
atan2: procedure; parse arg y,x; pi=pi(); s=sign(y)
select
when x=0 then z=s * pi * .5
when x<0 then if y=0 then z=pi; else z=s*(pi-abs(atan(y/x)))
otherwise z=s * atan(y/x)
end /*select*/; return z
/*───────────────────────────────────COS subroutine─────────────────────*/
cos: procedure; parse arg x; x=r2r(x); numeric fuzz $fuzz(5, 3)
a=abs(x); if a=0 then return 1; pi=pi(); if a=pi then return -1
if a=pi*.5 | a=pi*1.5 then return 0; if a=pi/3 then return .5
if a=pi*2/3 then return -.5; return .sinCos(1, 1, -1)
/*───────────────────────────────────PI subroutine──────────────────────*/
pi: return ,
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148
/*───────────────────────────────────SHOWIT subroutine──────────────────*/
showit: procedure expose showDig; numeric digits showDig; parse arg a,mA
return left('angles='a,30) 'mean angle=' format(mA,,showDig,0)/1
/*───────────────────────────────────COS subroutine─────────────────────*/
sin: procedure; parse arg x; x=r2r(x); numeric fuzz $fuzz(5, 3)
pi=pi(); if x=pi*.5 then return 1; if x==pi*1.5 then return -1
if abs(x)=pi | x=0 then return 0; return .sinCos(x, x, +1)
/*───────────────────────────────────.SINCOS subroutine─────────────────*/
.sinCos:parse arg z,_,i; x=x*x
do k=2 by 2 until p=z; p=z; _=-_*x/(k*(k+i)); z=z+_; end /*k*/
return z
/*───────────────────────────────────SQRT subroutine────────────────────*/
sqrt: procedure; parse arg x,i; if x=0 then return 0; d=digits(); m.=11
if x<0 then i='i'; numeric digits 11; numeric form; p=d+d%4+2
parse value format(x,2,1,,0) 'E0' with g 'E' _ .; g=g*.5'E'_%2
do j=0 while p>9; m.j=p; p=p%2+1; end /*j*/
do k=j+5 to 0 by -1; if m.k>11 then numeric digits m.k
g=.5*(g+x/g); end /*k*/; numeric digits d; return g/1

output' when using the default input:

angles=350 10                  mean angle= 0
angles=90 180 270 360          mean angle= 0
angles=10 20 30                mean angle= 20

[edit] Ruby

require 'complex' # Superfluous in Ruby >= 2.0; complex is added to core.
 
def deg2rad(d)
d * Math::PI / 180
end
 
def rad2deg(r)
r * 180 / Math::PI
end
 
def mean_angle(deg)
rad2deg((deg.inject(0) {|z, d| z + Complex.polar(1, deg2rad(d))} / deg.length).arg)
end
 
[[350, 10], [90, 180, 270, 360], [10, 20, 30]].each {|angles|
puts "The mean angle of %p is: %f degrees" % [angles, mean_angle(angles)]
}
Output:
The mean angle of [350, 10] is: -0.000000 degrees
The mean angle of [90, 180, 270, 360] is: -90.000000 degrees
The mean angle of [10, 20, 30] is: 20.000000 degrees

[edit] Scala

Library: Scala
trait MeanAnglesComputation {
import scala.math.{Pi, atan2, cos, sin}
 
def meanAngle(angles: List[Double], convFactor: Double = 180.0 / Pi) = {
val sums = angles.foldLeft((.0, .0))((r, c) => {
val rads = c / convFactor
(r._1 + sin(rads), r._2 + cos(rads))
})
val result = atan2(sums._1, sums._2)
(result + (if (result.signum == -1) 2 * Pi else 0.0)) * convFactor
}
}
 
object MeanAngles extends App with MeanAnglesComputation {
assert(meanAngle(List(350, 10), 180.0 / math.Pi).round == 360, "Unexpected result with 350, 10")
assert(meanAngle(List(90, 180, 270, 360)).round == 270, "Unexpected result with 90, 180, 270, 360")
assert(meanAngle(List(10, 20, 30)).round == 20, "Unexpected result with 10, 20, 30")
println("Successfully completed without errors.")
}

[edit] Seed7

$ include "seed7_05.s7i";
include "float.s7i";
include "math.s7i";
include "complex.s7i";
 
const func float: deg2rad (in float: degree) is
return degree * PI / 180.0;
 
const func float: rad2deg (in float: rad) is
return rad * 180.0 / PI;
 
const func float: meanAngle (in array float: degrees) is func
result
var float: mean is 0.0;
local
var float: degree is 0.0;
var complex: sum is complex.value;
begin
for degree range degrees do
sum +:= polar(1.0, deg2rad(degree));
end for;
mean := rad2deg(arg(sum / complex conv length(degrees)));
end func;
 
const proc: main is func
begin
writeln(meanAngle([] (350.0, 10.0)) digits 4);
writeln(meanAngle([] (90.0, 180.0, 270.0, 360.0)) digits 4);
writeln(meanAngle([] (10.0, 20.0, 30.0)) digits 4);
end func;
Output:
0.0000
90.0000
20.0000

[edit] Tcl

proc meanAngle {angles} {
set toRadians [expr {atan2(0,-1) / 180}]
set sumSin [set sumCos 0.0]
foreach a $angles {
set sumSin [expr {$sumSin + sin($a * $toRadians)}]
set sumCos [expr {$sumCos + cos($a * $toRadians)}]
}
# Don't need to divide by counts; atan2() cancels that out
return [expr {atan2($sumSin, $sumCos) / $toRadians}]
}

Demonstration code:

# A little pretty-printer
proc printMeanAngle {angles} {
puts [format "mean angle of \[%s\] = %.2f" \
[join $angles ", "] [meanAngle $angles]]
}
 
printMeanAngle {350 10}
printMeanAngle {90 180 270 360}
printMeanAngle {10 20 30}
Output:
mean angle of [350, 10] = -0.00
mean angle of [90, 180, 270, 360] = -90.00
mean angle of [10, 20, 30] = 20.00

[edit] XPL0

include c:\cxpl\codes;
 
def Pi = 3.14159265358979323846;
def D2R = Pi/180.0; \coefficient to convert degrees to radians
 
func real MeanAng(A); \Return the mean of the given list of angles
int A;
real X, Y;
int I;
[X:= 0.0; Y:= 0.0;
for I:= 1 to A(0) do
[X:= X + Cos(D2R*float(A(I)));
Y:= Y + Sin(D2R*float(A(I)));
];
return ATan2(Y,X)/D2R;
];
 
[Format(5, 15);
RlOut(0, MeanAng([2, 350, 10])); CrLf(0);
RlOut(0, MeanAng([4, 90, 180, 270, 360])); CrLf(0);
RlOut(0, MeanAng([3, 10, 20, 30])); CrLf(0);
]
Output:
   -0.000000000000003
  -90.000000000000000
   20.000000000000000

The second example is interesting because it computes ATan2(0.,0.), which is undefined in mathematics (like division by zero), but the floating point processor in IBM-PC-type computers defines it as zero. The reason -90 gets displayed instead is due to small rounding errors (and another extra-mathematical concept, -0). In fact almost any angle can result because of slight rounding errors as Y and X both approach zero.

[edit] zkl

fcn meanA(a1,a2,etc){ 
as:=vm.arglist.apply(T("toFloat","toRad"));
n:=as.len();
(as.apply("sin").sum(0.0)/n)
.atan2(as.apply("cos").sum(0.0)/n)
.toDeg()
}
Output:
zkl: meanA(350,10)
-1.61481e-15
zkl: meanA(90,180,270,360)
-90
meanA(10,20,30)
20
Personal tools
Namespaces

Variants
Actions
Community
Explore
Misc
Toolbox