On Jan 22, 10:29 pm, Nobody <nob...@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. -- http://mail.python.org/mailman/listinfo/python-list