Flethuseo
Flethuseo

Reputation: 6199

Finding the average of a table

I need to find the averages for all columns in the same row of a particular value of the first column. So for a table like the following:

0   11  12  40
1   22  24  92
0   12  13  45
1   24  26  90
2   33  36  138
1   22  24  80
2   36  39  135
0   11  12  46
2   33  36  120

I want the resulting table of averages:

0   11.33333333 12.33333333 43.66666667     
1   22.66666667 24.66666667 87.33333333     
2   34  37  131

So for example for the value 0.. I take the averages of (11 + 12 + 11)/3 on the second column, (12 + 13 + 12)/3 on the second, and (46 + 45 + 40)/3 on the third column

Upvotes: 0

Views: 174

Answers (3)

fl00r
fl00r

Reputation: 83680

data = "0   11  12  40
        1   22  24  92
        0   12  13  45
        1   24  26  90
        2   33  36  138
        1   22  24  80
        2   36  39  135
        0   11  12  46
        2   33  36  120"

avgs = data.split(/\n/).map{|d| d.split(/\t/)}.group_by{|d| d[0] }.each{|a,b| b.each(&:shift) }.inject({}){|avg, (k, ar)| avg[k] = ar.inject([0,0,0]){|av,(a,b,c)| av[0]+=a.to_f; av[1]+=b.to_f; av[2]+=c.to_f; av}.map{|e| e/ar.size}; avg}
#=> {"0"=>[11.333333333333332, 12.333333333333332, 43.66666666666667], "1"=>[22.666666666666664, 24.666666666666664, 87.33333333333334], "2"=>[34.0, 37.0, 131.0]}

to print it:

avgs.each{|k, arr| puts [k,*arr].join("\t") }
#=> 0   11.333333333333332  12.333333333333332  43.66666666666667
#=> 1   22.666666666666664  24.666666666666664  87.33333333333334
#=> 2   34.0    37.0    131.0

UPD

I've cleaned my method a little:

avgs = data.split(/\n/).
            map{|d| d.split(/\t/).map(&:to_f)}.
            group_by(&:first).
            inject({}){|avg, (k, ar)| avg[k] = ar.transpose[1..-1].map{|av| av.inject(:+)/av.size}; avg} 

Upvotes: 2

sawa
sawa

Reputation: 168199

a = [
  [0,   11,  12,  40],
  [1,   22,  24,  92],
  [0,   12,  13,  45],
  [1,   24,  26,  90],
  [2,   33,  36,  138],
  ...
]

def average array, index
  array = array.select{|l| l[0] == index}
  n = array.length.to_f
  array.transpose.drop(1).map {|values| values.inject(0){|s, v| s += v}/n}
end

average(a, 0) # => [11.33333333, 12.33333333, 43.66666667]

To have all of them at once:

array.group_by{|l| l[0]}.map do |k, array|
  n = array.length.to_f
  [k, array.transpose.drop(1).map {|values| values.inject(0){|s, v| s += v}/n}]
end

# =>
[
  [0, [11.33333333, 12.33333333, 43.66666667]],     
  [1, [22.66666667, 24.66666667, 87.33333333]]     
  [2, [34.0,  37.0,  131.0]]
]

Upvotes: 3

Andrei S
Andrei S

Reputation: 6516

say you have a table table = [ [0, 11, 12, 40] , [1, 22, 24, 92] .... [2, 33, 36, 120] ]

i'm presuming the table has a fixed number of elements on a line

first_elements_array = table.map { |line| line[0] }.uniq! #get [0,1,2] from your table

avgs = [] #will contain your [ [0, 11.333, 12.3333, 43.66667], ... ]

for element in first_elements_array
  #i need an array with 4 zeros - the number of elements on a line
  temp_sum = [0,0,0,0]
  count = 0 #number of lines that start with 0, or 1, or 2 etc

  #read the lines that start with 0, then 1, then 2 etc
  for line in table
    if line[0] == element
      count += 1
      #add in temp_sum the new line found, element by element
      (0...line.length).each do |i|
         temp_sum[i] += line[i]
      end
    end
  end

  line_avg = []

  #so, temp_sum contains the sum for one line that starts with 0 or 1 or 2 etc. now calculate the average
  for sum in temp_sum
    line_avg << sum/count
  end

  #... and push it in an array
  avgs << line_avg

end

this could probably be done more elegant so feel free to adapt it

also, haven't had any time to test it, let me know if it works

Upvotes: 1

Related Questions