Skip to content

Commit eb79f78

Browse files
committed
initial
0 parents  commit eb79f78

File tree

16 files changed

+2744
-0
lines changed

16 files changed

+2744
-0
lines changed

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
25+
26+
model.json

App.vue

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<style>
2+
canvas {
3+
width: 100%;
4+
}
5+
</style>
6+
<template>
7+
<v-app>
8+
<v-app-bar app flat>
9+
<v-toolbar-title> Bayesian Binomial Test Calculator</v-toolbar-title>
10+
</v-app-bar>
11+
<v-main>
12+
<v-container>
13+
<v-row>
14+
<v-col cols="12" lg="4">
15+
<v-container>
16+
<v-row>
17+
<v-col cols="12">
18+
<Group
19+
v-model="a"
20+
groupName="A"
21+
:posInitial="30"
22+
:totInitial="100"
23+
></Group>
24+
</v-col>
25+
</v-row>
26+
<v-row>
27+
<v-col cols="12">
28+
<Group
29+
v-model="b"
30+
groupName="B"
31+
:posInitial="b_init.pos"
32+
:totInitial="b_init.tot"
33+
></Group>
34+
</v-col>
35+
</v-row>
36+
<v-row>
37+
<v-col cols="12">
38+
<v-card outlined>
39+
<v-card-title> Detailed settings </v-card-title>
40+
<v-card-text>
41+
<v-row>
42+
<v-col cols="6">
43+
<v-text-field
44+
label="alpha"
45+
type="Number"
46+
:error-messages="alphaError"
47+
v-model.number="alpha"
48+
></v-text-field>
49+
</v-col>
50+
<v-col cols="6">
51+
<v-text-field
52+
label="beta"
53+
type="Number"
54+
v-model.number="beta"
55+
:error-messages="betaError"
56+
></v-text-field>
57+
</v-col>
58+
<v-col cols="6">
59+
<v-text-field
60+
label="Number of samples"
61+
type="Number"
62+
v-model.number="nSamples"
63+
:error-messages="nSamplesError"
64+
></v-text-field>
65+
</v-col>
66+
</v-row>
67+
</v-card-text>
68+
</v-card>
69+
</v-col>
70+
</v-row>
71+
</v-container>
72+
</v-col>
73+
<v-col
74+
cols="12"
75+
lg="8"
76+
:style="{ opacity: payload === null ? 0.4 : 1.0 }"
77+
>
78+
<v-row>
79+
<v-col cols="12">
80+
<div class="text-center pt-4 text-h5">
81+
Probability of B being the winner:
82+
{{ (100 * this.result).toFixed(2) }} %
83+
</div>
84+
<v-row class="pt-4">
85+
<v-col cols="1"></v-col>
86+
<v-col cols="10">
87+
<canvas
88+
ref="canvas_a_b"
89+
:width="800 * dpr"
90+
:height="300 * dpr"
91+
></canvas>
92+
</v-col>
93+
<v-col cols="1"></v-col>
94+
<v-col cols="1"></v-col>
95+
<v-col cols="10">
96+
<canvas
97+
ref="canvas_diff"
98+
:width="800 * dpr"
99+
:height="300 * dpr"
100+
></canvas>
101+
</v-col>
102+
<v-col cols="1"></v-col>
103+
</v-row>
104+
</v-col>
105+
</v-row>
106+
</v-col>
107+
</v-row>
108+
</v-container>
109+
</v-main>
110+
</v-app>
111+
</template>
112+
<script>
113+
import { compute } from "./bayesian-wasm/pkg";
114+
import Group from "./components/Group.vue";
115+
116+
export default {
117+
data() {
118+
return {
119+
a: null,
120+
b: null,
121+
a_init: {
122+
tot: 100,
123+
pos: 30,
124+
},
125+
b_init: {
126+
tot: 10,
127+
pos: 5,
128+
},
129+
alpha: 1.0,
130+
alphaError: [],
131+
beta: 1.0,
132+
betaError: [],
133+
nSamples: 100000,
134+
nSamplesError: [],
135+
n_bins: 100,
136+
result: null,
137+
dpr: 3,
138+
};
139+
},
140+
computed: {
141+
payload() {
142+
let invalid = false;
143+
if (this.a === null) invalid = true;
144+
if (this.b === null) invalid = true;
145+
let alpha = parseFloat(this.alpha);
146+
let beta = parseFloat(this.beta);
147+
let nSamples = parseInt(this.nSamples);
148+
if (isNaN(alpha) || alpha < 0) {
149+
this.alphaError = ["valid, positive float required."];
150+
invalid = true;
151+
} else {
152+
this.alphaError = null;
153+
}
154+
if (isNaN(beta) || beta < 0) {
155+
this.betaError = ["valid, positive float required."];
156+
invalid = true;
157+
} else {
158+
this.betaError = [];
159+
}
160+
if (isNaN(nSamples) || nSamples <= 0) {
161+
this.nSamplesError = ["this field must be strictly positive."];
162+
invalid = true;
163+
} else {
164+
this.nSamplesError = [];
165+
}
166+
if (invalid) {
167+
return null;
168+
}
169+
170+
let data = {
171+
prior_pos: alpha,
172+
prior_neg: beta,
173+
a_tot: this.a.tot,
174+
a_pos: this.a.pos,
175+
b_tot: this.b.tot,
176+
b_pos: this.b.pos,
177+
n_samples: nSamples,
178+
n_bins: this.n_bins,
179+
};
180+
return JSON.stringify(data);
181+
},
182+
},
183+
watch: {
184+
payload(nv) {
185+
this.compute(nv);
186+
},
187+
},
188+
methods: {
189+
async compute(payload) {
190+
console.log(payload);
191+
if (payload === null) {
192+
return;
193+
}
194+
195+
let res = compute(
196+
payload,
197+
this.dpr,
198+
this.$refs.canvas_a_b,
199+
this.$refs.canvas_diff
200+
);
201+
this.result = res;
202+
},
203+
},
204+
async mounted() {
205+
//this.compute(this.payload);
206+
},
207+
components: {
208+
Group,
209+
},
210+
};
211+
</script>

ReadMe.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Bayesian binomial test using WebAssembly
2+
3+
A/B test significance judgement using bayesian beta-binomial distribution is simple. In Python, we can do that like:
4+
```python
5+
import numpy as np
6+
7+
alpha_prior = 1.0
8+
beta_prior = 1.0
9+
10+
n_events_a = 100
11+
n_success_a = 30
12+
13+
n_events_b = 10
14+
n_success_b = 5
15+
16+
n_samples = 10000
17+
18+
rng = np.random.default_rng(0)
19+
20+
posterior_a = rng.beta(alpha_prior + n_success_a, beta_prior + n_events_a - n_success_a, size=n_samples)
21+
posterior_b = rng.beta(alpha_prior + n_success_b, beta_prior + n_events_b - n_success_b, size=n_samples)
22+
23+
# 0.90576
24+
(posterior_a < posterior_b).mean()
25+
```
26+
27+
This simple demo visualizes the Bayesian binomial test, using Rust + WebAssembly to sample from the posterior distribution (obviously overkill, but fun).
28+
29+
You can find a similar example app [here](https://making.lyst.com/bayesian-calculator/).

bayesian-wasm/.cargo-ok

Whitespace-only changes.

bayesian-wasm/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/target
2+
**/*.rs.bk
3+
Cargo.lock
4+
bin/
5+
pkg/
6+
wasm-pack.log
7+
node_modules
8+
dist/
9+
rust-version.html
10+
dist_rollup

bayesian-wasm/Cargo.toml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[package]
2+
authors = ["Tomoki <tomoki.ohtsuki129@gmail.com>"]
3+
edition = "2018"
4+
name = "bayesian-binomial-visualization"
5+
version = "0.1.0"
6+
7+
[lib]
8+
crate-type = ["cdylib", "rlib"]
9+
10+
[features]
11+
default = ["console_error_panic_hook"]
12+
13+
[dependencies]
14+
wasm-bindgen = "0.2.63"
15+
16+
# The `console_error_panic_hook` crate provides better debugging of panics by
17+
# logging them with `console.error`. This is great for development, but requires
18+
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
19+
# code size when deploying.
20+
console_error_panic_hook = {version = "0.1.6", optional = true}
21+
22+
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
23+
# compared to the default allocator's ~10K. It is slower than the default
24+
# allocator, however.
25+
#
26+
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
27+
getrandom = {version = "*", features = ["js"]}
28+
plotters = "0.3.1"
29+
plotters-bitmap = "0.3.1"
30+
plotters-canvas = "0.3.0"
31+
rand = "0.8.5"
32+
rand_pcg = "0.3.1"
33+
serde = {version = "1.0", features = ["derive"]}
34+
serde_json = "1.0.81"
35+
statrs = "0.15.0"
36+
web-sys = {version = "0.3.57", features = ['console']}
37+
wee_alloc = {version = "0.4.5", optional = true}
38+
39+
[dev-dependencies]
40+
wasm-bindgen-test = "0.3.13"
41+
42+
[profile.release]
43+
# Tell `rustc` to optimize for small code size.
44+
opt-level = "s"

0 commit comments

Comments
 (0)