I am trying to learn UI design so I threw together a Bayes Calculator (i.e. calculate the posterior probability of being sick given a positive/negative test result using the specificity, sensitivity of the test and current incidence rate). Since I already wrote markdown for a static site generator (hugo) I similarly created a html content page which is wrapped by the hugo theme fuji. This theme did not include styling for input elements (likely because it is intended for blogpost like markdown and not html). So I had to add some custom SCSS styling (but using existing color variables). In particular I added some logic to only activate form validation styling once an input element was modified once (add the class "wasModified").
The end result looks like this:
I am unsure how to align the input elements. A table would work, but then I lose the wrap around on mobile putting the labels above the input fields. Are there any best practices?
HTML
<!--content/blog/tools/bayes-helper.html -->
---
title: Bayes Helper
date: "2022-01-22"
tags: [maths, tools, health]
draft: true
---
<form id="bayes-form">
<label for="incidence">Incidence Rate</label>
<input type="number", id="incidence", name="incidence", min="0", max="100000", step="1", placeholder="per 100 000", required>
<br>
<label for="sensitivity">Sensitivity</label>
<input type="number", id="sensitivity", name="sensitivity", min="0", max="100", step=".01", placeholder="in percent", required>
<br>
<label for="specificity">Specificity</label>
<input type="number", id="specificity", name="specificity", min="0", max="100", step=".01", placeholder="in percent", required>
<br>
<label for="test_positive">Test positive?</label>
<input type="checkbox", id="test_positive", name="test_positive">
<br>
<label for="posterior" >Likelihood to be infected:</label>
<output id="posterior" name="posterior">Input not Valid</output>
</form>
<script>
function _calculatePosterior(prior, sensitivity, specificity, test_positive) {
const p_test_given_pos = test_positive ? sensitivity : 1-sensitivity
if (prior==0 || p_test_given_pos == 0){
return 0
}
const p_test_given_neg = test_positive ? 1-specificity : specificity
return 1/(1+ p_test_given_neg*(1-prior)/(p_test_given_pos*prior))
}
function calculatePosterior() {
this.posterior.value = _calculatePosterior(
this.incidence.value/100000.0,
this.sensitivity.value/100.0,
this.specificity.value/100.0,
this.test_positive.checked
) *100 + "%";
}
document.getElementById("bayes-form").oninput = calculatePosterior
document.getElementById("bayes-form").onreset = calculatePosterior
function add_wasModified() {
this.classList.add("wasModified");
}
for (const input of document.getElementsByTagName("input")) {
input.oninput = add_wasModified;
}
</script>
SCSS
// assets/scss/_custom_rules.scss
// Override CSS rules with that file
input {
color: var(--color-font);
background-color: var(--color-bg);
border: 2px solid var(--color-divider);
border-radius: 2px;
padding: 5px;
margin: 10px;
&:hover {
border: 2px solid var(--color-primary);
};
accent-color: var(--color-primary);
}
input.wasModified:invalid {
border: 2px solid $border-red-light;
}
button {
color: var(--color-font);
background-color: var(--color-codebg);
border-color: var(--color-primary);
border-radius: 2px;
}
Appendix: Math
For those interested (on request). So "sensitivity" is the likelihood of a test being positive conditional on truly having the condition, let us denote this by $$P(\text{test_p}|\text{pos})$$ "Specificity" is the likelihood of a test being negative conditional on not having the condition (being negative), denoted by $$P(\text{test_n} | \text{neg})$$
Now we want to know the probability of having the condition conditional on the test result, i.e.
$$\begin{align} P(\text{pos}|\text{test}) &= \frac{P(\text{pos}, \text{test})}{P(\text{test})} = \frac{P(\text{test}| \text{pos}) P(\text{pos})}{P(\text{test}, \text{pos}) +P(\text{test},\text{neg})}\\ &=\frac{ P(\text{test}| \text{pos})P(\text{pos}) }{ P(\text{test}| \text{pos})P(\text{pos}) +P(\text{test}|\text{neg})(1-P(\text{pos})) }\\ &=\frac{1}{1+\frac{ P(\text{test}|\text{neg})(1-P(\text{pos})) }{ P(\text{test}| \text{pos})P(\text{pos})} } \end{align}$$
Okay so what do we need to calculate this. First of all we need the prior probability to have the condition (before doing the test), P(pos), this is the incidence rate. For the two conditional probabilities we need we just need to look at specificity and sensitivity. For a positive test result we have
$$\begin{align} P(\text{test} | \text{pos}) &= P(\text{test_p} | \text{pos}) =\text{specificity}\\ P(\text{test} | \text{neg}) &= P(\text{test_p} | \text{neg}) = P(\text{test_n} | \text{neg}) = 1-\text{sensitivity} \end{align}$$ for a negative test result we have $$\begin{align} P(\text{test} | \text{pos}) &= P(\text{test_n} | \text{pos}) = 1- \text{specificity}\\ P(\text{test} | \text{neg}) &= P(\text{test_n} | \text{neg}) = \text{sensitivity} \end{align}$$
This explains the ternary operator. Then you just need to do some considerations for the probability zero cases and you just need to type up the algorithm.