soma.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.

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.

Generated from soma describe on this file.

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

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ 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}

Builtins used

abs, floor, len, map, sort, sqrt, sum, to_float

Source file: /corpus/math/statistics.cell · examples/corpus/math/statistics.cell