Ruby: Average Array of Times

Ruby: Average array of times

So you need do define a function that can calculate the average of times formatted as strings. Convert the data to minutes, avg the total minutes and then back to a time.

I would do it something like this:

a =  ['18:35', '19:07', '23:09']

def avg_of_times(array_of_time)
size = array_of_time.size
avg_minutes = array_of_time.map do |x|
hour, minute = x.split(':')
total_minutes = hour.to_i * 60 + minute.to_i
end.inject(:+)/size
"#{avg_minutes/60}:#{avg_minutes%60}"
end

p avg_of_times(a) # = > "20:17"

Then when you call you function you check if any/all items in your array is formatted as a time. Maybe using regexp.

Calculate average time from array of hashes

require 'time'

array = [
{"startTime"=>"2015-12-05T07:49:30.000Z", "endTime"=>"2015-12-05T15:56:30.000Z"},
{"startTime"=>"2015-12-04T07:02:30.000Z", "endTime"=>"2015-12-04T14:59:30.000Z"},
{"id"=>"5660614e8a3b090700bad845", "userId"=>"55cd59542d98600100397c12", "day"=>"2015-12-03", "startTime"=>"2015-12-03T06:02:32.000Z", "endTime"=>"2015-12-03T13:38:32.000Z"},
{"startTime"=>"2015-12-02T09:17:33.000Z", "endTime"=>"2015-12-02T15:27:33.000Z"},
{"startTime"=>"2015-12-01T05:45:36.000Z", "endTime"=>"2015-12-01T13:50:36.000Z"}
]

times = array.map{|el| el['startTime']}.map{|el| Time.parse(el) }
#=> [2015-12-05 07:49:30 UTC, 2015-12-04 07:02:30 UTC, 2015-12-03 06:02:32 UTC, 2015-12-02 09:17:33 UTC, 2015-12-01 05:45:36 UTC]

average_time = Time.at(times.map(&:to_f).inject(:+) / times.size)
#=> 2015-12-03 08:11:32 +0100

Average array of times as strings in Ruby

(sz = array.reject    {|t| t == '0:00:00' }).
map {|t| Time.parse t }.
reduce(0) {|a, t| a += t.to_i }.to_f / sz.size

You want to group these things into functional operations. Reject the stuff you don't need, then act on the rest.

Here, reduce is a viable way to get an array average, and has been answered before.


here is an alternative that is slightly more concise, slightly more cryptic

(sz = array.reject {|t| t == '0:00:00'     }).
map {|t| Time.parse(t).to_i }.
reduce(:+).to_f / sz.size

How do I create an average from a Ruby array?

Try this:

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

Note the .to_f, which you'll want for avoiding any problems from integer division. You can also do:

arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5

You can define it as part of Array as another commenter has suggested, but you need to avoid integer division or your results will be wrong. Also, this isn't generally applicable to every possible element type (obviously, an average only makes sense for things that can be averaged). But if you want to go that route, use this:

class Array
def sum
inject(0.0) { |result, el| result + el }
end

def mean
sum / size
end
end

If you haven't seen inject before, it's not as magical as it might appear. It iterates over each element and then applies an accumulator value to it. The accumulator is then handed to the next element. In this case, our accumulator is simply an integer that reflects the sum of all the previous elements.

Edit: Commenter Dave Ray proposed a nice improvement.

Edit: Commenter Glenn Jackman's proposal, using arr.inject(:+).to_f, is nice too but perhaps a bit too clever if you don't know what's going on. The :+ is a symbol; when passed to inject, it applies the method named by the symbol (in this case, the addition operation) to each element against the accumulator value.

Ruby average value of array of time values (fixnums)

def average_days(a)
seconds = a.inject(:+) / a.size
minutes = seconds / 60
days = (minutes / 1440).round
"#{days} days"
end

Sum and Average Array of Array

output = [
{"name"=>"aaa", "job"=>"a", "pay"=> 2 },
{"name"=>"zzz", "job"=>"a", "pay"=> 4 },
{"name"=>"xxx", "job"=>"a", "pay"=> 6 },
{"name"=>"yyy", "job"=>"a", "pay"=> 8 },
{"name"=>"aaa", "job"=>"b", "pay"=> 2 },
{"name"=>"zzz", "job"=>"b", "pay"=> 4 },
{"name"=>"xxx", "job"=>"b", "pay"=> 6 },
{"name"=>"yyy", "job"=>"b", "pay"=> 10 },
]

output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end

The group_by method will transform your list into a hash with the following structure:

{"a"=>[{"name"=>"aaa", "job"=>"a", "pay"=>2}, ...], "b"=>[{"name"=>"aaa", "job"=>"b", ...]}

After that, for each pair of that hash, we want to calculate the mean of its 'pay' values, and return a pair [key, mean]. We use a map for that, returning a pair with:

  1. They key itself ("a" or "b").
  2. The mean of the values. Note that the values list has the form of a list of hashes. To retrieve the values, we need to extract the last element of each pair; that's what list.map { |obj| obj['pay'] } is used for. Finally, calculate the mean by suming all elements with .reduce(:+) and dividing them by the list size as a float.

Not the most efficient solution, but it's practical.


Comparing the answer with @EricDuminil's, here's a benchmark with a list of size 8.000.000:

def Wikiti(output)
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
end

def EricDuminil(output)
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
end

require 'benchmark'

Benchmark.bm do |x|
x.report('Wikiti') { Wikiti(output) }
x.report('EricDuminil') { EricDuminil(output) }
end

user system total real
Wikiti 4.100000 0.020000 4.120000 ( 4.130373)
EricDuminil 4.250000 0.000000 4.250000 ( 4.272685)

Group by key in array and get max and average

So, when you do "data": { ... }, the data actually becomes a symbol, not a string so you would need to do something like:

@data[:data].group_by { |data| data[:datetime].split('-')[0] }

in order to group by the :datetime key, ignoring the time portion (I assume, the time portion is just everything after the -). Then you end up with a hash looking like:

{"20160815"=>[{:temp=>22, :wind=>351.0, :datetime=>"20160815-0330"}, {:temp=>21, :wind=>321.0, :datetime=>"20160815-0345"}]}

and to find the max :temp and average of the :wind you can do:

results = @data[:data].group_by { |data| data[:datetime].split('-')[0] }.map do |date, values|
[date, {
maximum_temp: values.max_by { |value| value[:temp] }[:temp],
average_wind: values.sum { |value| value[:wind] }.to_f / values.length
}]
end.to_h
# => {"20160815"=>{:maximum_temp=>22, :average_wind=>336.0}}

Array Average Rails

 average = temp.sum / temp.size.to_f


Related Topics



Leave a reply



Submit