Skip to content

Statistics Calculator — Swift source

Compute descriptive statistics - count, sum, mean, median, mode, min/max, range, variance, standard deviation, and quartiles (Q1/Q3/IQR) - from any list of numbers. Tolerates mixed separators and flags unparseable tokens. Choose sample (n−1) or population (n) variance. Everything runs 100% client-side.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// statistics — descriptive statistics over a free-form number list. Language: Swift (5.9+). Port of src/lib/statistics.ts — same logic as this dir's python.py: comma/whitespace parse (finite only), R-7 interpolated quantiles, mode (empty when uniform), sample vs population variance.
import Foundation

struct ParseResult { var values: [Double] = []; var invalid: [String] = [] }
struct Stats {
    var count = 0, sum = Double.nan, mean = Double.nan, median = Double.nan, mode: [Double] = []
    var min = Double.nan, max = Double.nan, range = Double.nan
    var variance = Double.nan, stddev = Double.nan, q1 = Double.nan, q3 = Double.nan, iqr = Double.nan
}

// Split on comma/whitespace runs; only fully-parsed finite tokens count as values.
func parseNumbers(_ input: String) -> ParseResult {
    var r = ParseResult()
    // Double("inf")/"nan" parse — the isFinite gate sends them to invalid.
    for tok in input.replacingOccurrences(of: ",", with: " ").split(whereSeparator: \.isWhitespace) {
        if let v = Double(tok), v.isFinite { r.values.append(v) } else { r.invalid.append(String(tok)) }
    }
    return r
}

// Linear-interpolation quantile (R-7 / NumPy / Excel PERCENTILE) over ascending, non-empty data.
func quantile(_ s: [Double], _ p: Double) -> Double {
    let h = Double(s.count - 1) * p
    let lo = Int(h.rounded(.down)), hi = Int(h.rounded(.up))
    return lo == hi ? s[lo] : s[lo] + (h - Double(lo)) * (s[hi] - s[lo])
}

// Most frequent value(s), ascending; empty when every distinct value ties (uniform).
func computeMode(_ values: [Double]) -> [Double] {
    var freq: [Double: Int] = [:] // keys use ==, so -0.0 and 0.0 collapse — matches JS Map keying
    for v in values { freq[v, default: 0] += 1 }
    if freq.count == 1 { return [values[0]] } // a single distinct value is the mode
    let best = freq.values.max()!
    let modes = freq.filter { $0.value == best }.map(\.key).sorted()
    return modes.count == freq.count ? [] : modes
}

func summarize(_ values: [Double], sample: Bool = true) -> Stats {
    guard !values.isEmpty else { return Stats() } // count 0, every numeric field NaN
    let s = values.sorted()
    let sum = values.reduce(0, +) // sum in original order
    let mean = sum / Double(values.count)
    let ss = values.reduce(0) { $0 + ($1 - mean) * ($1 - mean) }
    // Sample variance needs n >= 2; the population estimator always divides by n.
    let variance = sample
        ? (values.count >= 2 ? ss / Double(values.count - 1) : Double.nan)
        : ss / Double(values.count)
    var st = Stats()
    st.count = values.count; st.sum = sum; st.mean = mean
    st.median = quantile(s, 0.5); st.q1 = quantile(s, 0.25); st.q3 = quantile(s, 0.75); st.iqr = st.q3 - st.q1
    st.min = s[0]; st.max = s[s.count - 1]; st.range = st.max - st.min
    st.variance = variance; st.stddev = variance.squareRoot(); st.mode = computeMode(values)
    return st
}

let parsed = parseNumbers("2, 4 4, 6, 8, 11, oops")
let s = summarize(parsed.values) // sample=true — population: summarize(parsed.values, sample: false)
print("n=\(s.count) sum=\(s.sum) mean=\(s.mean)")
print("median=\(s.median) q1=\(s.q1) q3=\(s.q3) iqr=\(s.iqr)")
print("min=\(s.min) max=\(s.max) range=\(s.range)")
print("variance(sample)=\(s.variance) stddev=\(s.stddev)")
print("mode=\(s.mode)  invalid=\(parsed.invalid)")

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →