Exponential Smoothing for Noisy Growth Measurements
Lichen colonies grow 0.1 to 5 millimetres per year. At that rate, daily measurement error (vibration, temperature drift, focus variation) exceeds monthly growth. Your raw data looks like random noise with a faint upward trend buried inside.
Exponential smoothing solves this by maintaining a running estimate that weighs new observations against accumulated history. Each measurement updates the estimate by a fraction α (alpha), typically 0.1 to 0.3. Small α means heavy smoothing—you trust the past more than the present. Large α means you respond quickly to recent changes but amplify noise.
function smooth(measurements: number[], alpha: number): number[] {
const smoothed = [measurements[0]];
for (let i = 1; i < measurements.length; i++) {
smoothed[i] = alpha * measurements[i] + (1 - alpha) * smoothed[i - 1];
}
return smoothed;
}
const raw = [10.2, 9.8, 10.5, 9.9, 10.3, 10.1, 10.7, 10.4];
console.log("α=0.2:", smooth(raw, 0.2).map(v => v.toFixed(2)));
console.log("α=0.6:", smooth(raw, 0.6).map(v => v.toFixed(2)));
With α=0.2, the output flattens jitter: [10.20, 10.12, 10.20, 10.14, 10.17, 10.16, 10.26, 10.29]. With α=0.6, it tracks rapid shifts but keeps more noise: [10.20, 9.96, 10.28, 10.05, 10.20, 10.14, 10.48, 10.43].
let smooth alpha measurements =
let rec loop acc = function
| [] -> List.rev acc
| x :: xs ->
let s = alpha *. x +. (1.0 -. alpha) *. List.hd acc in
loop (s :: acc) xs
in
match measurements with
| [] -> []
| first :: rest -> loop [first] rest
let raw = [10.2; 9.8; 10.5; 9.9; 10.3; 10.1; 10.7; 10.4]
let () = List.iter (Printf.printf "%.2f ") (smooth 0.2 raw)
The method dates to Robert G. Brown’s 1956 work on inventory forecasting—long before anyone thought to aim it at crusty rocks that grow slower than fingernails. But the math doesn’t care whether you’re smoothing sales data or thallus diameter. Both are noisy signals where the present informs the future, but the past anchors your confidence.