Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cumulative weighted average in ruby

Tags:

ruby

I am trying to implement a cumulative weighted average function that takes as argument the list

[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]

and returns (rounded to 2 decimal places here)

[3.1, 2.47, 4.08, 5.81]

For, example: 2.47 = (1000 * 3.1 + 500 * 1.2) / 1500.

I have currently solved this using the following piece of code:

def cumulative_weighted_average(list)
  cs = 0
  qu = 0
  res = list.inject([0]) do |s, el|
    cs += el[0] * el[1]
    qu += el[0]
    s + [cs.to_f / qu]
  end
  res.shift
  res
end

Is there a shorter (more compact) way of doing this?

Edit: Thanks for the answers below! The list will on average contain about 1000 entries, so not sure about the speed requirement. Since I need to be able to essentially track two values within the block, is there some extension of inject that allows you to write

list.inject([0,0]){ |s1, s2, el| ...}

where s1 and s2 are initialized to 0?

like image 284
apotry Avatar asked Mar 23 '23 02:03

apotry


2 Answers

I think this is what you want:

def cumulative_weighted_average list
  cs, qu = 0.0, 0.0
  list
  .map{|x, w| [cs += x * w, qu += x]}
  .map{|cs, qu| cs / qu}
end

cumulative_weighted_average([[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]])
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]


For the additional question, things like this are possible:
list.inject([0,0]){|(s1, s2), el| ...}
like image 65
sawa Avatar answered Apr 07 '23 06:04

sawa


Is there a shorted (more compact) way of doing this?

I can try for you..

arr = [[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
arr2 = (1..arr.size).map do |i| 
  b = arr.take(i)
  b.reduce(0){|sum,a| sum + a.reduce(:*)}/b.reduce(0){|sum,k| sum + k[0]}
end
arr2
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
like image 25
Arup Rakshit Avatar answered Apr 07 '23 05:04

Arup Rakshit