Robustness analysis for informed Bayesian t-tests

Adventures in WASM and Quarto

programming
statistics
bayes
Author
Affiliation

School of Psychology, University of Sussex

Published

April 14, 2022

tldr

Currently, robustness analysis for informed Bayesian t-tests is not implemented in JASP. So I thought I’d just build it! This is also a great excuse to play around with Rust WASM and Observable blocks in Quarto.

Motivation

Feel free to skip this section if you’re not interested in the technical details. You can just jump straight to the calculator. I recently released the bayesplay Web-app and R Package for computing Bayes factors. At the prompting of a colleague, I decided to include Robustness analysis as a feature of this package. This was fairly straightforward to do, and I was able to quickly include it in the R package. However, when it came to including it in the web-app I hit a snag. The snag was that it was very slow. The common way to perform robustness analysis is to generate thousands of Bayes factor value for slightly different values of the prior parameters. The web-app currently performed all the computations in Go compiled to WASM. I was running into memory issues and fighting against the GC, and this was slowing it down. So I thought to myself, “this is a great excuse to write some RUST!” (Because that’s what all the cool kids think!). I didn’t want to build out a complete app just for benchmarking, so I had a second thought. I love ObservableHQ for building quick mock-ups of things. And RMarkdown (or rather Quarto) now has native support for Observable, so I thought I’d just make a blog post instead. And to keep things even simpler, I thought that instead of implementing robustness analysis for all the models that bayesplay supports, I’d just do it for two-sample informed Bayesian t-tests (e.g., Gronau et al., 2020). I picked this, because you can run these models in JASP. Well, almost, when you click the Bayes factor robustness check plot tick-box you get a message telling you: “Bayes factor robustness check plot currently not supported for informed prior”. So that’s the aim, implement this missing feature of JASP in WASM, and Observable.

Some design choices

For the calculator, I’ve aimed to replicate the JASP interface as closely as possible. JASP asks for a t value, and two sample sizes. This is slightly different to how you’d do it in bayesplay where you’d enter a d value and two sample sizes.

For alternative the prior, JASP gives you the choice between a normal distribution, a cauchy distribution, and a student t distribution. Since this is just a quick proof of concept, I have decided against implementing all three. And because a cauchy distribution is just a student t distribution where the degrees of freedom is equal to 1 I’ve decided to just implement the student t prior. For this prior you need a location, a scale and a df value.

For the robustness analysis itself, I had to decide which values you can vary. Varying all three at once (location, scale, and df) get’s tricky because then you have to visualise that in three dimensions. So instead, I’ve made it possible to vary the location and the scale together, the location by itself, and the scale by itself. Hopefully everything down below is self explanatory. So on to the calculator and plots themselves! I’ve already pre-filled it with the data from Gronau et al. (2020).

The calculator

The data

Alternative hypothesis

Bayes factor

The prior

Robustness Check

Location

Scale

Robustness plot

Final thoughts

First and foremost, I just can’t believe how fast Rust is. I knew Rust was fast, but I didn’t expect there to be such a huge difference between my Rust implementation and the equivalent Go implementation. Since the code is very simple I didn’t run into any of the usual headaches with the borrow checker that can sometimes make writing Rust difficult. The only real Rust gotcha was that two closures in Rust are always different types. So you can’t have a function that returns one of two possible closures as you might be able to do in many other languages. This not really a problem. Just something I found surprising.

The other thought is that I really wish there were some great numerics libraries in Rust. Go has Gonum, which is really good. In Rust, there’s the option of peroxide. However, interestingly, both these libraries really lacked something that was crucial for my task here. That is, they lack an implementation of the non-central t distribution. With Rust, there are some libraries that wrap C/C++ libraries, but getting these to compile to a WASM target didn’t work out the box, so I just went and implemented it myself. Thankfully I already have a Go implementation based on SciPy’s implementation (with the addition of a normal approximation when it fails). So I could just translate this. To throw a slight spanner in the works I also had to implement the type-(p,q) generalised hypergeometric function, because the non-central t implementation relies on it. All this just made me wish that there were great libraries like the GNU Scientific Library, the Math.NET Numerics library and the Boost numeric libraries available in Rust (or Go).

References

Gronau, Q. F., Ly, A., & Wagenmakers, E.-J. (2020). Informed bayesian t-tests. The American Statistician, 74(2), 137–143. https://doi.org/10.1080/00031305.2018.1562983

Reuse

Citation

BibTeX citation:
@online{jcolling2022,
  author = {Lincoln J Colling},
  editor = {},
  title = {Robustness Analysis for Informed {Bayesian} t-Tests},
  date = {2022-04-14},
  langid = {en},
  abstract = {A Rust library for informated Bayesian t-tests.}
}
For attribution, please cite this work as:
Lincoln J Colling. (2022, April 14). Robustness analysis for informed Bayesian t-tests.