Last active
February 8, 2017 02:03
-
-
Save s4parke/6a00eae1bf163b749af7bc01a1fcd205 to your computer and use it in GitHub Desktop.
Calculates mean time of day using circular vector average
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class AverageTimes | |
# Accepts an array of objects (ActiveRecord collection probably) | |
# Computes the average time of day for a given :time_attr | |
# by converting times to degrees, calculating mean angle | |
# | |
# @sleep_diaries = Diary.all | |
# @average_times = AverageTimes( @sleep_diaries ) | |
# @average_bedtime = @average_times.of( :bed_at ) | |
include Enumerable | |
require 'complex' #not needed in ruby >= 2.0 | |
#require 'active_support' #not needed in rails >= 2 | |
#require 'active_support/core_ext/time' #not needed in rails >= 3 | |
SECONDS_IN_DAY = 86400 | |
SECONDS_IN_HOUR = 3600 | |
SECONDS_IN_MIN = 60 | |
PI = Math::PI | |
DEGREES = 360 | |
VALID_TIME = /(\d?\d):(\d\d)/ | |
def initialize( data ) | |
@collection = data | |
end | |
def of( time_attr ) | |
@times = get_times(time_attr) | |
raise "Invalid time attribute: #{time_attr}" unless times | |
average_time @times | |
end | |
def average_time( times ) | |
deg2time( mean_angle(times.map {|t| time2deg t}) ) | |
end | |
def get_times( time_attr = 'strip' ) | |
conditions = lambda {|x| | |
x.respond_to?(time_attr) && | |
is_valid_time.call(x.send(time_attr) ) | |
} | |
@collection.select(&conditions).map{|x| parse_time.call x.send(time_attr) } | |
end | |
private | |
def is_valid_time | |
proc { |t| !(t.to_s.match(VALID_TIME).blank?) } | |
end | |
def parse_time | |
proc { |t| | |
if t.class == Time | |
t | |
elsif is_valid_time.call(t) | |
Time.parse t.to_s | |
else | |
false | |
end | |
} | |
end | |
def deg2rad(d) | |
d * PI / 180 | |
end | |
def rad2deg(r) | |
r * 180 / PI | |
end | |
def mean_angle(deg) | |
rad2deg((deg.inject(0) {|z, d| z + Complex.polar(1, deg2rad(d))} / deg.length).arg) | |
end | |
def time2deg(t) | |
t.seconds_since_midnight * DEGREES.to_f / SECONDS_IN_DAY | |
end | |
def deg2time(d) | |
sec = (d % DEGREES) * SECONDS_IN_DAY / DEGREES | |
"%02d:%02d:%02d" % [sec/SECONDS_IN_HOUR, (sec%SECONDS_IN_HOUR)/SECONDS_IN_MIN, sec%SECONDS_IN_MIN] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment