Bayes' Calculator

A very visual Bayesian calculator.

code.tsx

import { App, Component, getPropertyValue, formField, inputRange, div, h1, span, strong, type HValues } from "domeleon";

class BayesCalculator extends Component {
  prior = 0.1
  sensitivity = 0.9
  falsePositiveRate = 0.05

  get marginalLikelihood() {
    const truePositives = this.sensitivity * this.prior
    return truePositives + this.falsePositiveRate * (1 - this.prior)
  }

  get posterior() {
    const numerator = this.sensitivity * this.prior
    const denominator = this.marginalLikelihood
    return denominator === 0 ? 0 : numerator / denominator
  }

  slider (prop: () => number, labelText: string, notation: string, paramClass: string) {
    return formField({
      target: this,
      prop,      
      inputFn: inputRange,
      fieldAttrs: { class: "control-row" },
      inputProps: { attrs: { min: "0", max: "1", step: "0.01" } },
      label: div(
        `${labelText} (`,
        span({ class: paramClass }, notation),
        "): ",
        strong({ class: paramClass }, getPropertyValue(this, prop).toFixed(2))
      )
    })  
  }

  private controls() {
    return div({ class: "controls" },
      this.slider(() => this.prior, "Prior Probability", "P(A)", "param-prior"),
      this.slider(() => this.sensitivity, "Sensitivity", "P(B|A)", "param-sensitivity"),
      this.slider(() => this.falsePositiveRate, "False Positive Rate", "P(B|¬A)", "param-fpr")
    )
  }

  // Helper to create a region with subregions
  private region(leftPercent: number, widthPercent: number, isA: boolean) {
    const truePositiveHeightPercent = (isA ? this.sensitivity : this.falsePositiveRate) * 100
    const complementHeightPercent = 100 - truePositiveHeightPercent
    const regionClass = isA ? "region-a" : "region-nota"
    const positiveClass = isA ? "subregion-ab" : "subregion-notab"
    const negativeClass = isA ? "subregion-anotb" : "subregion-notanotb"

    return div({ 
      class: `region ${regionClass}`,
      style: { left: `${leftPercent}%`, width: `${widthPercent}%` }
    },
      div({ class: `subregion ${positiveClass}`, style: { height: `${truePositiveHeightPercent}%` } }),
      div({ class: `subregion ${negativeClass}`, style: { height: `${complementHeightPercent}%` } })
    );
  }

  private visualization() {
    const priorPercent = this.prior * 100
    const notAPercent = (1 - this.prior) * 100

    return div({ class: "bayes-rect" },
      this.region(0, priorPercent, true),
      this.region(priorPercent, notAPercent, false)
    );
  }

  // Helper to create a fraction in an equation step
  private fraction(numeratorContent: HValues[], denominatorContent: HValues[]) {
    return div({ class: "fraction" },
      div({ class: "numerator" }, ...numeratorContent),
      div({ class: "denominator" }, ...denominatorContent)
    );
  }

  // The famous Bayes' theorem formula: P(A|B) = P(B|A) × P(A) / P(B)
  private bayesFormula() {
    return this.fraction(
      [
        span({ class: "param-sensitivity" }, "P(B|A)"),
        " × ",
        span({ class: "param-prior" }, "P(A)")
      ],
      [
        span({ class: "param-marginal" }, "P(B)")
      ]
    )
  }

  // Helper to create the full Bayes' theorem expansion (symbolic or numerical)
  private bayesExpansion(useNumbers: boolean) {
    const mult = " × "
    const sens = useNumbers ? this.sensitivity.toFixed(2) : "P(B|A)"
    const pri = useNumbers ? this.prior.toFixed(2) : "P(A)"
    const fpr = useNumbers ? this.falsePositiveRate.toFixed(2) : "P(B|¬A)"
    const notA = useNumbers ? (1 - this.prior).toFixed(2) : "P(¬A)"

    const sensClass = "param-sensitivity"
    const priClass = "param-prior"
    const fprClass = "param-fpr"
    const notAClass = "param-nota"

    return this.fraction(
      [
        span({ class: sensClass }, sens),
        mult,
        span({ class: priClass }, pri)
      ],
      [
        span({ class: sensClass }, sens),
        mult,
        span({ class: priClass }, pri),
        " + ",
        span({ class: fprClass }, fpr),
        mult,
        span({ class: notAClass }, notA)
      ]
    )
  }

  private equations() {
    return div({ class: "equation-row" },
      div({ class: "eq-lhs-container" },
        div({ class: "eq-lhs" },
          span({ class: "param-posterior" }, "P(A|B)"),
          span({ class: "equals-inline" }, "=")
        ),
        div({ class: "eq-label" }, "(Posterior)")
      ),
      div({ class: "eq-rhs-stack" },
        div({ class: "eq-step" }, this.bayesFormula()),
        div({ class: "eq-step" }, this.bayesExpansion(false)),
        div({ class: "eq-step" }, this.bayesExpansion(true)),
        div({ class: "eq-step eq-result" },
          div({ class: "result-value param-posterior" }, this.posterior.toFixed(3))
        )
      )
    );
  }

  view() {
    return div({ class: "app" },
      h1("Bayes' Calculator"),
      this.controls(),
      this.visualization(),
      this.equations()
    )
  }
}

new App({ root: new BayesCalculator(), id: "app" })

Markdown source · More bulbs by samples · Typebulb home