233

How would get find an average from an array?

If I have the array:

[0,4,8,2,5,0,2,6]

Averaging would give me 3.375.

thumbtackthief
  • 6,093
  • 10
  • 41
  • 87
dotty
  • 40,405
  • 66
  • 150
  • 195
  • 12
    If you're getting 21.75 as the average of those numbers, something's very wrong... – ceejayoz Aug 27 '09 at 14:06
  • 2
    dotty, not sure how you got 21.75 but the average/mean for that set of data is 3.375 and the sum is 27. i'm not sure what sort of aggregation function would yield 21.75. Please double check and make sure that average is really what you're after! – Paul Sasik Aug 27 '09 at 14:07
  • 2
    I have NO idea where i got 21.75 from. Must had press something like 0+48+2+5+0+2+6 on the calculator! – dotty Aug 27 '09 at 14:19
  • 17
    Since this is also tagged ruby-on-rails, active record calculations are worth looking into if you are averaging an ActiveRecord array. Person.average(:age, :country => 'Brazil') returns the average age of people from Brazil. Pretty cool! – Kyle Heironimus Oct 27 '10 at 17:35

24 Answers24

270

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.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
John Feminella
  • 303,634
  • 46
  • 339
  • 357
121
a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375

A version of this that does not use instance_eval would be:

a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
BBonifield
  • 4,983
  • 19
  • 36
Corban Brook
  • 21,270
  • 4
  • 28
  • 34
  • Hi, Corban. I'm looking for some documentation on ":+". Does that fit into a "class" of symbols that a function, like reduce, can use? Thank you. – Zach Young Jul 26 '11 at 02:16
  • This one could be considered too clever, but very useful when you're inside of the console session and don't want to be extending Array class or storing Array in separate variable, so +1 definitely! – Ev Dolzhenko Oct 03 '11 at 08:57
  • 6
    I don't think it is too clever. I think it solves the problem idiomatically. I.e., it uses reduce, which is exactly correct. Programmers should be encouraged to understand what is correct, why it is correct, and then propagate. For a trivial operation like average, true, one doesn't need to be "clever". But by understanding what "reduce" is for a trivial case, one can then start applying it to much more complex problems. upvote. – pduey Feb 07 '12 at 17:50
  • 3
    why the need for instance_eval here? – tybro0103 May 09 '12 at 16:13
  • 11
    `instance_eval` lets you run the code while only specifying `a` once, so it can be chained with other commands. I.e. `random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } ` instead of `random = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size` – Benjamin Manns Jul 10 '12 at 17:56
  • 3
    I don't know, using instance_eval this way just seems weird, and it has a lot of gotchas associated with it that make this approach a bad idea, IMO. (For example, if you tried to access and instance variable or a method on `self` inside that block, you'd run into problems.) `instance_eval` is more for metaprogramming or DSL. – Ajedi32 May 21 '15 at 21:55
  • 1
    @Ajedi32 I agree, don't use this in your application code. It was however very nice to be able to paste into my repl (: – animatedgif Nov 27 '17 at 20:31
  • Thanks, this was just what I was looking for! This is a _perfect_ appropriate use of `instance_eval`. I do not understand the nay-sayers. If you already have your array in a variable, then sure, `a.reduce(:+) / a.size.to_f ` is pretty reasonable. But if you want to "in line" find the mean of an array _literal_ or an array that is returned from a function/expression — without duplicating the entire expression (`[0,4,8].reduce(:+) / [0,4,8].length.to_f`, for example, is abhorrent) or being _required_ to assign to a local, then `instance_eval` option is a beautiful, elegant, idiomatic solution!! – Tyler Rick Jun 08 '21 at 18:36
  • 1
    `instance_eval` is analogous to using `tap`, `yield_self`, … when you are dealing with a chain of method calls: _do use it_ whenever it's appropriate and helpful! And in this case, I absolutely believe that it is. – Tyler Rick Jun 08 '21 at 18:39
  • Or if you're looking for a core extension that adds this to the `Array` class, I'd recommend the facets gem (`require 'facets/array/average'`). Then you can just do `array.average`. And, from looking at the source, it turns out they do the _exact_ same thing as the `instance_eval` approach above. The only difference is that it's implemented as a method—which of course _already_ has `self` pointing to itself—instead of a block): ```def average; return nil if empty?; reduce(:+) / length.to_f; end``` Main advantage of this is that it's even more concise/readable and it handles the empty? case. – Tyler Rick Jun 08 '21 at 18:53
96

I believe the simplest answer is

list.reduce(:+).to_f / list.size
Udo Held
  • 12,314
  • 11
  • 67
  • 93
Shu Wu
  • 1,342
  • 11
  • 7
  • 1
    It took me a moment to find it -- `reduce` is a method of the `Enumerable` mixin used by `Array`. And despite its name, I agree with the @ShuWu ... unless you're using Rails which implements `sum`. – Tom Harrison Aug 31 '13 at 23:21
  • I see solutions here, that I know that look extremely neat, but I'm afraid if I read my code in the future they'll like gibberish. Thanks for the clean solution! – patm Sep 11 '14 at 11:16
  • On my system this is 3x faster than the accepted answer. – builder-7000 Nov 13 '19 at 07:34
58

I was hoping for Math.average(values), but no such luck.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
Mohammad
  • 3,276
  • 2
  • 19
  • 35
Denny Abraham
  • 812
  • 6
  • 6
  • 3
    I didn't realize #sum was added by Rails! Thanks for pointing that out. – Denny Abraham Jul 26 '11 at 11:29
  • 15
    After Christmas 2016 (Ruby 2.4), Array _will_ have a `sum` method, so this appears to be a correct answer after 6 years, worthy of the Nostradamus award. – steenslag Nov 28 '16 at 21:57
54

Ruby versions >= 2.4 has an Enumerable#sum method.

And to get floating point average, you can use Integer#fdiv

arr = [0,4,8,2,5,0,2,6]

arr.sum.fdiv(arr.size)
# => 3.375

For older versions:

arr.reduce(:+).fdiv(arr.size)
# => 3.375
Santhosh
  • 28,097
  • 9
  • 82
  • 87
12

Some benchmarking of top solutions (in order of most efficient):

Large Array:

array = (1..10_000_000).to_a

Benchmark.bm do |bm|
  bm.report { array.instance_eval { reduce(:+) / size.to_f } }
  bm.report { array.sum.fdiv(array.size) }
  bm.report { array.sum / array.size.to_f }
  bm.report { array.reduce(:+).to_f / array.size }
  bm.report { array.reduce(:+).try(:to_f).try(:/, array.size) }
  bm.report { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size }
  bm.report { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) }
end


    user     system      total        real
0.480000   0.000000   0.480000   (0.473920)
0.500000   0.000000   0.500000   (0.502158)
0.500000   0.000000   0.500000   (0.508075)
0.510000   0.000000   0.510000   (0.512600)
0.520000   0.000000   0.520000   (0.516096)
0.760000   0.000000   0.760000   (0.767743)
1.530000   0.000000   1.530000   (1.534404)

Small Arrays:

array = Array.new(10) { rand(0.5..2.0) }

Benchmark.bm do |bm|
  bm.report { 1_000_000.times { array.reduce(:+).to_f / array.size } }
  bm.report { 1_000_000.times { array.sum / array.size.to_f } }
  bm.report { 1_000_000.times { array.sum.fdiv(array.size) } }
  bm.report { 1_000_000.times { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size } }
  bm.report { 1_000_000.times { array.instance_eval { reduce(:+) / size.to_f } } }
  bm.report { 1_000_000.times { array.reduce(:+).try(:to_f).try(:/, array.size) } }
  bm.report { 1_000_000.times { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) } }
end


    user     system      total        real
0.760000   0.000000   0.760000   (0.760353)
0.870000   0.000000   0.870000   (0.876087)
0.900000   0.000000   0.900000   (0.901102)
0.920000   0.000000   0.920000   (0.920888)
0.950000   0.000000   0.950000   (0.952842)
1.690000   0.000000   1.690000   (1.694117)
1.840000   0.010000   1.850000   (1.845623)
stevenspiel
  • 5,775
  • 13
  • 60
  • 89
  • Your benchmark is a bit wrong. benchmark/ips is actually better for these kind of comparisons. Also I would suggest to use an Array populated randomly with negative and positive numbers as well as floats, to get a more realistic result. You'll find that instance_eval is slower than array.sum.fdiv . By about 8x for floats. and about x1.12 for integers. Also, different OSes will give different results. on my mac some of these methods are 2 times as slow than on my Linux Droplet – konung Sep 07 '18 at 18:38
  • Also sum method uses Gauss's formula, on ranges instead of calculating the sum. – Santhosh Sep 21 '18 at 03:23
12

Without having to repeat the array (e.g. perfect for one-liners):

[1, 2, 3, 4].then { |a| a.sum.to_f / a.size }
Dorian
  • 7,749
  • 4
  • 38
  • 57
4
class Array
  def sum 
    inject( nil ) { |sum,x| sum ? sum+x : x }
  end

  def mean 
    sum.to_f / size.to_f
  end
end

[0,4,8,2,5,0,2,6].mean
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
astropanic
  • 10,800
  • 19
  • 72
  • 132
  • 2
    This returns incorrect values because of integer division. Try it with, for example, [2,3].mean, which returns 2 instead of 2.5. – John Feminella Aug 27 '09 at 14:06
  • 1
    Why should an empty array have a sum of `nil` rather than 0? – Andrew Grimm May 23 '11 at 11:59
  • 1
    Because you can get the difference between [] and [0]. And I think everybody who want a real mean can make use of to_i or replace the above nil with an 0 – astropanic May 23 '11 at 12:58
4

Let me bring something into competition which solves the division by zero problem:

a = [1,2,3,4,5,6,7,8]
a.reduce(:+).try(:to_f).try(:/,a.size) #==> 4.5

a = []
a.reduce(:+).try(:to_f).try(:/,a.size) #==> nil

I must admit, however, that "try" is a Rails helper. But you can easily solve this:

class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end

BTW: I think it is correct that the average of an empty list is nil. The average of nothing is nothing, not 0. So that is expected behavior. However, if you change to:

class Array;def avg;reduce(0.0,:+).try(:/,size);end;end

the result for empty Arrays won't be an exception as I had expected but instead it returns NaN... I've never seen that before in Ruby. ;-) Seems to be a special behavior of the Float class...

0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
hurikhan77
  • 5,881
  • 3
  • 32
  • 47
3

what I don't like about the accepted solution

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

is that it does not really work in a purely functional way. we need a variable arr to compute arr.size at the end.

to solve this purely functionally we need to keep track of two values: the sum of all elements, and the number of elements.

[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
    [ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5   

Santhosh improved on this solution: instead of the argument r being an array, we could use destructuring to immediatly pick it apart into two variables

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   [ sum + ele, size + 1 ]
end.inject(:/)

if you want to see how it works, add some puts:

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   r2 = [ sum + ele, size + 1 ]
   puts "adding #{ele} gives #{r2}"
   r2
end.inject(:/)

adding 5 gives [5.0, 1]
adding 6 gives [11.0, 2]
adding 7 gives [18.0, 3]
adding 8 gives [26.0, 4]
=> 6.5

We could also use a struct instead of an array to contain the sum and the count, but then we have to declare the struct first:

R=Struct.new(:sum, :count)
[5, 6, 7, 8].inject( R.new(0.0, 0) ) do |r,ele|
    r.sum += ele
    r.count += 1
    r
end.inject(:/)
bjelli
  • 9,752
  • 4
  • 35
  • 50
  • This is the first time I see `end.method` used in ruby, thanks for this! – Epigene Apr 14 '15 at 13:18
  • The array passed to inject method can be dispersed. `arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)` – Santhosh Apr 01 '18 at 08:08
  • @Santhosh: yes, that's a lot more readable! I would not call this "dispersing" though, I would call it "destructuring" http://tony.pitluga.com/2011/08/08/destructuring-with-ruby.html – bjelli Apr 02 '18 at 12:16
3

For public amusement, yet another solution:

a = 0, 4, 8, 2, 5, 0, 2, 6
a.reduce [ 0.0, 0 ] do |(s, c), e| [ s + e, c + 1 ] end.reduce :/
#=> 3.375
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
3

Another simple solution too

arr = [0,4,8,2,5,0,2,6]
arr.sum(0.0) / arr.size
yvzlkrkt
  • 35
  • 4
3

You can choose one of the below solutions as you wish.

Bruteforce

[0,4,8,2,5,0,2,6].sum.to_f / [0,4,8,2,5,0,2,6].size.to_f

=> 3.375

Method

def avg(array)
  array.sum.to_f / array.size.to_f
end  

avg([0,4,8,2,5,0,2,6])
=> 3.375

Monkey Patching

class Array
  def avg
    sum.to_f / size.to_f
  end
end

[0,4,8,2,5,0,2,6].avg
=> 3.375

But I don't recommend to monkey patch the Array class, this practice is dangerous and can potentially lead to undesirable effects on your system.

For our good, ruby language provides a nice feature to overcome this problem, the Refinements, which is a safe way for monkey patching on ruby.

To simplify, with the Refinements you can monkey patch the Array class and the changes will only be available inside the scope of the class that is using the refinement! :)

You can use the refinement inside the class you are working on and you are ready to go.

Refinements

module ArrayRefinements
  refine Array do
    def avg
      sum.to_f / size.to_f
    end
  end
end

class MyClass
  using ArrayRefinements

  def test(array)
    array.avg
  end
end

MyClass.new.test([0,4,8,2,5,0,2,6])
=> 3.375
Victor
  • 1,904
  • 18
  • 18
2

Don't have ruby on this pc, but something to this extent should work:

values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
 total += val
end

average = total/values.size
saret
  • 2,217
  • 13
  • 12
2

Add Array#average.

I was doing the same thing quite often so I thought it was prudent to just extend the Array class with a simple average method. It doesn't work for anything besides an Array of numbers like Integers or Floats or Decimals but it's handy when you use it right.

I'm using Ruby on Rails so I've placed this in config/initializers/array.rb but you can place it anywhere that's included on boot, etc.

config/initializers/array.rb

class Array

  # Will only work for an Array of numbers like Integers, Floats or Decimals.
  #
  # Throws various errors when trying to call it on an Array of other types, like Strings.
  # Returns nil for an empty Array.
  #
  def average
    return nil if self.empty?

    self.sum.to_d / self.size
  end

end
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
  • Does not work if the average is a decimal value. For example: `[1, 2, 3, 4].average`. It is returning `2` instead of `2.5`. If somebody desires a fully working solution with Monkey Patch, it can be found right here: https://stackoverflow.com/a/65337711/7644846 – Victor Dec 18 '20 at 16:38
  • @Victor You're right! Thanks for the heads up. Seems like a pretty obvious omission on my part. I fixed it by converting the `sum` to a `BigDecimal` before dividing by the `size`. Now, `[1, 2, 3, 4].average #=> 0.25e1`. Thanks again. – Joshua Pinter Dec 21 '20 at 04:59
1
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
erik
  • 6,406
  • 3
  • 36
  • 36
  • 4
    This will return incorrect values because of integer division. For example, if a is [2, 3], the expected result is 2.5, but you'll return 2. – John Feminella Aug 27 '09 at 14:03
1
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375

Solves divide by zero, integer division and is easy to read. Can be easily modified if you choose to have an empty array return 0.

I like this variant too, but it's a little more wordy.

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : [a.reduce(:+), a.size.to_f].reduce(:/)
=> 3.375
Matt Stevens
  • 1,104
  • 1
  • 12
  • 24
1
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Rahul Patel
  • 1,386
  • 1
  • 14
  • 16
1

This method can be helpful.

def avg(arr)
  val = 0.0

  arr.each do |n|
    val += n
  end

  len = arr.length

  val / len 
end

p avg([0,4,8,2,5,0,2,6])
  • 1
    Welcome to stack overflow here Original Poster of the question wants the answer as 3.375 and your solution gives 3. i,e 27 / 8 = 3. – Ajay Barot Jan 25 '19 at 11:20
  • Thank you for your comments. I know the Original Poster of the question wants answer as 3.375 and thats what this method does as I have given variable 'var' a float value (i.e; 0.0). Munim Munna I have to agree with u there is indeed a similar ans. – Kishor Budhathoki Jan 29 '19 at 07:34
0

print array.sum / array.count is how i've done it

seasonalz
  • 61
  • 7
0

I really like to define a mean() method so my code is more expressive.

I usually want to ignore nil by default, so here's what I define

def mean(arr)
  arr.compact.inject{ |sum, el| sum + el }.to_f / arr.compact.size
end

mean([1, nil, 5])
=> 3.0

If you want to keep the nils, just remove both the .compacts.

stevec
  • 41,291
  • 27
  • 223
  • 311
0

A much faster solution than .inject is :

arr.sum(0.0) / arr.size

See this article for ref: https://andycroll.com/ruby/calculate-a-mean-average-from-a-ruby-array/

MMafiews
  • 23
  • 6
-1

You could try something like the following:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
  • Does not work when the average should be a decimal value. Test this code with this array: `a = [1, 2, 3, 4]`. The average should be `2.5` instead of `2.0`. It should be `a.sum.to_f / a.length.to_f` instead. – Victor Dec 18 '20 at 16:35
-1
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

Short but using instance variable

  • 2
    I'd do `a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size` rather than create an instance variable. – Andrew Grimm Aug 01 '11 at 23:50