Welford Online Mean and Stddev
Track count, mean, and sample standard deviation in one pass with Welford's algorithm, avoiding catastrophic cancellation.
Code
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))
Report a problem with this exampleOpens a pre-filled issue on GitHub.