https://kotlinlang.org logo
#getting-started
Title
# getting-started
p

poohbar

04/12/2022, 8:50 PM
Is there a built-in to give me stats about a collection of numbers? E.g. avg, min, max at the same time?
h

hfhbd

04/12/2022, 9:22 PM
I don't think there is a built-in solution.
Copy code
import kotlinx.serialization.*
import kotlin.js.*
import kotlin.math.*

@Serializable
data class Stats(
    val name: String,
    val count: Int,
    val min: Double,
    val max: Double,
    val median: Double,
    val average: Double,

    val std: Double,
    val variance: Double,

    val lowerQuantile: Double,
    val upperQuantile: Double,

    val uniqueValues: List<Double>?,

    val iqr: Double,
    val whiskerLength: Double,
    val lowerWhisker: Double?,
    val upperWhisker: Double?,
    @Transient
    val lowerExtrema: List<Double>? = null,
    @Transient
    val upperExtrema: List<Double>? = null
) {

    companion object {
        @JsName("createSelector")
        operator fun <T> invoke(
            name: String,
            values: Iterable<T>,
            whiskerLength: Double = 1.5,
            selector: (T) -> Double
        ): Stats? = invoke(sortedValues = values.map(selector).sorted(), whiskerLength = whiskerLength, name = name)

        @JsName("create")
        operator fun invoke(name: String, sortedValues: List<Double>, whiskerLength: Double = 1.5): Stats? {
            val count = sortedValues.size
            if (count == 0) {
                return null
            }
            val min: Double = sortedValues.minOrNull()!!
            val max: Double = sortedValues.maxOrNull()!!
            val median: Double = sortedValues[sortedValues.size / 2]
            val average: Double = sortedValues.average()

            val variance = sortedValues.sumOf { (it - average).pow(2) } / count
            val std = sqrt(variance)

            val lowerQuantile: Double = sortedValues[(sortedValues.size * 0.25).toInt()]
            val upperQuantile: Double = sortedValues[(sortedValues.size * 0.75).toInt()]

            val iqr = upperQuantile - lowerQuantile

            val lowerWhisker = (lowerQuantile - whiskerLength * iqr).let {
                if (min >= it) {
                    max(min, it)
                } else {
                    sortedValues.takeLastWhile { v -> v > it }.firstOrNull()
                }
            }
            val upperWhisker = (upperQuantile + whiskerLength * iqr).let {
                if (max <= it) {
                    min(max, it)
                } else {
                    sortedValues.takeWhile { v -> v <= it }.lastOrNull()
                }
            }

            val lowerExtrema =
                lowerWhisker?.let { sortedValues.takeWhile { it < lowerWhisker } }
            val upperExtrema = upperWhisker?.let {
                sortedValues.takeLastWhile { it > upperWhisker }
            }

            val uniqueValues = sortedValues.distinct().takeIf { it.size <= 15 }

            return Stats(
                name = name,
                count = count,
                min = min, max = max,
                median = median,
                average = average,
                variance = variance,
                std = std,
                lowerQuantile = lowerQuantile,
                upperQuantile = upperQuantile,
                iqr = iqr,
                whiskerLength = whiskerLength,
                lowerWhisker = lowerWhisker,
                upperWhisker = upperWhisker,
                lowerExtrema = lowerExtrema,
                upperExtrema = upperExtrema,
                uniqueValues = uniqueValues
            )
        }
    }
}
If you need an efficient calculation, you could loop over the elements once and use
max
,
min
,
count
(for
avg
) variables.
m

Michael de Kaste

04/12/2022, 9:55 PM
there isn't in kotlin, but you can convert to a stream from java and get SummaryStatistics
yourList.stream().collect(Collectors.summarizingLong(Long::longValue));
Copy code
LongSummaryStatistics for range 1-5
Count: 5
Avg: 3.0
Min: 1
Max: 5
Sum: 15
🆒 1
p

Paul Griffith

04/12/2022, 11:08 PM
TIL intsummarystatistics and the like exist easy enough to adapt to an extension method
Copy code
fun Iterable<Int>.statistics(): IntSummaryStatistics {
            return fold(IntSummaryStatistics()) { statistics, next ->
                statistics.accept(next)
                statistics
            }
        }
k

Klitos Kyriacou

04/13/2022, 7:59 AM
It's a pity it doesn't include standard deviation. (But then that's just one of many things; where would it stop.)
m

Michael de Kaste

04/13/2022, 8:04 AM
@Paul Griffith Oh I like that extension method, very nice!
would be cool to design your own statistiscs class where you can assign exactly which stats to track on creation
3 Views