Navigation Menu

Skip to content
This repository has been archived by the owner on Jan 10, 2024. It is now read-only.

Commit

Permalink
Resolved performance/memory issues.
Browse files Browse the repository at this point in the history
[#68953776]
  • Loading branch information
Michael Webb committed Oct 28, 2014
1 parent ef0fa3a commit a4973ab
Show file tree
Hide file tree
Showing 17 changed files with 199 additions and 153 deletions.
2 changes: 1 addition & 1 deletion app/controllers/predictions_controller.rb
Expand Up @@ -126,7 +126,7 @@ def withdraw
end

def statistics
@statistics ||= Response.wagers.statistics
@statistics ||= Statistics.new
end

def show_statistics?
Expand Down
4 changes: 2 additions & 2 deletions app/helpers/markup_helper.rb
Expand Up @@ -59,9 +59,9 @@ def classes(*args)
def certainty_heading(heading)
case heading
when "100"
link_to "#{heading}%", 'http://en.wikipedia.org/wiki/Almost_surely', :class => 'egg', :title => 'Almost surely'
link_to heading, 'http://en.wikipedia.org/wiki/Almost_surely', :class => 'egg', :title => 'Almost surely'
else
"#{heading}%"
heading
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/prediction.rb
Expand Up @@ -27,7 +27,7 @@ def self.future
sort(:deadline).not_private.not_withdrawn.includes(DEFAULT_INCLUDES).where("judgements.outcome IS NULL AND deadline > UTC_TIMESTAMP()")
end
def self.recent
rsort.not_private.not_withdrawn(:include => DEFAULT_INCLUDES)
rsort.not_private.not_withdrawn.all(:include => DEFAULT_INCLUDES)
end
def self.popular
opts = {
Expand Down
3 changes: 0 additions & 3 deletions app/models/response.rb
Expand Up @@ -25,9 +25,6 @@ class Response < ActiveRecord::Base
def predictions
collect(&:prediction).uniq
end
def statistics
Statistics.new(self)
end
def mean_confidence
average(:confidence).round unless empty?
end
Expand Down
107 changes: 62 additions & 45 deletions app/models/statistics.rb
@@ -1,107 +1,124 @@
class Statistics
include Enumerable
def initialize(wagers)

def initialize(wager_condition = nil)
setup_intervals
wagers = wagers.prefetch_joins if wagers.respond_to?(:prefetch_joins)
wagers.each do |wager|
interval_for(wager.relative_confidence).add(wager)
# Sums up the total count and the accuracy into bands
sql = <<-EOS
SELECT
ROUND(rel_conf - 5, -1) band,
COUNT(*) sample_size,
SUM(correct) / COUNT(*) accuracy
FROM
(SELECT
CASE
WHEN r.confidence >= 50 THEN r.confidence
ELSE 100 - r.confidence
END rel_conf,
CASE
WHEN (r.confidence >= 50 AND j.outcome = 1) OR
(r.confidence < 50 AND j.outcome = 0) THEN 1
ELSE 0
END correct
FROM responses r
INNER JOIN predictions p ON r.prediction_id = p.id
INNER JOIN (SELECT prediction_id, MAX(id) id
FROM judgements
GROUP BY prediction_id) mrj ON mrj.prediction_id = p.id
INNER JOIN judgements j ON j.id = mrj.id
WHERE r.confidence IS NOT NULL
#{"AND #{wager_condition}" if wager_condition}) rc
GROUP BY ROUND(rel_conf - 5, -1)
EOS
data = ActiveRecord::Base.connection.execute(sql)
data.each do |datum|
@intervals[datum[0]].update(datum)
end
self
end

def each
@intervals.keys.sort.each do |key|
yield @intervals[key]
end
self
end

def headings
collect(&:heading)
end

def sizes
collect(&:count)
end

def size
sizes.sum
end

def accuracies
collect(&:accuracy)
end

def image_url

#TODO: refactor (extract and such) and port to http://gchartrb.rubyforge.org/

# http://code.google.com/apis/chart/#shape_markers
# circle, red, first set, all points, 20px, on top of everything |
# circle, red, first set, all points, 20px, on top of everything |
blob = 'o,AAAAFF,0,-1,25,1'
# horizontal line, color, ignored, start point, end point
fifty_line = 'r,44FF44,0,0.49,0.51'
fifty_line = 'r,44FF44,0,0.49,0.51'
# line, color, data set, size, priority
joiner_line = 'D,FFCCCC,0,0,0.5,-1'
markers = [blob, fifty_line, joiner_line].join('|')
intervals = collect(&:heading).join(",")
intervals = self.headings.join(",").gsub("%", "")
accuracies = self.accuracies.join(",")
# Images api uses values 0..100 only
sizes = self.sizes.join(',')
image_size = "355x200"
grid_lines = "20,20"
# http://code.google.com/apis/chart/#multiple_axes_labels
axis = 'x,x,y,y'
# second axis, start at 50, go to 100
# second axis, start at 50, go to 100
axis_ranges = '0,50,100|1,0,100'
# if we specify they are scaled for us
data_ranges = "50,100,0,100,0,#{self.sizes.max}"
title = 'How sure are you? Confidence vs. Accuracy (%)'
axis_labels = "1:|Confidence (%)|3:|Accuracy"
axis_labels_positions = "1,50|3,50"

"http://chart.apis.google.com/chart?" + [
"cht=s", # scatterplot
# "chtt=#{title}",
"chg=#{grid_lines}",
"chds=#{data_ranges}",
"chm=#{markers}",
"chxt=#{axis}",
"chxr=#{axis_ranges}",
"chd=t:#{intervals}|#{accuracies}|#{sizes}",
"chd=t:#{intervals}|#{accuracies}|#{sizes}",
"chs=#{image_size}",
"chxl=#{axis_labels}",
"chxp=#{axis_labels_positions}",
].join('&')
end

def interval_for(percentage)
@intervals[((percentage)/10)*10]
end


def setup_intervals
@intervals = {100 => Interval.new("100",(100..100))}
[50,60,70,80,90].each do |start|
range = (start..start+9)
@intervals[start] = Interval.new("#{start}",range)
@intervals = {}
[50,60,70,80,90,100].each do |band|
@intervals[band] = Interval.new(band)
end
end

class Interval
attr_reader :heading
def initialize(heading,range)
@heading = heading
@range = range
@wagers = []
attr_reader :heading, :accuracy, :count
def initialize(band)
@heading = "#{band}%"
@accuracy = 0
@count = 0
end
def add(wager)
@wagers.push(wager)
end
def count
@wagers.reject {|w| w.unknown? }.size
end
def accuracy
count > 0 ? (correct.to_f/count*100).round : 0
end
def correct
@wagers.select {|w| w.correct? }.size

def update(row)
@accuracy = ((row[2] || 0) * 100).round.to_i
@count = row[1] || 0
end
end
end
4 changes: 3 additions & 1 deletion app/models/user.rb
Expand Up @@ -46,7 +46,9 @@ def self.[](login)
find_by_login!(login.gsub("[dot]","."))
end

delegate :statistics, :to => :wagers
def statistics
Statistics.new("r.user_id = #{id}")
end

def statistics_image_url
statistics.image_url
Expand Down
2 changes: 1 addition & 1 deletion app/views/predictions/_simple.html.erb
@@ -1,2 +1,2 @@
<span class='title'><%= link_to show_title(prediction.description), prediction %></span>
<%= render :partial => 'summary', :locals => {:prediction => prediction} -%>
<%= render :partial => 'predictions/summary', :locals => {:prediction => prediction} -%>
82 changes: 66 additions & 16 deletions db/seeds.rb
Expand Up @@ -5,39 +5,89 @@
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)

def random_future_day
random_int.days.from_now
end

def random_past_day
random_int.days.ago
end

def random_int
Random.rand(100)
end

puts "SEEDING!"

User.delete_all
Prediction.delete_all
Judgement.delete_all
Response.delete_all

first_user = User.new({:login => "Test", :password => "blahblah", :password_confirmation =>"blahblah"})
first_user.save!

second_user = User.new({:login => "Second", :password => "jajaja", :password_confirmation => "jajaja"})
second_user.save!

100.times do
prediction = first_user.predictions.build(:deadline => 24.days.from_now, :initial_confidence => 24, :creator => User.first, :description => "this event will come true")
prediction.save!
DATA_SIZE = 1000

first_response = prediction.responses.build({:user => second_user, :confidence => 10})
first_response.save!
puts "creating future unjudged"
DATA_SIZE.times do
prediction = first_user.predictions.build(
:deadline => random_future_day,
:initial_confidence => random_int,
:creator => User.first,
:description => "this event will come true"
)
prediction.save!
prediction.responses.create!(:user => second_user, :confidence => random_int)
end

100.times do
unjudged = second_user.predictions.build({:deadline => 24.days.ago, :initial_confidence => 33, :description => "this event has came past", :creator => second_user})
unjudged.save!
puts "creating past unjudged"
DATA_SIZE.times do
prediction = second_user.predictions.build(
:deadline => random_past_day,
:initial_confidence => random_int,
:description => "this event has came past",
:creator => second_user
)
prediction.save!
end

100.times do
judged = first_user.predictions.build({:deadline => 3.days.ago, :initial_confidence => 44, :description => "this event is judged", :creator => first_user})
puts "creating past judged"
DATA_SIZE.times do
judged = first_user.predictions.build(
:deadline => random_past_day,
:initial_confidence => random_int,
:description => "this event is judged",
:creator => first_user
)
judged.save!

judgement = Judgement.new({:user_id => second_user.id, :prediction_id => judged.id,:outcome => 0})
judgement.save!
Judgement.create!(
:user => second_user,
:prediction => judged,
:outcome => random_int % 2
)
end

puts "creating future commented"
DATA_SIZE.times do
commented = first_user.predictions.build(
:deadline => random_future_day,
:initial_confidence => random_int,
:description => "commented prediction",
:creator => first_user
)
commented.save!

commented = first_user.predictions.build({:deadline => 3.days.from_now, :initial_confidence => 77, :description => "commented prediction", :creator => first_user})
commented.save!

commented.responses.create(:user => second_user, :confidence => 44, :comment => "No way this will happen!")
commented.responses.create!(
:user => second_user,
:confidence => random_int,
:comment => "No way this will happen!"
)
end

puts "END SEEDING"
7 changes: 3 additions & 4 deletions spec/controllers/predictions_controller_spec.rb
Expand Up @@ -76,10 +76,9 @@
end

it 'should delegate statistics to the wagers collection' do
wagers = double('wagers')
Response.stub(:wagers).and_return(wagers)
wagers.stub(:statistics).and_return(:statistics)
controller.statistics.should == :statistics
stats = double(Statistics)
Statistics.stub(:new).and_return(stats)
controller.statistics.should == stats
end
end

Expand Down

0 comments on commit a4973ab

Please sign in to comment.