DEV Community

Cover image for Why Statistics Needs Animation — And How to Do It in Python
Rishabh Bhartiya
Rishabh Bhartiya

Posted on • Originally published at Medium

Why Statistics Needs Animation — And How to Do It in Python

Originally published on Medium.

Static diagrams freeze what statistics is meant to show in motion. Here’s how one Python library is closing the gap.

Ask a statistics student what a confidence interval means. Most can recite the definition. Ask them to feel it — to have immediate intuitive grasp of why it shrinks as sample size grows — almost none can.”

This isn’t a failure of intelligence. It’s a failure of medium.

Statistics describes processes that unfold over time: samples being drawn, distributions emerging, parameters shifting, evidence accumulating. Yet we almost universally teach it through static images and symbolic notation. The normal distribution on a whiteboard is frozen. The actual thing it describes — the shape that emerges from infinite repeated measurement — is alive.

The gap between the symbol and the intuition is where most statistical confusion lives. Animation closes that gap. And with the right tooling, building statistical animations in Python is now surprisingly straightforward.

Why Manim — and why it needed a statistics layer
Manim Community is the open-source mathematical animation engine behind much of 3Blue1Brown’s visual mathematics work. It renders at publication quality and operates on a clean object model where every visual element is a mobject with position, color, and transform behaviour.

The problem for statistics specifically: Manim operates at a very low level of abstraction. Want to animate a normal distribution with a shadeable region? You’re writing curve geometry and area calculation code before you’ve touched a single line of statistics.

Learn about Medium’s values
statanim raises that abstraction level specifically for statistics. It provides statistical objects and animations as first-class Manim extensions — distributions, inference visualisations, regression surfaces, and physical probability props — all built on top of Manim Community.

Setup
You’ll need Python 3.10+, Manim Community, a LaTeX distribution, and statanim:

# Install Manim
pip install manim

# LaTeX (pick your platform)
# macOS:   brew install mactex-no-gui
# Ubuntu:  sudo apt-get install texlive-full
# Windows: install MiKTeX from https://miktex.org/
# Install statanim
pip install statanim
Enter fullscreen mode Exit fullscreen mode

Verify your setup with:

from manim import *
from statanim.distributions.normal3d import NormalCurve3D
from statanim.props.card import Card3D
from statanim.core.colors import DARK_THEME
Enter fullscreen mode Exit fullscreen mode

If that imports cleanly, you’re ready.

Part 1: Animating Distributions
The normal distribution — parameter sweep
The most common introductory statistics animation: a normal distribution whose sigma changes, showing how spread and peak height relate.

from manim import *
from statanim.distributions.normal3d import NormalCurve3D

class NormalSweep(Scene):
    def construct(self):
        axes = Axes(
            x_range=[-5, 5, 1],
            y_range=[0, 0.9, 0.1],
            x_length=10,
            y_length=5
        )
        self.play(Create(axes))
        curve = NormalCurve3D(mu=0, sigma=1, axes=axes)
        self.play(Create(curve))
        self.wait(1)
        # shade one-sigma region
        self.play(curve.shade_region(x_min=-1, x_max=1))
        self.wait(1)
        # morph to sigma = 2
        curve2 = NormalCurve3D(mu=0, sigma=2, axes=axes)
        self.play(Transform(curve, curve2))
        self.wait(2)
Enter fullscreen mode Exit fullscreen mode

Run it with manim -pql normal_sweep.py NormalSweep. The -pql flag renders at low quality for fast preview; switch to -pqh for production output.

_What you get: a curve that draws itself, shades the 68% region, then morphs wider as sigma doubles — the relationship between sigma and spread made immediately visible.
_

Part 2: Probability from First Principles
Animated probability trees
Conditional probability is notoriously difficult to teach. The formula is simple; the intuition is not. An animated tree that builds itself branch by branch changes that.

from statanim.probability.tree import ProbabilityTree3D

class ConditionalProb(Scene):
    def construct(self):
        tree = ProbabilityTree3D(
            branches={"Rain": 0.3, "No Rain": 0.7},
            conditionals={
                "Rain":    {"Carry Umbrella": 0.9, "No Umbrella": 0.1},
                "No Rain": {"Carry Umbrella": 0.2, "No Umbrella": 0.8}
            }
        )
        tree.move_to(ORIGIN)
        self.play(tree.build())
        self.wait(2)
Enter fullscreen mode Exit fullscreen mode

The tree renders left to right, labelling each branch as it draws. Joint probabilities at the leaves are calculated and displayed automatically.

The Birthday Paradox

One of the most effective demonstrations of counterintuitive probability:

from statanim.probability.classical import BirthdayParadox3D

class Birthday(Scene):
    def construct(self):
        scene = BirthdayParadox3D()
        self.play(scene.animate_growth())
        self.wait(2)
Enter fullscreen mode Exit fullscreen mode

The animation shows the probability of a shared birthday growing with room size — the curve crossing 50% far sooner than most people expect is the moment that tends to land hardest.

Part 3: Statistical Inference

Confidence intervals — the most misunderstood concept in statistics
Ask ten people what a 95% confidence interval means.
A significant number will say “there’s a 95% chance the true parameter is in this interval.” That’s not what it means — and animating repeated sampling makes the correct interpretation viscerally clear.

from statanim.inference.confidence import ConfidenceInterval3D

class CIDemo(Scene):
    def construct(self):
        ci = ConfidenceInterval3D(
            true_mean=0,
            sigma=1,
            n=30,
            confidence=0.95,
            n_samples=20
        )
        self.play(ci.animate_sampling())
        self.wait(2)
Enter fullscreen mode Exit fullscreen mode

This draws 20 confidence intervals from repeated samples of size 30. Roughly 19 capture the true mean (marked with a vertical line). The one or two that miss it make the frequentist interpretation concrete in a way no amount of re-reading the definition achieves.

Hypothesis testing — Type I and Type II errors

from statanim.inference.hypothesis import HypothesisRegion3D

class HypothesisTest(Scene):
    def construct(self):
        h = HypothesisRegion3D(
            null_mean=0, alt_mean=1.5,
            sigma=1, alpha=0.05
        )
        self.play(h.show_null())
        self.play(h.show_critical_region())
        self.play(h.show_alternative())
        self.play(h.show_type2_error())
        self.wait(2)
Enter fullscreen mode Exit fullscreen mode

This sequence draws the null distribution, marks the critical region (Type I error), then overlays the alternative distribution and shades the Type II error — showing exactly why reducing alpha increases beta.

Getting started
The package is at v0.1.2, MIT licensed, and actively developed. Issues and contributions are welcome on GitHub.

PyPI

GitHub

API Reference

Built on Manim Community. Statistical algorithms from SciPy and NumPy. Inspired by the mathematical animation work of 3Blue1Brown.

Top comments (0)