Descriptive statistics — mean, variance, stddev, median via sort
Mean is sum/len. Variance is the mean of squared deviations. Median sorts the sample and takes the middle (or the average of the two middles for an even count). All of it rides on list reductions and bracket indexing.
A Soma program from the math part of the verified corpus: 69 lines, cells Statistics, StatTests. It passes soma check with soma 3.2.4.
How it is built
The cell Statistics declares 5 signals in its face: mean(xs: List) -> Float, variance(xs: List) -> Float, stddev(xs: List) -> Float, median(xs: List) -> Float and run() -> Map. Handlers: mean (line 21), variance (line 25), stddev (line 31), median (line 35) and run (line 45).
The file also holds 1 test cell (StatTests) with 5 assertions, run by soma test.
The source
statistics.cell
// ============================================================================
// Descriptive statistics — mean, variance, stddev, median via sort
// ============================================================================
//
// Mean is sum/len. Variance is the mean of squared deviations. Median sorts
// the sample and takes the middle (or the average of the two middles for an
// even count). All of it rides on list reductions and bracket indexing.
//
// soma run statistics.cell run
// ============================================================================
cell Statistics {
face {
signal mean(xs: List) -> Float
signal variance(xs: List) -> Float
signal stddev(xs: List) -> Float
signal median(xs: List) -> Float
signal run() -> Map
}
on mean(xs: List) {
return to_float(sum(xs)) / to_float(len(xs))
}
on variance(xs: List) {
let m = mean(xs)
let sq = xs |> map(x => (to_float(x) - m) * (to_float(x) - m))
return sum(sq) / to_float(len(xs))
}
on stddev(xs: List) {
return sqrt(variance(xs))
}
on median(xs: List) {
let s = sort(xs)
let n = len(s)
let mid = floor(n / 2)
if n % 2 == 1 {
return to_float(s[mid])
}
return (to_float(s[mid - 1]) + to_float(s[mid])) / 2.0
}
on run() {
let xs = [2, 4, 4, 4, 5, 5, 7, 9]
return map(
"mean", mean(xs),
"variance", variance(xs),
"stddev", stddev(xs),
"median", median(xs)
)
}
}
cell test StatTests {
rules {
// mean of 2,4,4,4,5,5,7,9 = 40/8 = 5
assert mean([2, 4, 4, 4, 5, 5, 7, 9]) == 5.0
// textbook population variance = 4, stddev = 2
assert abs(variance([2, 4, 4, 4, 5, 5, 7, 9]) - 4.0) < 0.0000001
assert abs(stddev([2, 4, 4, 4, 5, 5, 7, 9]) - 2.0) < 0.0000001
// odd-count median
assert median([7, 1, 3]) == 3.0
// even-count median averages the two middles
assert median([1, 2, 3, 4]) == 2.5
}
}What the toolchain says
$ soma check statistics.cell
✓ All checks passed.$ soma test statistics.cell
test StatTests ...
✓ assert mean([2, 4, 4, 4, 5, 5, 7, 9]) == 5.0
✓ assert abs(variance([2, 4, 4, 4, 5, 5, 7, 9]) - 4.0) < 0.0000001
✓ assert abs(stddev([2, 4, 4, 4, 5, 5, 7, 9]) - 2.0) < 0.0000001
✓ assert median([7, 1, 3]) == 3.0
✓ assert median([1, 2, 3, 4]) == 2.5
5 tests: 5 passed, 0 failed$ soma verify statistics.cell
Verifying statistics.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)$ soma run statistics.cell run
{"mean": 5.0, "variance": 4.0, "stddev": 2.0, "median": 4.5}