skip to content

Brad Walker

Drawing beta pdfs in (P)react & D3

introduction

Last fall I worked on an MVP for visualizing experiment results. The goal of the project was to give LaunchDarkly user’s a way to easily interpret results of a given treatment and quickly make a decision.

While we use both normal and beta distributions to display results, this article is focused on understanding beta probability density functions and how to render them with VisX, D3 and [P]react.

Understanding the Beta Distribution and Probability Density Functions (PDFs)

The Beta distribution is a family of continuous probability distributions defined on the interval [0, 1], parameterized by two positive shape parameters, alpha (α) and beta (β). These parameters determine the distribution’s shape, making the Beta distribution suitable for variables like proportions or percentages, such as conversion rates in A/B testing.

The Probability Density Function (PDF) of the Beta distribution describes the likelihood of a random variable assuming a value within a possible range. The betaPDF function in the provided code calculates the PDF for the Beta distribution, determining the probability density at each x value according to the Beta distribution.

Generating a Beta Distribution with TypeScript

To calculate a beta distribution, we pass our two shape parameters, α and β, to our randomBeta function. This generates the y domain inside our beta PDF. The x domain is linearly generated by creating n samples between the bounds of [0, 1].

type Datum = { x: number; y: number };

const domain = (n: number) => [...Array(n).keys()].map((x) => x / n);

const betaPDF = (x: number, alpha: number, beta: number, result: number) =>
	(Math.pow(x, alpha - 1) * Math.pow(1 - x, beta - 1)) / result;

export const betaDistribution = (
	samples: number,
	alpha: number,
	beta: number
): Datum[] => {
	const betaFn = randomBeta(alpha, beta);
	const result = betaFn();

	return domain(samples).map((x) => ({
		x,
		y: betaPDF(x, alpha, beta, result),
	}));
};

The domain Function

The domain function in the code is instrumental in generating a set of evenly spaced values between 0 and 1. These values serve as the x-coordinates for the Beta distribution plot. Specifically, for a given n, the function produces an array of n elements, each initialized with its index value, and then divides each by n to ensure all values lie between 0 and 1.

Plotting the Distribution

The following code illustrates how to plot this distribution:

// Code for plotting the distribution
import { h } from "preact";
import { scaleLinear } from "@visx/scale";
import { AreaClosed } from "@visx/shape";
import { curveBasis } from "@visx/curve";
import { Group } from "@visx/group";
import { GridColumns } from "@visx/grid";
import { AxisBottom, AxisLeft } from "@visx/axis";
import { extent } from "d3-array";

type Datum = { x: number; y: number };

type ChartProps = {
	width: number;
	height: number;
	data: Datum[];
};

export const DensityPlot = ({ width, height, data }: ChartProps) => {
	// bounds
	const margin = { top: 40, right: 40, bottom: 64, left: 64 };
	const xMax = width - margin.left - margin.right;
	const yMax = height - margin.top - margin.bottom;

	const x = scaleLinear<number>({
		range: [1 / data.length, xMax],
		nice: true,
		clamp: true,
	});
	const y = scaleLinear<number>({ range: [yMax, 0], nice: true, clamp: true });

	const xDomain = extent(data, ({ x: q }) => q) as number[];
	const yDomain = extent(data, ({ y: p }) => p) as number[];

	x.domain(xDomain);
	y.domain(yDomain);

	return (
		<svg width={width} height={height}>
			<Group left={margin.left} top={margin.top}>
				<GridColumns scale={x} height={yMax} />
				<AreaClosed
					curve={curveBasis}
					data={data}
					yScale={y}
					fill={"rgb(230, 30, 113)"}
					x={(d: Datum) => x(d.x)}
					y={(d: Datum) => y(d.y)}
				/>
				<AxisLeft
					scale={y}
					tickValues={[]}
					label="density"
					labelClassName={styles.axisLabel}
					labelOffset={20}
				/>
				<AxisBottom
					scale={x}
					top={yMax}
					label={xLabel}
					labelClassName={styles.axisLabel}
					labelOffset={20}
				/>
			</Group>
		</svg>
	);
};

There’s a lot going on here. DensityPlot is our charting component that renders an svg. Within our component, we scale or x and y domains linearly) to generate their respective axis. The distribution itself is rendered with the <AreaClosed /> component which draws a curve with the area filled in underneath.

Demo

If we combine our chart with Preact signals, we get an interactive chart where the alpha, beta, and sample-size values can be adjusted to change the shape of the distribution.

  1. Example of a beta distribution
  2. d3/API.md at main · d3/d3 · GitHub
  3. visx | documentation
  4. Experimentation statistical methodology