lib/split/experiment.rb in split-0.7.3 vs lib/split/experiment.rb in split-0.8.0
- old
+ new
@@ -1,12 +1,13 @@
-module Split
+ module Split
class Experiment
attr_accessor :name
attr_writer :algorithm
attr_accessor :resettable
attr_accessor :goals
attr_accessor :alternatives
+ attr_accessor :alternative_probabilities
DEFAULT_OPTIONS = {
:resettable => true,
}
@@ -48,10 +49,27 @@
if alts[0].is_a? Hash
alts = alts[0].map{|k,v| {k => v} }
end
end
+ if alts.empty?
+ exp_config = Split.configuration.experiment_for(name)
+ if exp_config
+ alts = load_alternatives_from_configuration
+ options[:goals] = load_goals_from_configuration
+ options[:resettable] = exp_config[:resettable]
+ options[:algorithm] = exp_config[:algorithm]
+ end
+ end
+
+ self.alternatives = alts
+ self.goals = options[:goals]
+ self.algorithm = options[:algorithm]
+ self.resettable = options[:resettable]
+
+ # calculate probability that each alternative is the winner
+ @alternative_probabilities = {}
alts
end
def self.all
ExperimentCatalog.all
@@ -249,9 +267,137 @@
exp_config = Split.redis.hgetall(experiment_config_key)
self.resettable = exp_config['resettable']
self.algorithm = exp_config['algorithm']
self.alternatives = load_alternatives_from_redis
self.goals = load_goals_from_redis
+ end
+
+ def calc_winning_alternatives
+ if goals.empty?
+ self.estimate_winning_alternative
+ else
+ goals.each do |goal|
+ self.estimate_winning_alternative(goal)
+ end
+ end
+
+ calc_time = Time.now.day
+
+ self.save
+ end
+
+ def estimate_winning_alternative(goal = nil)
+ # TODO - refactor out functionality to work with and without goals
+
+ # initialize a hash of beta distributions based on the alternatives' conversion rates
+ beta_params = calc_beta_params(goal)
+
+ winning_alternatives = []
+
+ Split.configuration.beta_probability_simulations.times do
+ # calculate simulated conversion rates from the beta distributions
+ simulated_cr_hash = calc_simulated_conversion_rates(beta_params)
+
+ winning_alternative = find_simulated_winner(simulated_cr_hash)
+
+ # push the winning pair to the winning_alternatives array
+ winning_alternatives.push(winning_alternative)
+ end
+
+ winning_counts = count_simulated_wins(winning_alternatives)
+
+ @alternative_probabilities = calc_alternative_probabilities(winning_counts, Split.configuration.beta_probability_simulations)
+
+ write_to_alternatives(@alternative_probabilities, goal)
+
+ self.save
+ end
+
+ def write_to_alternatives(alternative_probabilities, goal = nil)
+ alternatives.each do |alternative|
+ alternative.set_p_winner(@alternative_probabilities[alternative], goal)
+ end
+ end
+
+ def calc_alternative_probabilities(winning_counts, number_of_simulations)
+ alternative_probabilities = {}
+ winning_counts.each do |alternative, wins|
+ alternative_probabilities[alternative] = wins / number_of_simulations.to_f
+ end
+ return alternative_probabilities
+ end
+
+ def count_simulated_wins(winning_alternatives)
+ # initialize a hash to keep track of winning alternative in simulations
+ winning_counts = {}
+ alternatives.each do |alternative|
+ winning_counts[alternative] = 0
+ end
+ # count number of times each alternative won, calculate probabilities, place in hash
+ winning_alternatives.each do |alternative|
+ winning_counts[alternative] += 1
+ end
+ return winning_counts
+ end
+
+ def find_simulated_winner(simulated_cr_hash)
+ # figure out which alternative had the highest simulated conversion rate
+ winning_pair = ["",0.0]
+ simulated_cr_hash.each do |alternative, rate|
+ if rate > winning_pair[1]
+ winning_pair = [alternative, rate]
+ end
+ end
+ winner = winning_pair[0]
+ return winner
+ end
+
+ def calc_simulated_conversion_rates(beta_params)
+ # initialize a random variable (from which to simulate conversion rates ~beta-distributed)
+ rand = SimpleRandom.new
+ rand.set_seed
+
+ simulated_cr_hash = {}
+
+ # create a hash which has the conversion rate pulled from each alternative's beta distribution
+ beta_params.each do |alternative, params|
+ alpha = params[0]
+ beta = params[1]
+ simulated_conversion_rate = rand.beta(alpha, beta)
+ simulated_cr_hash[alternative] = simulated_conversion_rate
+ end
+
+ return simulated_cr_hash
+ end
+
+ def calc_beta_params(goal = nil)
+ beta_params = {}
+ alternatives.each do |alternative|
+ conversions = goal.nil? ? alternative.completed_count : alternative.completed_count(goal)
+ alpha = 1 + conversions
+ beta = 1 + alternative.participant_count - conversions
+
+ params = [alpha, beta]
+
+ beta_params[alternative] = params
+ end
+ return beta_params
+ end
+
+ def calc_time=(time)
+ Split.redis.hset(experiment_config_key, :calc_time, time)
+ end
+
+ def calc_time
+ Split.redis.hget(experiment_config_key, :calc_time)
+ end
+
+ def jstring(goal = nil)
+ unless goal.nil?
+ jstring = name + "-" + goal
+ else
+ jstring = name
+ end
end
protected
def experiment_config_key