medians for degree measurements

Steve Howell showell30 at yahoo.com
Sat Jan 23 02:03:05 EST 2010


On Jan 22, 10:29 pm, Nobody <nob... at nowhere.com> wrote:
> On Fri, 22 Jan 2010 16:09:03 -0800, Steve Howell wrote:
> > I just saw the thread for medians, and it reminded me of a problem
> > that I need to solve.  We are writing some Python software for
> > sailing, and we need to detect when we've departed from the median
> > heading on the leg.  Calculating arithmetic medians is
> > straightforward, but compass bearings add a twist.
>
> > The numerical median of 1, 2, 3, 4, 5, 6, 359 is 4.  But for
> > navigational purposes you would actually order the numbers 359, 1, 2,
> > 3, 4, 5, 6, so the desired median heading of the boat is actually 3.
>
> > Of course, you could express 359 better as -1 degrees to north, then
> > the sequence would be -1, 1, 2, 3, 4, 5, and 6.  And you'd be good.
>
> > But that trick does not generalize if you go south instead, as you
> > have similar issues with -179, 174, 175, 176, 177, 178, and 179.
>
> > Has anybody solved this in Python, either for compass bearings or a
> > different domain?  I can think of kind of a brute force solution where
> > you keep rotating the sequence until the endpoints are closest
> > together mod 360, but I wonder if there is something more elegant.
>
> First, there isn't always a solution; what would you consider to be the
> median of [0, 90, 180, 270]?
>

You probably posted before seeing my recent post.  I agree that the
problem is ill-defined for certain cases.

> In the case where the bearings are clustered, one approach is to
> convert each bearing from polar to cartesian coordinates, compute the
> centroid, then convert back to polar coordinates, i.e.:
>
>         from math import degrees, radians, sin, cos, atan2
>
>         def mean(bearings):
>                 x = sum(sin(radians(a)) for a in bearings)
>                 y = sum(cos(radians(a)) for a in bearings)
>                 return degrees(atan2(x, y))
>
> Then, subtract the mean from each bearing, coerce all angles into the
> range -180..+180, calculate the median, add the mean, coerce back to
> 0..360.
>
>         def median(bearings):
>                 m = mean(bearings)
>                 bearings = [(a - m + 180) % 360 - 180 for a in bearings]
>                 bearings.sort()
>                 median = bearings[len(bearings) / 2]
>                 median += m
>                 median %= 360
>                 return median

Yep, that's exactly the solution I'm leaning toward now.



More information about the Python-list mailing list