field_test 0.5.1 → 0.5.4
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/CHANGELOG.md +14 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -2
- data/app/models/field_test/membership.rb +1 -1
- data/ext/field_test/bayestest.hpp +313 -0
- data/ext/field_test/ext.cpp +9 -5
- data/lib/field_test/experiment.rb +13 -32
- data/lib/field_test/helpers.rb +1 -1
- data/lib/field_test/version.rb +1 -1
- metadata +4 -4
- data/ext/field_test/bayesian_ab.hpp +0 -103
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d72f2ad8e2e3848b2e28bc826a479322e0994c1a65f23217d6bbdd85e1a708e5
|
4
|
+
data.tar.gz: 593a9d6292533404aaf5706e8d6efee44d168e659ef39b44f19d2c266259c69a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6605c5ba8800d72422bb121a9a27a7c1ac202022ce7fc37308b33ea77c9b0084e963c1b56d1ee54caf3b288abf16d4b91c86896b183bf8beba85ed98bf1e1113
|
7
|
+
data.tar.gz: 5b4d3d12e2511cf8369b73ae64bdd5ff49a0d8a1160f6bda31ee180d658ea34ee3efe96fe43cb59625195ce0682a30cef15ac21f7084790e7c46258db7906333
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## 0.5.4 (2022-06-16)
|
2
|
+
|
3
|
+
- Fixed bug in results with MySQL and multiple goals (again)
|
4
|
+
|
5
|
+
## 0.5.3 (2022-05-26)
|
6
|
+
|
7
|
+
- Fixed bug in results with MySQL and multiple goals
|
8
|
+
|
9
|
+
## 0.5.2 (2021-12-16)
|
10
|
+
|
11
|
+
- Fixed error with `events` association
|
12
|
+
- Fixed `exclude` option with custom logic
|
13
|
+
- Fixed installation error on macOS 12
|
14
|
+
|
1
15
|
## 0.5.1 (2021-09-22)
|
2
16
|
|
3
17
|
- Improved performance of Bayesian calculations
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -34,8 +34,6 @@ mount FieldTest::Engine, at: "field_test"
|
|
34
34
|
|
35
35
|
Be sure to [secure the dashboard](#dashboard-security) in production.
|
36
36
|
|
37
|
-

|
38
|
-
|
39
37
|
## Getting Started
|
40
38
|
|
41
39
|
Add an experiment to `config/field_test.yml`.
|
@@ -427,5 +425,6 @@ To get started with development:
|
|
427
425
|
git clone https://github.com/ankane/field_test.git
|
428
426
|
cd field_test
|
429
427
|
bundle install
|
428
|
+
bundle exec rake compile
|
430
429
|
bundle exec rake test
|
431
430
|
```
|
@@ -2,7 +2,7 @@ module FieldTest
|
|
2
2
|
class Membership < ActiveRecord::Base
|
3
3
|
self.table_name = "field_test_memberships"
|
4
4
|
|
5
|
-
has_many :events, class_name: "FieldTest::Event"
|
5
|
+
has_many :events, class_name: "FieldTest::Event", foreign_key: "field_test_membership_id"
|
6
6
|
|
7
7
|
validates :participant, presence: true, if: -> { FieldTest.legacy_participants }
|
8
8
|
validates :participant_id, presence: true, if: -> { !FieldTest.legacy_participants }
|
@@ -0,0 +1,313 @@
|
|
1
|
+
/*!
|
2
|
+
* BayesTest C++ v0.1.0
|
3
|
+
* https://github.com/ankane/bayestest-cpp
|
4
|
+
* MIT License
|
5
|
+
*/
|
6
|
+
|
7
|
+
#pragma once
|
8
|
+
|
9
|
+
#include <cmath>
|
10
|
+
#include <vector>
|
11
|
+
|
12
|
+
namespace bayestest {
|
13
|
+
|
14
|
+
double logbeta(double a, double b) {
|
15
|
+
return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
|
16
|
+
}
|
17
|
+
|
18
|
+
double prob_b_beats_a(int alpha_a, int beta_a, int alpha_b, int beta_b) {
|
19
|
+
double total = 0.0;
|
20
|
+
double logbeta_aa_ba = logbeta(alpha_a, beta_a);
|
21
|
+
double beta_ba = beta_b + beta_a;
|
22
|
+
|
23
|
+
for (auto i = 0; i < alpha_b; i++) {
|
24
|
+
total += exp(logbeta(alpha_a + i, beta_ba) - log(beta_b + i) - logbeta(1 + i, beta_b) - logbeta_aa_ba);
|
25
|
+
}
|
26
|
+
|
27
|
+
return total;
|
28
|
+
}
|
29
|
+
|
30
|
+
double prob_c_beats_ab(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c) {
|
31
|
+
double total = 0.0;
|
32
|
+
|
33
|
+
double logbeta_ac_bc = logbeta(alpha_c, beta_c);
|
34
|
+
|
35
|
+
std::vector<double> log_bb_j_logbeta_j_bb;
|
36
|
+
log_bb_j_logbeta_j_bb.reserve(alpha_b);
|
37
|
+
|
38
|
+
for (auto j = 0; j < alpha_b; j++) {
|
39
|
+
log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
|
40
|
+
}
|
41
|
+
|
42
|
+
double abc = beta_a + beta_b + beta_c;
|
43
|
+
std::vector<double> logbeta_ac_i_j;
|
44
|
+
logbeta_ac_i_j.reserve(alpha_a + alpha_b);
|
45
|
+
|
46
|
+
for (auto i = 0; i < alpha_a + alpha_b; i++) {
|
47
|
+
logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
|
48
|
+
}
|
49
|
+
|
50
|
+
for (auto i = 0; i < alpha_a; i++) {
|
51
|
+
double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
|
52
|
+
|
53
|
+
for (auto j = 0; j < alpha_b; j++) {
|
54
|
+
total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j_logbeta_j_bb[j]);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
|
59
|
+
prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
|
60
|
+
}
|
61
|
+
|
62
|
+
double prob_d_beats_abc(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c, int alpha_d, int beta_d) {
|
63
|
+
double total = 0.0;
|
64
|
+
|
65
|
+
double logbeta_ad_bd = logbeta(alpha_d, beta_d);
|
66
|
+
|
67
|
+
std::vector<double> log_bb_j_logbeta_j_bb;
|
68
|
+
log_bb_j_logbeta_j_bb.reserve(alpha_b);
|
69
|
+
|
70
|
+
for (auto j = 0; j < alpha_b; j++) {
|
71
|
+
log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
|
72
|
+
}
|
73
|
+
|
74
|
+
std::vector<double> log_bc_k_logbeta_k_bc;
|
75
|
+
log_bc_k_logbeta_k_bc.reserve(alpha_c);
|
76
|
+
|
77
|
+
for (auto k = 0; k < alpha_c; k++) {
|
78
|
+
log_bc_k_logbeta_k_bc.push_back(log(beta_c + k) + logbeta(1 + k, beta_c));
|
79
|
+
}
|
80
|
+
|
81
|
+
double abcd = beta_a + beta_b + beta_c + beta_d;
|
82
|
+
std::vector<double> logbeta_bd_i_j_k;
|
83
|
+
logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
|
84
|
+
|
85
|
+
for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
|
86
|
+
logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
|
87
|
+
}
|
88
|
+
|
89
|
+
for (auto i = 0; i < alpha_a; i++) {
|
90
|
+
double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
|
91
|
+
|
92
|
+
for (auto j = 0; j < alpha_b; j++) {
|
93
|
+
double sum_j = sum_i - log_bb_j_logbeta_j_bb[j];
|
94
|
+
|
95
|
+
for (auto k = 0; k < alpha_c; k++) {
|
96
|
+
total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k_logbeta_k_bc[k]);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
|
102
|
+
prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
|
103
|
+
prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
|
104
|
+
prob_c_beats_ab(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
|
105
|
+
prob_c_beats_ab(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
|
106
|
+
prob_c_beats_ab(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
|
107
|
+
}
|
108
|
+
|
109
|
+
double prob_1_beats_2(int alpha_1, int beta_1, int alpha_2, int beta_2) {
|
110
|
+
double total = 0.0;
|
111
|
+
double log_b1 = log(beta_1);
|
112
|
+
double a2_log_b2 = alpha_2 * log(beta_2);
|
113
|
+
double log_b1_b2 = log(beta_1 + beta_2);
|
114
|
+
|
115
|
+
for (auto k = 0; k < alpha_1; k++) {
|
116
|
+
total += exp(k * log_b1 +
|
117
|
+
a2_log_b2 -
|
118
|
+
(k + alpha_2) * log_b1_b2 -
|
119
|
+
log(k + alpha_2) -
|
120
|
+
logbeta(k + 1, alpha_2));
|
121
|
+
}
|
122
|
+
|
123
|
+
return total;
|
124
|
+
}
|
125
|
+
|
126
|
+
double prob_1_beats_23(int alpha_1, int beta_1, int alpha_2, int beta_2, int alpha_3, int beta_3) {
|
127
|
+
double total = 0.0;
|
128
|
+
|
129
|
+
double log_b1_b2_b3 = log(beta_1 + beta_2 + beta_3);
|
130
|
+
double a1_log_b1 = alpha_1 * log(beta_1);
|
131
|
+
double log_b2 = log(beta_2);
|
132
|
+
double log_b3 = log(beta_3);
|
133
|
+
double loggamma_a1 = std::lgamma(alpha_1);
|
134
|
+
|
135
|
+
for (auto k = 0; k < alpha_2; k++) {
|
136
|
+
double sum_k = a1_log_b1 + k * log_b2 - std::lgamma(k + 1);
|
137
|
+
|
138
|
+
for (auto l = 0; l < alpha_3; l++) {
|
139
|
+
total += exp(sum_k + l * log_b3
|
140
|
+
- (k + l + alpha_1) * log_b1_b2_b3
|
141
|
+
+ std::lgamma(k + l + alpha_1) - std::lgamma(l + 1) - loggamma_a1);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
return 1.0 - prob_1_beats_2(alpha_2, beta_2, alpha_1, beta_1)
|
146
|
+
- prob_1_beats_2(alpha_3, beta_3, alpha_1, beta_1) + total;
|
147
|
+
}
|
148
|
+
|
149
|
+
class BinaryTest {
|
150
|
+
public:
|
151
|
+
void add(int participants, int conversions) {
|
152
|
+
variants.emplace_back(participants, conversions);
|
153
|
+
}
|
154
|
+
|
155
|
+
std::vector<double> probabilities() {
|
156
|
+
std::vector<double> probs;
|
157
|
+
probs.reserve(variants.size());
|
158
|
+
|
159
|
+
switch (variants.size()) {
|
160
|
+
case 0: {
|
161
|
+
break;
|
162
|
+
}
|
163
|
+
case 1: {
|
164
|
+
probs.push_back(1);
|
165
|
+
|
166
|
+
break;
|
167
|
+
}
|
168
|
+
case 2: {
|
169
|
+
auto b = variants[0];
|
170
|
+
auto a = variants[1];
|
171
|
+
|
172
|
+
auto prob = prob_b_beats_a(
|
173
|
+
1 + a.conversions,
|
174
|
+
1 + a.participants - a.conversions,
|
175
|
+
1 + b.conversions,
|
176
|
+
1 + b.participants - b.conversions
|
177
|
+
);
|
178
|
+
probs.push_back(prob);
|
179
|
+
probs.push_back(1 - prob);
|
180
|
+
|
181
|
+
break;
|
182
|
+
}
|
183
|
+
case 3: {
|
184
|
+
auto total = 0.0;
|
185
|
+
for (auto i = 0; i < 2; i++) {
|
186
|
+
auto c = variants[i];
|
187
|
+
auto b = variants[(i + 1) % 3];
|
188
|
+
auto a = variants[(i + 2) % 3];
|
189
|
+
|
190
|
+
auto prob = prob_c_beats_ab(
|
191
|
+
1 + a.conversions,
|
192
|
+
1 + a.participants - a.conversions,
|
193
|
+
1 + b.conversions,
|
194
|
+
1 + b.participants - b.conversions,
|
195
|
+
1 + c.conversions,
|
196
|
+
1 + c.participants - c.conversions
|
197
|
+
);
|
198
|
+
|
199
|
+
probs.push_back(prob);
|
200
|
+
total += prob;
|
201
|
+
}
|
202
|
+
probs.push_back(1 - total);
|
203
|
+
|
204
|
+
break;
|
205
|
+
}
|
206
|
+
default: {
|
207
|
+
auto total = 0.0;
|
208
|
+
for (auto i = 0; i < 3; i++) {
|
209
|
+
auto d = variants[i];
|
210
|
+
auto c = variants[(i + 1) % 4];
|
211
|
+
auto b = variants[(i + 2) % 4];
|
212
|
+
auto a = variants[(i + 3) % 4];
|
213
|
+
|
214
|
+
auto prob = prob_d_beats_abc(
|
215
|
+
1 + a.conversions,
|
216
|
+
1 + a.participants - a.conversions,
|
217
|
+
1 + b.conversions,
|
218
|
+
1 + b.participants - b.conversions,
|
219
|
+
1 + c.conversions,
|
220
|
+
1 + c.participants - c.conversions,
|
221
|
+
1 + d.conversions,
|
222
|
+
1 + d.participants - d.conversions
|
223
|
+
);
|
224
|
+
|
225
|
+
probs.push_back(prob);
|
226
|
+
total += prob;
|
227
|
+
}
|
228
|
+
probs.push_back(1 - total);
|
229
|
+
}
|
230
|
+
}
|
231
|
+
return probs;
|
232
|
+
}
|
233
|
+
|
234
|
+
private:
|
235
|
+
struct Variant {
|
236
|
+
Variant(int participants, int conversions) : participants(participants), conversions(conversions) {};
|
237
|
+
int participants;
|
238
|
+
int conversions;
|
239
|
+
};
|
240
|
+
|
241
|
+
std::vector<Variant> variants;
|
242
|
+
};
|
243
|
+
|
244
|
+
class CountTest {
|
245
|
+
public:
|
246
|
+
void add(int events, int exposure) {
|
247
|
+
variants.emplace_back(events, exposure);
|
248
|
+
}
|
249
|
+
|
250
|
+
std::vector<double> probabilities() {
|
251
|
+
std::vector<double> probs;
|
252
|
+
probs.reserve(variants.size());
|
253
|
+
|
254
|
+
switch (variants.size()) {
|
255
|
+
case 0: {
|
256
|
+
break;
|
257
|
+
}
|
258
|
+
case 1: {
|
259
|
+
probs.push_back(1);
|
260
|
+
|
261
|
+
break;
|
262
|
+
}
|
263
|
+
case 2: {
|
264
|
+
auto a = variants[0];
|
265
|
+
auto b = variants[1];
|
266
|
+
|
267
|
+
auto prob = prob_1_beats_2(
|
268
|
+
a.events,
|
269
|
+
a.exposure,
|
270
|
+
b.events,
|
271
|
+
b.exposure
|
272
|
+
);
|
273
|
+
probs.push_back(prob);
|
274
|
+
probs.push_back(1 - prob);
|
275
|
+
|
276
|
+
break;
|
277
|
+
}
|
278
|
+
default: {
|
279
|
+
auto total = 0.0;
|
280
|
+
for (auto i = 0; i < 2; i++) {
|
281
|
+
auto a = variants[i];
|
282
|
+
auto b = variants[(i + 1) % 3];
|
283
|
+
auto c = variants[(i + 2) % 3];
|
284
|
+
|
285
|
+
auto prob = prob_1_beats_23(
|
286
|
+
a.events,
|
287
|
+
a.exposure,
|
288
|
+
b.events,
|
289
|
+
b.exposure,
|
290
|
+
c.events,
|
291
|
+
c.exposure
|
292
|
+
);
|
293
|
+
|
294
|
+
probs.push_back(prob);
|
295
|
+
total += prob;
|
296
|
+
}
|
297
|
+
probs.push_back(1 - total);
|
298
|
+
}
|
299
|
+
}
|
300
|
+
return probs;
|
301
|
+
}
|
302
|
+
|
303
|
+
private:
|
304
|
+
struct Variant {
|
305
|
+
Variant(int events, int exposure) : events(events), exposure(exposure) {};
|
306
|
+
int events;
|
307
|
+
int exposure;
|
308
|
+
};
|
309
|
+
|
310
|
+
std::vector<Variant> variants;
|
311
|
+
};
|
312
|
+
|
313
|
+
}
|
data/ext/field_test/ext.cpp
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
#include <rice/rice.hpp>
|
2
|
-
#include
|
2
|
+
#include <rice/stl.hpp>
|
3
|
+
|
4
|
+
#include "bayestest.hpp"
|
5
|
+
|
6
|
+
using bayestest::BinaryTest;
|
3
7
|
|
4
8
|
extern "C"
|
5
9
|
void Init_ext() {
|
6
10
|
auto rb_mFieldTest = Rice::define_module("FieldTest");
|
7
11
|
|
8
|
-
Rice::define_class_under(rb_mFieldTest, "
|
9
|
-
.
|
10
|
-
.
|
11
|
-
.
|
12
|
+
Rice::define_class_under<BinaryTest>(rb_mFieldTest, "BinaryTest")
|
13
|
+
.define_constructor(Rice::Constructor<BinaryTest>())
|
14
|
+
.define_method("add", &BinaryTest::add)
|
15
|
+
.define_method("probabilities", &BinaryTest::probabilities);
|
12
16
|
}
|
@@ -114,9 +114,9 @@ module FieldTest
|
|
114
114
|
elsif adapter_name =~ /postg/i # postgres
|
115
115
|
"(participant_type, participant_id)"
|
116
116
|
elsif adapter_name =~ /mysql/i
|
117
|
-
"participant_type, participant_id"
|
117
|
+
"COALESCE(participant_type, ''), participant_id"
|
118
118
|
else
|
119
|
-
#
|
119
|
+
# SQLite supports single column
|
120
120
|
"COALESCE(participant_type, '') || ':' || participant_id"
|
121
121
|
end
|
122
122
|
|
@@ -141,40 +141,21 @@ module FieldTest
|
|
141
141
|
}
|
142
142
|
end
|
143
143
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
b = results.values[(i + 1) % variants.size]
|
151
|
-
a = results.values[(i + 2) % variants.size]
|
152
|
-
|
153
|
-
alpha_a = 1 + a[:converted]
|
154
|
-
beta_a = 1 + a[:participated] - a[:converted]
|
155
|
-
alpha_b = 1 + b[:converted]
|
156
|
-
beta_b = 1 + b[:participated] - b[:converted]
|
157
|
-
alpha_c = 1 + c[:converted]
|
158
|
-
beta_c = 1 + c[:participated] - c[:converted]
|
159
|
-
|
160
|
-
# TODO calculate this incrementally by caching intermediate results
|
161
|
-
prob_winning =
|
162
|
-
if variants.size == 2
|
163
|
-
cache_fetch ["field_test", "prob_b_beats_a", alpha_b, beta_b, alpha_c, beta_c] do
|
164
|
-
Calculations.prob_b_beats_a(alpha_b, beta_b, alpha_c, beta_c)
|
165
|
-
end
|
166
|
-
else
|
167
|
-
cache_fetch ["field_test", "prob_c_beats_a_and_b", alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c] do
|
168
|
-
Calculations.prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
|
169
|
-
end
|
144
|
+
if variants.size <= 3
|
145
|
+
probabilities =
|
146
|
+
cache_fetch(["field_test", "probabilities"] + results.flat_map { |_, v| [v[:participated], v[:converted]] }) do
|
147
|
+
binary_test = BinaryTest.new
|
148
|
+
results.each do |_, v|
|
149
|
+
binary_test.add(v[:participated], v[:converted])
|
170
150
|
end
|
151
|
+
binary_test.probabilities.to_a
|
152
|
+
end
|
171
153
|
|
172
|
-
|
173
|
-
|
154
|
+
results.each_key.zip(probabilities) do |variant, prob_winning|
|
155
|
+
results[variant][:prob_winning] = prob_winning
|
174
156
|
end
|
175
|
-
|
176
|
-
results[variants.last][:prob_winning] = 1 - total
|
177
157
|
end
|
158
|
+
|
178
159
|
results
|
179
160
|
end
|
180
161
|
|
data/lib/field_test/helpers.rb
CHANGED
@@ -13,7 +13,7 @@ module FieldTest
|
|
13
13
|
end
|
14
14
|
|
15
15
|
if FieldTest.exclude_bots?
|
16
|
-
options[:exclude]
|
16
|
+
options[:exclude] ||= Browser.new(request.user_agent).bot?
|
17
17
|
end
|
18
18
|
|
19
19
|
options[:exclude] ||= FieldTest.excluded_ips.any? { |ip| ip.include?(request.remote_ip) }
|
data/lib/field_test/version.rb
CHANGED
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.5.
|
4
|
+
version: 0.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -89,7 +89,7 @@ files:
|
|
89
89
|
- app/views/field_test/participants/show.html.erb
|
90
90
|
- app/views/layouts/field_test/application.html.erb
|
91
91
|
- config/routes.rb
|
92
|
-
- ext/field_test/
|
92
|
+
- ext/field_test/bayestest.hpp
|
93
93
|
- ext/field_test/ext.cpp
|
94
94
|
- ext/field_test/extconf.rb
|
95
95
|
- lib/field_test.rb
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
126
|
requirements: []
|
127
|
-
rubygems_version: 3.
|
127
|
+
rubygems_version: 3.3.7
|
128
128
|
signing_key:
|
129
129
|
specification_version: 4
|
130
130
|
summary: A/B testing for Rails
|
@@ -1,103 +0,0 @@
|
|
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_logbeta_j_bb;
|
30
|
-
log_bb_j_logbeta_j_bb.reserve(alpha_b);
|
31
|
-
|
32
|
-
for (auto j = 0; j < alpha_b; j++) {
|
33
|
-
log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
|
34
|
-
}
|
35
|
-
|
36
|
-
double abc = beta_a + beta_b + beta_c;
|
37
|
-
std::vector<double> logbeta_ac_i_j;
|
38
|
-
logbeta_ac_i_j.reserve(alpha_a + alpha_b);
|
39
|
-
|
40
|
-
for (auto i = 0; i < alpha_a + alpha_b; i++) {
|
41
|
-
logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
|
42
|
-
}
|
43
|
-
|
44
|
-
for (auto i = 0; i < alpha_a; i++) {
|
45
|
-
double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
|
46
|
-
|
47
|
-
for (auto j = 0; j < alpha_b; j++) {
|
48
|
-
total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j_logbeta_j_bb[j]);
|
49
|
-
}
|
50
|
-
}
|
51
|
-
|
52
|
-
return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
|
53
|
-
prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
|
54
|
-
}
|
55
|
-
|
56
|
-
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) {
|
57
|
-
double total = 0.0;
|
58
|
-
|
59
|
-
double logbeta_ad_bd = logbeta(alpha_d, beta_d);
|
60
|
-
|
61
|
-
std::vector<double> log_bb_j_logbeta_j_bb;
|
62
|
-
log_bb_j_logbeta_j_bb.reserve(alpha_b);
|
63
|
-
|
64
|
-
for (auto j = 0; j < alpha_b; j++) {
|
65
|
-
log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
|
66
|
-
}
|
67
|
-
|
68
|
-
std::vector<double> log_bc_k_logbeta_k_bc;
|
69
|
-
log_bc_k_logbeta_k_bc.reserve(alpha_c);
|
70
|
-
|
71
|
-
for (auto k = 0; k < alpha_c; k++) {
|
72
|
-
log_bc_k_logbeta_k_bc.push_back(log(beta_c + k) + logbeta(1 + k, beta_c));
|
73
|
-
}
|
74
|
-
|
75
|
-
double abcd = beta_a + beta_b + beta_c + beta_d;
|
76
|
-
std::vector<double> logbeta_bd_i_j_k;
|
77
|
-
logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
|
78
|
-
|
79
|
-
for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
|
80
|
-
logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
|
81
|
-
}
|
82
|
-
|
83
|
-
for (auto i = 0; i < alpha_a; i++) {
|
84
|
-
double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
|
85
|
-
|
86
|
-
for (auto j = 0; j < alpha_b; j++) {
|
87
|
-
double sum_j = sum_i - log_bb_j_logbeta_j_bb[j];
|
88
|
-
|
89
|
-
for (auto k = 0; k < alpha_c; k++) {
|
90
|
-
total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k_logbeta_k_bc[k]);
|
91
|
-
}
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
|
96
|
-
prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
|
97
|
-
prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
|
98
|
-
prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
|
99
|
-
prob_c_beats_a_and_b(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
|
100
|
-
prob_c_beats_a_and_b(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
|
101
|
-
}
|
102
|
-
|
103
|
-
}
|