planout 0.0.4 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +7 -3
- data/examples/{planout → plan_out}/voting_experiment.rb +2 -2
- data/lib/plan_out.rb +8 -0
- data/lib/{planout → plan_out}/assignment.rb +1 -1
- data/lib/{planout → plan_out}/experiment.rb +1 -1
- data/lib/plan_out/op_random.rb +84 -0
- data/lib/{planout/op_simple.rb → plan_out/operator.rb} +14 -2
- data/lib/{planout → plan_out}/simple_experiment.rb +1 -1
- data/lib/plan_out/version.rb +3 -0
- data/planout.gemspec +2 -2
- data/test/{planout → plan_out}/assignment_test.rb +1 -1
- data/test/{planout → plan_out}/experiment_test.rb +6 -6
- data/test/{planout → plan_out}/operator_test.rb +1 -1
- data/test/test_helper.rb +1 -1
- metadata +17 -23
- data/lib/planout.rb +0 -16
- data/lib/planout/bernoulli_trial.rb +0 -11
- data/lib/planout/op_random.rb +0 -28
- data/lib/planout/operator.rb +0 -14
- data/lib/planout/random_float.rb +0 -11
- data/lib/planout/random_integer.rb +0 -11
- data/lib/planout/uniform_choice.rb +0 -12
- data/lib/planout/version.rb +0 -3
- data/lib/planout/weighted_choice.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6ac7ac834d56d050fdc5d7a354f963e7fbef99
|
4
|
+
data.tar.gz: 5a28a98c04bf3560be6d2ce89662f083ed249ba5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41b8aac76573def514e18e6676af436340f9f86a7e9cfb2c7295c46e8a10620a8ed53ffed8b883503de7eb84d391d96917cb437145bc4351e185da56d015a1df
|
7
|
+
data.tar.gz: c2b98270f2d422568adc7d12b3c6fad917a36c0ef744a3aa7312fa2c71acc0a1c6ee006a2cc33d8bfdbd510d6ff52d0891186cd0e7a8ed0fb9163b4d3738d870
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# PlanOut
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/planout.svg)](http://badge.fury.io/rb/planout)
|
3
|
+
[![Build Status](https://travis-ci.org/facebook/planout.svg)](https://travis-ci.org/facebook/planout)
|
4
|
+
|
1
5
|
## Overview
|
2
6
|
This is a rough implementation of the Experiment / logging infrasture for running PlanOut experiments, with all the random assignment operators available in Python. This port is nearly a line-by-line port, and produces assignments that are completely consistent with those produced by the Python reference implementation.
|
3
7
|
|
@@ -24,7 +28,7 @@ This defines a simple experiment that randomly assigns three variables, foo, bar
|
|
24
28
|
`foo` and `baz` use `userid` as input, while `bar` uses a pair, namely `userid` combined with the value of `foo` from the prior step.
|
25
29
|
|
26
30
|
```ruby
|
27
|
-
module
|
31
|
+
module PlanOut
|
28
32
|
class VotingExperiment < SimpleExperiment
|
29
33
|
# Experiment#assign takes params and an input array
|
30
34
|
def assign(params, **inputs)
|
@@ -48,7 +52,7 @@ end
|
|
48
52
|
Then, we can examine the assignments produced for a few input userids. Note that since exposure logging is enabled by default, all of the experiments' inputs, configuration information, timestamp, and parameter assignments are pooped out via the Logger class.
|
49
53
|
|
50
54
|
```ruby
|
51
|
-
my_exp =
|
55
|
+
my_exp = PlanOut::VotingExperiment.new(userid: 14)
|
52
56
|
my_button_color = my_exp.get(:button_color)
|
53
57
|
button_text = my_exp.get(:button_text)
|
54
58
|
puts "button color is #{my_button_color} and button text is #{button_text}."
|
@@ -57,7 +61,7 @@ puts "button color is #{my_button_color} and button text is #{button_text}."
|
|
57
61
|
The output of the Ruby script looks something like this:
|
58
62
|
|
59
63
|
```ruby
|
60
|
-
logged data: {"name":"
|
64
|
+
logged data: {"name":"PlanOut::VotingExperiment","time":1404944726,"salt":"PlanOut::VotingExperiment","inputs":{"userid":14},"params":{"button_color":"ff0000","button_text":"I'm a voter"},"event":"exposure"}
|
61
65
|
|
62
66
|
button color is ff0000 and button text is I'm a voter.
|
63
67
|
```
|
data/lib/plan_out.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require_relative 'operator'
|
2
|
+
|
3
|
+
module PlanOut
|
4
|
+
class OpRandom < OpSimple
|
5
|
+
LONG_SCALE = Float(0xFFFFFFFFFFFFFFF)
|
6
|
+
|
7
|
+
def get_unit(appended_unit = nil)
|
8
|
+
unit = @parameters[:unit]
|
9
|
+
unit = [unit] if !unit.is_a? Array
|
10
|
+
unit += appended_unit if appended_unit != nil
|
11
|
+
unit
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_hash(appended_unit = nil)
|
15
|
+
salt = @parameters[:salt]
|
16
|
+
salty = "#{@mapper.experiment_salt}.#{salt}"
|
17
|
+
unit_str = get_unit(appended_unit).join('.')
|
18
|
+
x = "#{salty}.#{unit_str}"
|
19
|
+
last_hex = (Digest::SHA1.hexdigest(x))[0..14]
|
20
|
+
last_hex.to_i(16)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_uniform(min_val = 0.0, max_val = 1.0, appended_unit = nil)
|
24
|
+
zero_to_one = self.get_hash(appended_unit)/LONG_SCALE
|
25
|
+
min_val + (max_val-min_val) * zero_to_one
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class RandomFloat < OpRandom
|
30
|
+
def simple_execute
|
31
|
+
min_val = @parameters.fetch(:min, 0)
|
32
|
+
max_val = @parameters.fetch(:max, 1)
|
33
|
+
get_uniform(min_val, max_val)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class RandomInteger < OpRandom
|
38
|
+
def simple_execute
|
39
|
+
min_val = @parameters.fetch(:min, 0)
|
40
|
+
max_val = @parameters.fetch(:max, 1)
|
41
|
+
min_val + get_hash() % (max_val - min_val + 1)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BernoulliTrial < OpRandom
|
46
|
+
def simple_execute
|
47
|
+
p = @parameters[:p]
|
48
|
+
rand_val = get_uniform(0.0, 1.0)
|
49
|
+
(rand_val <= p) ? 1 : 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class WeightedChoice < OpRandom
|
54
|
+
def simple_execute
|
55
|
+
choices = @parameters[:choices]
|
56
|
+
weights = @parameters[:weights]
|
57
|
+
|
58
|
+
return [] if choices.length() == 0
|
59
|
+
|
60
|
+
cum_weights = choices.zip(weights)
|
61
|
+
cum_sum = 0.0
|
62
|
+
|
63
|
+
cum_weights.each do |choice, weight|
|
64
|
+
cum_sum += weight
|
65
|
+
cum_weights[choice] = cum_sum
|
66
|
+
end
|
67
|
+
|
68
|
+
stop_value = get_uniform(0.0, cum_sum)
|
69
|
+
|
70
|
+
cum_weights.each do |choice, cum_weight|
|
71
|
+
choice if stop_value <= cum_weight
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class UniformChoice < OpRandom
|
77
|
+
def simple_execute
|
78
|
+
choices = @parameters[:choices]
|
79
|
+
return [] if choices.length() == 0
|
80
|
+
rand_index = get_hash() % choices.length()
|
81
|
+
choices[rand_index]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,6 +1,18 @@
|
|
1
|
-
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module PlanOut
|
4
|
+
class Operator
|
5
|
+
attr_accessor :args
|
6
|
+
|
7
|
+
def initialize(parameters)
|
8
|
+
@args = parameters
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(mapper)
|
12
|
+
mapper.experiment_salt
|
13
|
+
end
|
14
|
+
end
|
2
15
|
|
3
|
-
module Planout
|
4
16
|
class OpSimple < Operator
|
5
17
|
def execute(mapper)
|
6
18
|
@mapper = mapper
|
data/planout.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'plan_out/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "planout"
|
8
|
-
spec.version =
|
8
|
+
spec.version = PlanOut::VERSION
|
9
9
|
spec.authors = ["Eytan Bakshy", "Mohnish Thallavajhula"]
|
10
10
|
spec.email = ["ebakshy@gmail.com", "i@mohni.sh"]
|
11
11
|
spec.summary = %q{PlanOut is a framework and programming language for online field experimentation.}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
|
-
require_relative '../../examples/
|
2
|
+
require_relative '../../examples/plan_out/voting_experiment'
|
3
3
|
|
4
|
-
module
|
4
|
+
module PlanOut
|
5
5
|
class ExperimentTest < Minitest::Test
|
6
6
|
def setup
|
7
7
|
@voting_experiment = VotingExperiment.new(userid: 14)
|
@@ -15,18 +15,18 @@ module Planout
|
|
15
15
|
assert_equal(1, @voting_experiment.get(:missing_key, 1))
|
16
16
|
assert_equal('ff0000', @voting_experiment2.get(:button_color))
|
17
17
|
assert_equal("I'm voting", @voting_experiment.get(:button_text))
|
18
|
-
assert_equal("I'm
|
18
|
+
assert_equal("I'm voting", @voting_experiment2.get(:button_text))
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_get_params
|
22
22
|
assert_equal({ button_color: 'ff0000', button_text: "I'm voting" }, @voting_experiment.get_params)
|
23
|
-
assert_equal({ button_color: 'ff0000', button_text: "I'm
|
23
|
+
assert_equal({ button_color: 'ff0000', button_text: "I'm voting" }, @voting_experiment2.get_params)
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_as_blob
|
27
27
|
result = @voting_experiment.as_blob
|
28
|
-
assert_equal('
|
29
|
-
assert_equal('
|
28
|
+
assert_equal('PlanOut::VotingExperiment', result[:name])
|
29
|
+
assert_equal('PlanOut::VotingExperiment', result[:salt])
|
30
30
|
assert_equal({ userid: 14 }, result[:inputs])
|
31
31
|
end
|
32
32
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
|
-
require_relative '../lib/
|
2
|
+
require_relative '../lib/plan_out'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: planout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eytan Bakshy
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -67,24 +67,18 @@ files:
|
|
67
67
|
- LICENSE
|
68
68
|
- README.md
|
69
69
|
- Rakefile
|
70
|
-
- examples/
|
71
|
-
- lib/
|
72
|
-
- lib/
|
73
|
-
- lib/
|
74
|
-
- lib/
|
75
|
-
- lib/
|
76
|
-
- lib/
|
77
|
-
- lib/
|
78
|
-
- lib/planout/random_float.rb
|
79
|
-
- lib/planout/random_integer.rb
|
80
|
-
- lib/planout/simple_experiment.rb
|
81
|
-
- lib/planout/uniform_choice.rb
|
82
|
-
- lib/planout/version.rb
|
83
|
-
- lib/planout/weighted_choice.rb
|
70
|
+
- examples/plan_out/voting_experiment.rb
|
71
|
+
- lib/plan_out.rb
|
72
|
+
- lib/plan_out/assignment.rb
|
73
|
+
- lib/plan_out/experiment.rb
|
74
|
+
- lib/plan_out/op_random.rb
|
75
|
+
- lib/plan_out/operator.rb
|
76
|
+
- lib/plan_out/simple_experiment.rb
|
77
|
+
- lib/plan_out/version.rb
|
84
78
|
- planout.gemspec
|
85
|
-
- test/
|
86
|
-
- test/
|
87
|
-
- test/
|
79
|
+
- test/plan_out/assignment_test.rb
|
80
|
+
- test/plan_out/experiment_test.rb
|
81
|
+
- test/plan_out/operator_test.rb
|
88
82
|
- test/test_helper.rb
|
89
83
|
homepage: https://facebook.github.io/planout
|
90
84
|
licenses:
|
@@ -106,12 +100,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
100
|
version: '0'
|
107
101
|
requirements: []
|
108
102
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.
|
103
|
+
rubygems_version: 2.4.5
|
110
104
|
signing_key:
|
111
105
|
specification_version: 4
|
112
106
|
summary: PlanOut is a framework and programming language for online field experimentation.
|
113
107
|
test_files:
|
114
|
-
- test/
|
115
|
-
- test/
|
116
|
-
- test/
|
108
|
+
- test/plan_out/assignment_test.rb
|
109
|
+
- test/plan_out/experiment_test.rb
|
110
|
+
- test/plan_out/operator_test.rb
|
117
111
|
- test/test_helper.rb
|
data/lib/planout.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
require_relative 'planout/operator'
|
2
|
-
require_relative 'planout/op_simple'
|
3
|
-
require_relative 'planout/op_random'
|
4
|
-
require_relative 'planout/random_float'
|
5
|
-
require_relative 'planout/random_integer'
|
6
|
-
require_relative 'planout/bernoulli_trial'
|
7
|
-
require_relative 'planout/weighted_choice'
|
8
|
-
require_relative 'planout/uniform_choice'
|
9
|
-
require_relative 'planout/assignment'
|
10
|
-
require_relative 'planout/experiment'
|
11
|
-
require_relative 'planout/simple_experiment'
|
12
|
-
require_relative 'planout/version'
|
13
|
-
|
14
|
-
module Planout
|
15
|
-
|
16
|
-
end
|
data/lib/planout/op_random.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
require_relative 'op_simple'
|
2
|
-
|
3
|
-
module Planout
|
4
|
-
class OpRandom < OpSimple
|
5
|
-
LongScale = Float(0xFFFFFFFFFFFFFFF)
|
6
|
-
|
7
|
-
def get_unit(appended_unit = nil)
|
8
|
-
unit = @parameters[:unit]
|
9
|
-
unit = [unit] if !unit.is_a? Array
|
10
|
-
unit += appended_unit if appended_unit != nil
|
11
|
-
unit
|
12
|
-
end
|
13
|
-
|
14
|
-
def get_hash(appended_unit = nil)
|
15
|
-
salt = @parameters[:salt]
|
16
|
-
salty = "#{@mapper.experiment_salt}.#{salt}"
|
17
|
-
unit_str = get_unit(appended_unit).join('.')
|
18
|
-
x = "#{salty}.#{unit_str}"
|
19
|
-
last_hex = (Digest::SHA1.hexdigest(x))[0..14]
|
20
|
-
last_hex.to_i(16)
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_uniform(min_val = 0.0, max_val = 1.0, appended_unit = nil)
|
24
|
-
zero_to_one = self.get_hash(appended_unit)/LongScale
|
25
|
-
min_val + (max_val-min_val) * zero_to_one
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
data/lib/planout/operator.rb
DELETED
data/lib/planout/random_float.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
require_relative 'op_random'
|
2
|
-
|
3
|
-
module Planout
|
4
|
-
class UniformChoice < OpRandom
|
5
|
-
def simple_execute
|
6
|
-
choices = @parameters[:choices]
|
7
|
-
return [] if choices.length() == 0
|
8
|
-
rand_index = get_hash() % choices.length()
|
9
|
-
choices[rand_index]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
data/lib/planout/version.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require_relative 'op_random'
|
2
|
-
|
3
|
-
module Planout
|
4
|
-
class WeightedChoice < OpRandom
|
5
|
-
def simple_execute
|
6
|
-
choices = @parameters[:choices]
|
7
|
-
weights = @parameters[:weights]
|
8
|
-
|
9
|
-
return [] if choices.length() == 0
|
10
|
-
|
11
|
-
cum_weights = choices.zip(weights)
|
12
|
-
cum_sum = 0.0
|
13
|
-
|
14
|
-
cum_weights.each do |choice, weight|
|
15
|
-
cum_sum += weight
|
16
|
-
cum_weights[choice] = cum_sum
|
17
|
-
end
|
18
|
-
|
19
|
-
stop_value = get_uniform(0.0, cum_sum)
|
20
|
-
|
21
|
-
cum_weights.each do |choice, cum_weight|
|
22
|
-
choice if stop_value <= cum_weight
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|