Kotlin Notebook
Try this guide as a Kotlin Notebook with Kandy visualizations — run the cells to see charts and explore the data interactively.
Dataset
Copy
val responseTimeMs = doubleArrayOf(
89.2, 95.1, 87.6, 102.3, 91.8, 88.4, 96.7, 103.5, 90.1, 94.3,
88.9, 97.2, 105.8, 91.4, 93.6, 87.1, 99.0, 92.5, 96.1, 104.2,
90.7, 88.3, 101.6, 93.9, 95.4, 89.8, 98.3, 106.1, 91.0, 94.7
)
val errorsPerHour = doubleArrayOf(
2.0, 3.0, 1.0, 5.0, 2.0, 1.0, 4.0, 6.0, 2.0, 3.0,
1.0, 4.0, 7.0, 2.0, 3.0, 1.0, 5.0, 2.0, 4.0, 6.0,
2.0, 1.0, 5.0, 3.0, 3.0, 1.0, 4.0, 8.0, 2.0, 3.0
)
val memoryUsageMb = doubleArrayOf(
512.3, 528.1, 505.7, 545.2, 519.6, 508.4, 534.8, 551.3, 515.0, 526.7,
509.2, 537.1, 558.4, 517.8, 524.3, 503.1, 541.6, 520.9, 531.5, 549.7,
514.2, 506.8, 543.9, 522.5, 529.0, 511.4, 539.3, 561.2, 516.3, 527.4
)
val throughputRps = doubleArrayOf(
245.0, 238.0, 251.0, 225.0, 242.0, 249.0, 232.0, 218.0, 244.0, 236.0,
250.0, 230.0, 212.0, 243.0, 237.0, 253.0, 227.0, 241.0, 233.0, 220.0,
246.0, 252.0, 224.0, 239.0, 235.0, 248.0, 228.0, 210.0, 243.0, 234.0
)
Step 1: Summary Statistics
Copy
val rtSummary = responseTimeMs.describe()
val errSummary = errorsPerHour.describe()
val memSummary = memoryUsageMb.describe()
val tpSummary = throughputRps.describe()
rtSummary.mean; rtSummary.standardDeviation; rtSummary.min; rtSummary.max
errSummary.mean; errSummary.standardDeviation; errSummary.min; errSummary.max
memSummary.mean; memSummary.standardDeviation; memSummary.min; memSummary.max
tpSummary.mean; tpSummary.standardDeviation; tpSummary.min; tpSummary.max
Frequency distribution
Copy
val rtBins = responseTimeMs.frequencyTable(binCount = 5)
rtBins.forEach { bin ->
// bin.range, bin.count, bin.relativeFrequency
}
val errBins = errorsPerHour.frequencyTable(binSize = 2.0)
errBins.forEach { bin ->
// bin.range, bin.count, bin.cumulativeFrequency
}
Step 2: Distribution Shape
Copy
responseTimeMs.skewness() // positive = right-skewed
responseTimeMs.kurtosis() // positive excess = heavier tails than Normal
errorsPerHour.skewness()
errorsPerHour.kurtosis()
memoryUsageMb.skewness()
throughputRps.skewness()
Normality tests
Copy
shapiroWilkTest(responseTimeMs).pValue
shapiroWilkTest(errorsPerHour).pValue
shapiroWilkTest(memoryUsageMb).pValue
shapiroWilkTest(throughputRps).pValue
Fit a candidate distribution
Copy
val rtFit = NormalDistribution(
mu = responseTimeMs.mean(),
sigma = responseTimeMs.standardDeviation()
)
kolmogorovSmirnovTest(responseTimeMs, rtFit).pValue
val memFit = NormalDistribution(
mu = memoryUsageMb.mean(),
sigma = memoryUsageMb.standardDeviation()
)
kolmogorovSmirnovTest(memoryUsageMb, memFit).pValue
Step 3: Correlations
Copy
// Correlation matrix across all four metrics
val matrix = correlationMatrix(responseTimeMs, errorsPerHour, memoryUsageMb, throughputRps)
// matrix[i][j] gives Pearson r between metrics i and j
// Deeper look at specific relationships
val rtVsErrors = pearsonCorrelation(responseTimeMs, errorsPerHour)
rtVsErrors.coefficient // positive = errors rise with latency
rtVsErrors.pValue
val rtVsThroughput = spearmanCorrelation(responseTimeMs, throughputRps)
rtVsThroughput.coefficient // negative = latency rises when throughput drops
rtVsThroughput.pValue
Regression
Copy
// Model: how does error count relate to response time?
val regression = simpleLinearRegression(errorsPerHour, responseTimeMs)
regression.slope // ms increase per additional error/hour
regression.intercept // baseline response time at zero errors
regression.rSquared // proportion of variance explained
regression.predict(4.0) // expected latency at 4 errors/hour
Step 4: Compare Periods
Split the data into two halves and check whether performance changed.Copy
val firstHalfRt = responseTimeMs.sliceArray(0 until 15)
val secondHalfRt = responseTimeMs.sliceArray(15 until 30)
val periodComparison = tTest(firstHalfRt, secondHalfRt)
periodComparison.pValue
periodComparison.isSignificant()
// Non-parametric alternative
val periodRank = mannWhitneyUTest(firstHalfRt, secondHalfRt)
periodRank.pValue
Copy
val firstHalfTp = throughputRps.sliceArray(0 until 15)
val secondHalfTp = throughputRps.sliceArray(15 until 30)
val tpComparison = tTest(firstHalfTp, secondHalfTp)
tpComparison.pValue
Step 5: Normalize and Rank
Compare metrics on a common scale.Copy
// Z-score: values become standard deviations from mean
val rtNormalized = responseTimeMs.zScore()
val memNormalized = memoryUsageMb.zScore()
// Both are now on the same scale and can be compared directly
// Min-max scaling to [0, 1]
val rtScaled = responseTimeMs.minMaxNormalize()
val tpScaled = throughputRps.minMaxNormalize()
// Rank the days by response time (worst days get highest rank)
val rtRanked = responseTimeMs.rank()
Z-score normalization is useful for combining metrics into a composite score:
a day with high z-scores across response time, errors, and memory may warrant investigation.