field_test 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25a516eac00c5ded25aa203d9e3feb89196c7641868e5575938bf223ee8d20d2
4
- data.tar.gz: 89dc335addf645088c5251ccd7a99b0d5348857a210c520350a3d39a9244fc3f
3
+ metadata.gz: 913c2ad9eaefe134cdabd915eec17f4f48fa9062c9066e37aa638989f0c05072
4
+ data.tar.gz: bfe73138f6b18fbf8b72e144d2f3c6e727a1d398e6c0f09860981aade30b9d45
5
5
  SHA512:
6
- metadata.gz: 97c5900ff54996e5b31250b882300104c991e93ab4582b30e1a2ada4fb0c8f191ddbac9240941fce9d2160bf01a66ca1ed73cd3e4a5a734de99e487b9c0c3772
7
- data.tar.gz: 765b5fbb1928889ff40b576b710b93ffe14adc38d6153715b89d806cbcfa46021fb4d0cc780754a1e794d3ad2ccbcb8f939dff72a148392ae95a0b968cb7d443
6
+ metadata.gz: e35ca617ba6e37ad7650f14ef46fd225ce3469faac29cb91909cc682f42b160b0af6af522906cfb53015a381e8c0ddb27ebda2e1a219c186147716e38d7d9396
7
+ data.tar.gz: 3464e1e26a219ddb43a8a766d965608c60ef014a158ac2df923403d915a684c58fdef6f6ab75b9ccf60d1201f658c84638cb9c2e5025ac48a69b45f721121290
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.5.0 (2021-09-21)
2
+
3
+ - Significantly improved performance of Bayesian calculations
4
+ - Dropped support for Ruby < 2.6 and Rails < 5.2
5
+
1
6
  ## 0.4.1 (2020-09-07)
2
7
 
3
8
  - Use `datetime` type in migration
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016-2019 Andrew Kane
1
+ Copyright (c) 2016-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  Uses [Bayesian statistics](https://www.evanmiller.org/bayesian-ab-testing.html) to evaluate results so you don’t need to choose a sample size ahead of time.
11
11
 
12
- [![Build Status](https://travis-ci.org/ankane/field_test.svg?branch=master)](https://travis-ci.org/ankane/field_test)
12
+ [![Build Status](https://github.com/ankane/field_test/workflows/build/badge.svg?branch=master)](https://github.com/ankane/field_test/actions)
13
13
 
14
14
  ## Installation
15
15
 
@@ -0,0 +1,120 @@
1
+ #pragma once
2
+
3
+ #include <cmath>
4
+ #include <vector>
5
+
6
+ namespace bayesian_ab {
7
+
8
+ double logbeta(double a, double b) {
9
+ return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
10
+ }
11
+
12
+ double prob_b_beats_a(int alpha_a, int beta_a, int alpha_b, int beta_b) {
13
+ double total = 0.0;
14
+ double logbeta_aa_ba = logbeta(alpha_a, beta_a);
15
+ double beta_ba = beta_b + beta_a;
16
+
17
+ for (auto i = 0; i < alpha_b; i++) {
18
+ total += exp(logbeta(alpha_a + i, beta_ba) - log(beta_b + i) - logbeta(1 + i, beta_b) - logbeta_aa_ba);
19
+ }
20
+
21
+ return total;
22
+ }
23
+
24
+ double prob_c_beats_a_and_b(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c) {
25
+ double total = 0.0;
26
+
27
+ double logbeta_ac_bc = logbeta(alpha_c, beta_c);
28
+
29
+ std::vector<double> log_bb_j;
30
+ log_bb_j.reserve(alpha_b);
31
+ std::vector<double> logbeta_j_bb;
32
+ logbeta_j_bb.reserve(alpha_b);
33
+
34
+ for (auto j = 0; j < alpha_b; j++) {
35
+ log_bb_j.push_back(log(beta_b + j));
36
+ logbeta_j_bb.push_back(logbeta(1 + j, beta_b));
37
+ }
38
+
39
+ double abc = beta_a + beta_b + beta_c;
40
+ std::vector<double> logbeta_ac_i_j;
41
+ logbeta_ac_i_j.reserve(alpha_a + alpha_b);
42
+
43
+ for (auto i = 0; i < alpha_a + alpha_b; i++) {
44
+ logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
45
+ }
46
+
47
+ for (auto i = 0; i < alpha_a; i++) {
48
+ double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
49
+
50
+ for (auto j = 0; j < alpha_b; j++) {
51
+ total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j[j] - logbeta_j_bb[j]);
52
+ }
53
+ }
54
+
55
+ return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
56
+ prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
57
+ }
58
+
59
+ double prob_d_beats_a_and_b_and_c(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c, int alpha_d, int beta_d) {
60
+ double total = 0.0;
61
+
62
+ double logbeta_ad_bd = logbeta(alpha_d, beta_d);
63
+
64
+ std::vector<double> log_bb_j;
65
+ log_bb_j.reserve(alpha_b);
66
+ std::vector<double> logbeta_j_bb;
67
+ logbeta_j_bb.reserve(alpha_b);
68
+
69
+ for (auto j = 0; j < alpha_b; j++) {
70
+ log_bb_j.push_back(log(beta_b + j));
71
+ logbeta_j_bb.push_back(logbeta(1 + j, beta_b));
72
+ }
73
+
74
+ std::vector<double> log_bc_k;
75
+ log_bc_k.reserve(alpha_c);
76
+ std::vector<double> logbeta_k_bc;
77
+ logbeta_k_bc.reserve(alpha_c);
78
+
79
+ for (auto k = 0; k < alpha_c; k++) {
80
+ log_bc_k.push_back(log(beta_c + k));
81
+ logbeta_k_bc.push_back(logbeta(1 + k, beta_c));
82
+ }
83
+
84
+ double abcd = beta_a + beta_b + beta_c + beta_d;
85
+ std::vector<double> logbeta_bd_i_j_k;
86
+ logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
87
+
88
+ for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
89
+ logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
90
+ }
91
+
92
+ std::vector<double> sum_i_j;
93
+ sum_i_j.reserve(alpha_a * alpha_b);
94
+ for (auto i = 0; i < alpha_a; i++) {
95
+ double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
96
+
97
+ for (auto j = 0; j < alpha_b; j++) {
98
+ sum_i_j.push_back(sum_i - log_bb_j[j] - logbeta_j_bb[j]);
99
+ }
100
+ }
101
+
102
+ for (auto i = 0; i < alpha_a; i++) {
103
+ for (auto j = 0; j < alpha_b; j++) {
104
+ double sum_j = sum_i_j[i * alpha_b + j];
105
+
106
+ for (auto k = 0; k < alpha_c; k++) {
107
+ total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k[k] - logbeta_k_bc[k]);
108
+ }
109
+ }
110
+ }
111
+
112
+ return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
113
+ prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
114
+ prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
115
+ prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
116
+ prob_c_beats_a_and_b(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
117
+ prob_c_beats_a_and_b(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
118
+ }
119
+
120
+ }
@@ -0,0 +1,12 @@
1
+ #include <rice/rice.hpp>
2
+ #include "bayesian_ab.hpp"
3
+
4
+ extern "C"
5
+ void Init_ext() {
6
+ auto rb_mFieldTest = Rice::define_module("FieldTest");
7
+
8
+ Rice::define_class_under(rb_mFieldTest, "Calculations")
9
+ .define_singleton_function("prob_b_beats_a", &bayesian_ab::prob_b_beats_a)
10
+ .define_singleton_function("prob_c_beats_a_and_b", &bayesian_ab::prob_c_beats_a_and_b)
11
+ .define_singleton_function("prob_d_beats_a_and_b_and_c", &bayesian_ab::prob_d_beats_a_and_b_and_c);
12
+ }
@@ -0,0 +1,5 @@
1
+ require "mkmf-rice"
2
+
3
+ $CXXFLAGS << " -std=c++17 $(optflags)"
4
+
5
+ create_makefile("field_test/ext")
@@ -1,3 +1,3 @@
1
1
  module FieldTest
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/field_test.rb CHANGED
@@ -3,8 +3,10 @@ require "active_support"
3
3
  require "browser"
4
4
  require "ipaddr"
5
5
 
6
+ # ext
7
+ require "field_test/ext"
8
+
6
9
  # modules
7
- require "field_test/calculations"
8
10
  require "field_test/experiment"
9
11
  require "field_test/helpers"
10
12
  require "field_test/participant"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: field_test
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-08 00:00:00.000000000 Z
11
+ date: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -16,42 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5'
26
+ version: '5.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '5'
33
+ version: '5.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '5'
41
- - !ruby/object:Gem::Dependency
42
- name: distribution
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
40
+ version: '5.2'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: browser
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -67,93 +53,24 @@ dependencies:
67
53
  - !ruby/object:Gem::Version
68
54
  version: '2.0'
69
55
  - !ruby/object:Gem::Dependency
70
- name: bundler
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: minitest
56
+ name: rice
99
57
  requirement: !ruby/object:Gem::Requirement
100
58
  requirements:
101
59
  - - ">="
102
60
  - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: combustion
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: rails
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: sqlite3
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
61
+ version: 4.0.2
62
+ type: :runtime
147
63
  prerelease: false
148
64
  version_requirements: !ruby/object:Gem::Requirement
149
65
  requirements:
150
66
  - - ">="
151
67
  - !ruby/object:Gem::Version
152
- version: '0'
153
- description:
154
- email: andrew@chartkick.com
68
+ version: 4.0.2
69
+ description:
70
+ email: andrew@ankane.org
155
71
  executables: []
156
- extensions: []
72
+ extensions:
73
+ - ext/field_test/extconf.rb
157
74
  extra_rdoc_files: []
158
75
  files:
159
76
  - CHANGELOG.md
@@ -172,8 +89,10 @@ files:
172
89
  - app/views/field_test/participants/show.html.erb
173
90
  - app/views/layouts/field_test/application.html.erb
174
91
  - config/routes.rb
92
+ - ext/field_test/bayesian_ab.hpp
93
+ - ext/field_test/ext.cpp
94
+ - ext/field_test/extconf.rb
175
95
  - lib/field_test.rb
176
- - lib/field_test/calculations.rb
177
96
  - lib/field_test/controller.rb
178
97
  - lib/field_test/engine.rb
179
98
  - lib/field_test/experiment.rb
@@ -190,7 +109,7 @@ homepage: https://github.com/ankane/field_test
190
109
  licenses:
191
110
  - MIT
192
111
  metadata: {}
193
- post_install_message:
112
+ post_install_message:
194
113
  rdoc_options: []
195
114
  require_paths:
196
115
  - lib
@@ -198,15 +117,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
117
  requirements:
199
118
  - - ">="
200
119
  - !ruby/object:Gem::Version
201
- version: '2.4'
120
+ version: '2.6'
202
121
  required_rubygems_version: !ruby/object:Gem::Requirement
203
122
  requirements:
204
123
  - - ">="
205
124
  - !ruby/object:Gem::Version
206
125
  version: '0'
207
126
  requirements: []
208
- rubygems_version: 3.1.2
209
- signing_key:
127
+ rubygems_version: 3.2.22
128
+ signing_key:
210
129
  specification_version: 4
211
130
  summary: A/B testing for Rails
212
131
  test_files: []
@@ -1,58 +0,0 @@
1
- require "distribution/math_extension"
2
-
3
- # formulas from
4
- # https://www.evanmiller.org/bayesian-ab-testing.html
5
- module FieldTest
6
- module Calculations
7
- def self.prob_b_beats_a(alpha_a, beta_a, alpha_b, beta_b)
8
- total = 0.0
9
-
10
- # for performance
11
- logbeta_aa_ba = Math.logbeta(alpha_a, beta_a)
12
- beta_ba = beta_b + beta_a
13
-
14
- 0.upto(alpha_b - 1) do |i|
15
- total += Math.exp(Math.logbeta(alpha_a + i, beta_ba) -
16
- Math.log(beta_b + i) - Math.logbeta(1 + i, beta_b) -
17
- logbeta_aa_ba)
18
- end
19
-
20
- total
21
- end
22
-
23
- def self.prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
24
- total = 0.0
25
-
26
- # for performance
27
- logbeta_ac_bc = Math.logbeta(alpha_c, beta_c)
28
- abc = beta_a + beta_b + beta_c
29
- log_bb_j = []
30
- logbeta_j_bb = []
31
- logbeta_ac_i_j = []
32
- 0.upto(alpha_b - 1) do |j|
33
- log_bb_j[j] = Math.log(beta_b + j)
34
- logbeta_j_bb[j] = Math.logbeta(1 + j, beta_b)
35
-
36
- 0.upto(alpha_a - 1) do |i|
37
- logbeta_ac_i_j[i + j] ||= Math.logbeta(alpha_c + i + j, abc)
38
- end
39
- end
40
-
41
- 0.upto(alpha_a - 1) do |i|
42
- # for performance
43
- log_ba_i = Math.log(beta_a + i)
44
- logbeta_i_ba = Math.logbeta(1 + i, beta_a)
45
-
46
- 0.upto(alpha_b - 1) do |j|
47
- total += Math.exp(logbeta_ac_i_j[i + j] -
48
- log_ba_i - log_bb_j[j] -
49
- logbeta_i_ba - logbeta_j_bb[j] -
50
- logbeta_ac_bc)
51
- end
52
- end
53
-
54
- 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
55
- prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total
56
- end
57
- end
58
- end