kmp openharmony 分位数与箱线图五数概括分析
本文介绍了一个基于Kotlin Multiplatform的分位数与箱线图分析工具,用于评估数据分布特征。该工具可计算五数概括(Min/Q1/Median/Q3/Max)、常见分位数(P90/P95/P99),并通过箱线图IQR规则检测离群值。核心功能包括:对输入数值序列进行排序与插值计算;输出完整分析报告;帮助快速识别长尾分布与异常值。适用于接口耗时分析、请求体大小评估、任务耗时诊断等场景,比平

在接口耗时、请求大小、任务执行时长等场景中,我们经常需要回答这样的问题:
这批数据的整体分布是偏集中还是偏长尾?
典型请求“中位数”表现如何?有没有 P95/P99 被少数极慢请求拖高?
有没有明显的离群值(outlier)需要单独关注?
本案例基于 Kotlin Multiplatform(KMP)与 OpenHarmony,实现了一个分位数与箱线图五数概括分析器:
- 对输入的数值序列进行排序与插值计算;
- 输出 Min / Q1 / Median / Q3 / Max 五数概括;
- 计算常见分位数 P90 / P95 / P99;
- 基于箱线图 IQR 规则检测上下侧离群值;
- 通过 ArkTS 单页面展示完整分析报告,帮助你快速理解分布形态与长尾风险。
一、问题背景与典型场景
在 AIOps 与性能分析中,平均值往往是不够的。典型场景包括:
-
接口耗时分布分析
SLO 可能要求“95% 请求耗时低于 200ms”,此时 P95 比平均值更关键;极少数 P99/P99.9 请求是否超时,决定体验的稳定性。 -
请求体大小与流量分布
需要知道大部分请求体是否集中在一个区间,还是存在少数“超大请求”拖高整体资源消耗。 -
批处理任务耗时诊断
一批任务的执行时间差异很大,需要判断是“整体偏慢”还是“少数极慢任务”导致整体指标异常。 -
数据质量与异常值检测
对指标做箱线图分析,可以快速标记超出正常范围的异常值,为后续告警与根因分析提供线索。
这些问题可以抽象为:
给定数值样本序列 \( x_0, x_1, \dots, x_{n-1} \),
计算五数概括 (Min, Q1, Median, Q3, Max) 以及若干分位数 (P90, P95, P99),
并根据 IQR 规则检测异常离群值。
二、Kotlin 分位数与箱线图分析引擎
1. 输入格式设计
为与前几个案例保持一致,本案例继续使用简单的文本配置输入:
series=120,95,180,210,160,300,240
也支持直接裸写一行或多行数值序列,例如:
120,95,180,210,160,300,240
80,90,110
解析逻辑会尝试从每一行中按 , / 空格 / ; / 换行拆分并转换为 Double。
2. Kotlin 分析主入口
在 App.kt 中,我们定义了对外暴露的分析函数,并通过 @JsExport 让 OpenHarmony 端可以直接调用:
@JsExport
fun percentileBoxplotAnalyzer(inputData: String): String {
val sanitized = inputData.trim()
if (sanitized.isEmpty()) {
return "❌ 输入为空,请按 series=120,95,180,... 形式提供数据"
}
val lines = sanitized.lines()
.map { it.trim() }
.filter { it.isNotEmpty() }
val values = mutableListOf<Double>()
for (line in lines) {
if (line.startsWith("series=", ignoreCase = true)) {
val parsed = line.substringAfter("=")
.split(",", " ", ";", "\n")
.mapNotNull { it.trim().takeIf { s -> s.isNotEmpty() }?.toDoubleOrNull() }
values += parsed
} else {
// 也允许直接裸序列行
val parsed = line.split(",", " ", ";", "\n")
.mapNotNull { it.trim().takeIf { s -> s.isNotEmpty() }?.toDoubleOrNull() }
values += parsed
}
}
if (values.isEmpty()) {
return "❌ 未解析到任何数值,请检查 series=120,95,180,... 的格式是否正确"
}
val sorted = values.sorted()
val n = sorted.size
fun percentile(p: Double): Double {
if (n == 1) return sorted[0]
val rank = p * (n - 1)
val low = rank.toInt()
val high = min(low + 1, n - 1)
val frac = rank - low
return sorted[low] * (1 - frac) + sorted[high] * frac
}
val minValue = sorted.first()
val maxValue = sorted.last()
val median = percentile(0.5)
val q1 = percentile(0.25)
val q3 = percentile(0.75)
val p90 = percentile(0.9)
val p95 = percentile(0.95)
val p99 = percentile(0.99)
val iqr = q3 - q1
val lowerFence = q1 - 1.5 * iqr
val upperFence = q3 + 1.5 * iqr
val lowerOutliers = sorted.filter { it < lowerFence }
val upperOutliers = sorted.filter { it > upperFence }
val builder = StringBuilder()
builder.appendLine("📦 分位数与箱线图五数概括分析报告")
builder.appendLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
builder.appendLine("样本数量: $n")
builder.appendLine("最小值 (Min): $minValue")
builder.appendLine("Q1 (25% 分位): ${round2(q1)}")
builder.appendLine("中位数 (P50): ${round2(median)}")
builder.appendLine("Q3 (75% 分位): ${round2(q3)}")
builder.appendLine("最大值 (Max): $maxValue")
builder.appendLine("IQR (四分位距): ${round2(iqr)}")
builder.appendLine()
builder.appendLine("🎯 典型分位数")
builder.appendLine("P90: ${round2(p90)}")
builder.appendLine("P95: ${round2(p95)}")
builder.appendLine("P99: ${round2(p99)}")
builder.appendLine()
builder.appendLine("🚩 箱线图离群值检测")
builder.appendLine("下边界阈值 (Q1 - 1.5 * IQR): ${round2(lowerFence)}")
builder.appendLine("上边界阈值 (Q3 + 1.5 * IQR): ${round2(upperFence)}")
builder.appendLine()
if (lowerOutliers.isEmpty() && upperOutliers.isEmpty()) {
builder.appendLine("✅ 未检测到明显离群值,分布相对集中")
} else {
if (lowerOutliers.isNotEmpty()) {
builder.appendLine("⬇ 下侧离群值 (${lowerOutliers.size} 个): ${lowerOutliers.joinToString()}")
}
if (upperOutliers.isNotEmpty()) {
builder.appendLine("⬆ 上侧离群值 (${upperOutliers.size} 个): ${upperOutliers.joinToString()}")
}
}
builder.appendLine()
builder.appendLine("🧠 工程化解读")
builder.appendLine("- 通过五数概括 (Min/Q1/Median/Q3/Max) 可以快速把握整体分布轮廓;")
builder.appendLine("- P90/P95/P99 有助于评估接口 SLO 是否容易被长尾拖累;")
builder.appendLine("- 利用箱线图离群值规则,可以快速标出“极慢请求”或“异常数据点”,用于告警与优化。")
return builder.toString().trim()
}
这里采用了较为常见的“线性插值分位数”实现方式:
- 先对样本
sorted(),得到有序序列; - 对于目标分位数 \( p \),计算秩值 \( r = p \cdot (n-1) \),在相邻两个点之间做线性插值;
- 同一实现复用到 P50 / P90 / P95 / P99 以及 Q1/Q3 上,保证行为一致。
离群值检测则基于经典箱线图规则:
- 计算 IQR = Q3 - Q1;
- 下边界 = Q1 - 1.5 * IQR,上边界 = Q3 + 1.5 * IQR;
- 小于下边界或大于上边界的样本视为“潜在离群值”,分别归类为下侧/上侧离群。
三、OpenHarmony 侧调用与 UI 展示思路
在 ArkTS 页面中,可以像之前案例一样从 hellokjs 导入该分析函数:
import { percentileBoxplotAnalyzer } from './hellokjs'
页面只需要维护一个样本输入状态,例如:
seriesInput:"120,95,180,210,160,300,240"
调用时自动加上 series= 前缀(如果用户没有写):
const seriesLine = this.seriesInput.includes('series=') ? this.seriesInput : `series=${this.seriesInput}`
const payload = seriesLine
this.result = percentileBoxplotAnalyzer(payload)
展示区域可以使用等宽字体 Text 直接呈现 Kotlin 返回的多行报告,在 UI 上重点突出:
- 五数概括部分(Min/Q1/Median/Q3/Max);
- P90/P95/P99 行;
- “箱线图离群值检测”下的告警行(使用颜色或图标增强可读性)。
如果需要进一步可视化,可以在 ArkTS 侧根据这些数值画出简化版“水平箱线图”,例如用若干 Row + Flex 模拟刻度条。
四、复杂度与工程实践建议
当前实现的主要开销来自排序:
- 排序时间复杂度:\( O(n \log n) \);
- 之后的分位数和 IQR 计算都是 \( O(1) \) 或 \( O(n) \) 级别;
- 整体复杂度约为 \( O(n \log n) \),在大多数终端侧场景下足够使用。
在工程实践中,可以考虑以下扩展:
-
在线分位数估计
对于极大规模或流式数据,可以引入 P² 算法或 t-digest 等近似分位数算法,在不保存全部样本的前提下估计 P95/P99。 -
预聚合 + 分位数分析
先按接口 / 服务 / 租户等维度聚合统计,再对每个维度的样本做分位数 / 箱线图分析,构建多维度 SLO 健康度看板。 -
与 TopK / 阈值 / 滑动窗口联动
将本案例与前几个案例组合:- 对“滑动窗口峰值序列”做分位数分析;
- 对“TopK 峰值样本”的局部分布做箱线图概览;
- 在阈值附近窗口中单独计算 P95/P99 评估风险。
-
端侧离线诊断
利用 KMP + OpenHarmony 的跨端特性,将这套分位数分析逻辑下沉到终端设备,在离线或弱网环境中依然能完成基本的性能分布诊断。
通过这个分位数与箱线图五数概括案例,你可以把统计学中的基础工具直接落地到接口耗时分析、容量规划与异常检测等工程场景中,
在保持实现简洁的前提下,大幅提升对“长尾”和“离群值”的洞察能力。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)