Skip to content

Statistics Calculator — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// statistics — descriptive statistics over a free-form number list. Language: Kotlin (JVM 1.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 kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.sqrt

data class ParseResult(val values: List<Double>, val invalid: List<String>)
data class Stats(
    val count: Int, val sum: Double, val mean: Double, val median: Double, val mode: List<Double>,
    val min: Double, val max: Double, val range: Double, val variance: Double, val stddev: Double,
    val q1: Double, val q3: Double, val iqr: Double,
)

/* Split on comma/whitespace runs; only fully-parsed finite tokens count as values. */
fun parseNumbers(input: String): ParseResult {
    val values = mutableListOf<Double>()
    val invalid = mutableListOf<String>()
    for (tok in input.replace(',', ' ').split(Regex("\\s+"))) {
        if (tok.isEmpty()) continue
        val v = tok.toDoubleOrNull() // accepts inf/nan — filtered by isFinite
        if (v != null && v.isFinite()) values.add(v) else invalid.add(tok)
    }
    return ParseResult(values, invalid)
}

/* Linear-interpolation quantile (R-7 / NumPy / Excel PERCENTILE) over ascending, non-empty data. */
fun quantile(sorted: List<Double>, p: Double): Double {
    val h = (sorted.size - 1) * p
    val lo = floor(h).toInt()
    val hi = ceil(h).toInt()
    return if (lo == hi) sorted[lo] else sorted[lo] + (h - lo) * (sorted[hi] - sorted[lo])
}

/* Most frequent value(s), ascending; empty when every distinct value ties (uniform). */
fun computeMode(values: List<Double>): List<Double> {
    val freq = values.groupingBy { it }.eachCount()
    if (freq.size == 1) return listOf(values[0]) // a single distinct value is the mode
    val best = freq.values.max()
    val modes = freq.filterValues { it == best }.keys.sorted()
    return if (modes.size == freq.size) emptyList() else modes
}

fun summarize(values: List<Double>, sample: Boolean = true): Stats {
    if (values.isEmpty()) return Stats(0, Double.NaN, Double.NaN, Double.NaN, emptyList(), Double.NaN,
        Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN)
    val s = values.sorted()
    val sum = values.reduce(Double::plus) // sum in original order
    val mean = sum / values.size
    val ss = values.sumOf { (it - mean) * (it - mean) }
    // Sample variance needs n >= 2; the population estimator always divides by n.
    val variance = if (sample) (if (values.size >= 2) ss / (values.size - 1) else Double.NaN) else ss / values.size
    val q1 = quantile(s, 0.25)
    val med = quantile(s, 0.5)
    val q3 = quantile(s, 0.75)
    return Stats(values.size, sum, mean, med, computeMode(values), s.first(), s.last(), s.last() - s.first(),
        variance, sqrt(variance), q1, q3, q3 - q1)
}

fun main() {
    val (values, invalid) = parseNumbers("2, 4 4, 6, 8, 11, oops")
    val st = summarize(values) // sample=true — population: summarize(values, false)
    println("n=${st.count} sum=${st.sum} mean=${st.mean}")
    println("median=${st.median} q1=${st.q1} q3=${st.q3} iqr=${st.iqr}")
    println("min=${st.min} max=${st.max} range=${st.range}")
    println("variance(sample)=${st.variance} stddev=${st.stddev}")
    println("mode=${st.mode}  invalid=$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 →