planout 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](http://badge.fury.io/rb/planout)
|
3
|
+
[](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
|