Welford Online Mean and Stddev

Track count, mean, and sample standard deviation in one pass with Welford's algorithm, avoiding catastrophic cancellation.

PythonData Integrity

Code

snippet
from dataclasses import dataclass


@dataclass
class Welford:
    count: int = 0
    mean: float = 0.0
    m2: float = 0.0

    def add(self, value: float) -> None:
        self.count += 1
        delta = value - self.mean
        self.mean += delta / self.count
        self.m2 += delta * (value - self.mean)

    @property
    def variance(self) -> float:
        if self.count < 2:
            return 0.0
        return self.m2 / (self.count - 1)

    @property
    def stddev(self) -> float:
        return self.variance ** 0.5

    def merge(self, other: "Welford") -> None:
        if other.count == 0:
            return
        total = self.count + other.count
        delta = other.mean - self.mean
        self.mean += delta * other.count / total
        self.m2 += other.m2 + delta * delta * self.count * other.count / total
        self.count = total


def summarize(values: list[float]) -> dict[str, float | int]:
    stats = Welford()
    for value in values:
        stats.add(value)
    return {
        "count": stats.count,
        "mean": round(stats.mean, 4),
        "stddev": round(stats.stddev, 4),
    }


if __name__ == "__main__":
    sample = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]
    print(summarize(sample))
    left, right = Welford(), Welford()
    for value in sample[:3]:
        left.add(value)
    for value in sample[3:]:
        right.add(value)
    left.merge(right)
    print("merged:", round(left.mean, 4), round(left.stddev, 4))