stats API
Luna-Flow/mare_mark/stats 把计时数组转化为描述性摘要、成对比较、带种子的自助法区间以及过滤了离群值的视图。每个函数都是纯函数:它读取参数、分配结果,从不接触事件流。每个估计量背后的数学推导见 stats 设计。
import {
"Luna-Flow/mare_mark/model",
"Luna-Flow/mare_mark/stats",
}
描述性摘要
SummaryStats
SummaryStats 保存一个样本的描述性统计量。
pub struct SummaryStats {
count : Int
min : Double
max : Double
mean : Double
median : Double
mad : Double
q1 : Double
q3 : Double
iqr : Double
std_dev : Double
}
对于顺序统计量为 的样本 ,各字段为:
| 字段 | 定义 |
|---|---|
count | |
min, max | , |
mean | |
median | |
q1, q3 | , |
iqr | |
mad | 的中位数,不带正态一致性因子 |
std_dev | 总体标准差 |
是线性插值的样本分位数(Hyndman–Fan 第 7 型):令 ,
这些字段在包外是只读的;其值由 summarize 产生。
summarize
summarize 为一个值数组计算 SummaryStats。
pub fn summarize(Array[Double]) -> SummaryStats
输入在排序前会被复制,因此调用者的数组保持原有顺序。空数组得到 count == 0,其余字段均为 0.0;读取其他字段前请先检查 count。非有限值不会被拒绝:一个 NaN 会使均值和标准差变为 NaN,且它在排序中的位置是未指定的。代价:两次排序需要 时间,额外空间为 。
test "summarize a sample" {
let summary = @stats.summarize([4.0, 1.0, 3.0, 2.0, 100.0])
inspect(summary.count, content="5")
inspect(summary.median, content="3")
inspect(summary.q1, content="2")
inspect(summary.q3, content="4")
inspect(summary.mad, content="1")
inspect(summary.mean, content="22")
}
一个慢值就把均值拉到了 22;中位数、四分位数和 MAD 则不受影响。这就是下面的比较建立在中位数之上的原因。
成对比较
Decision
Decision 是成对比较的判定结果。
pub enum Decision {
Faster
Slower
Equivalent
Invalid
Unknown
}
| 构造器 | 含义 |
|---|---|
Faster | 相对中位数差值小于或等于 |
Slower | 相对中位数差值大于或等于 |
Equivalent | 相对中位数差值严格位于 与 之间 |
Invalid | 两个数组长度不同 |
Unknown | 没有任何配对 |
这里 是以 practical_delta_pct 传入的实际阈值。Decision 在包外是只读的:你可以对它进行匹配,但不能构建它。
Interval
Interval 是一对边界,并标注了它所属的阶段。
pub struct Interval {
low : Double
high : Double
mode : @model.IntervalMode
}
compare_paired 用成对差值的四分位距填充它;bootstrap_interval 和 compare_paired_with_bootstrap 用百分位自助法区间填充它。mode 是从调用者处复制来的标签;它不会改变计算。
Comparison
Comparison 是把候选与基线进行比较的结果。
pub struct Comparison {
baseline_id : String
candidate_id : String
relative_delta_pct : Double
speedup : Double
interval : Interval
decision : Decision
valid_samples : Int
}
| 字段 | 含义 |
|---|---|
relative_delta_pct | ,当 时为 0.0 |
speedup | ,当 时为 1.0 |
interval | 成对差值的边界,单位与输入相同(例如 µs) |
decision | 上文描述的 Decision |
valid_samples | 配对数, |
是基线数组, 是候选数组, 是成对差值。speedup 大于 1.0 表示候选更快。
paired_deltas
paired_deltas 用相同索引处的候选值减去基线值。
pub fn paired_deltas(Array[Double], Array[Double]) -> Array[Double]
第一个参数是基线,第二个是候选。结果有 个元素:较长数组多出的值会被忽略,因此当长度不一致应视为错误时,请自行检查长度。
compare_paired
compare_paired 用实际阈值比较两个对齐的计时数组。
pub fn compare_paired(String, String, Array[Double], Array[Double], Double, @model.IntervalMode) -> Comparison
参数:基线 id、候选 id、基线值、候选值、以百分比表示的实际阈值 ,以及区间标签。两个数组的索引 必须描述同一个数据集、重复和区组。决策只使用点估计 ;区间是差值的四分位距 ,是描述性的,而不是置信区间。
test "paired comparison" {
let baseline = [100.0, 102.0, 98.0, 101.0, 99.0, 103.0, 97.0, 100.0]
let candidate = [90.0, 93.0, 88.0, 92.0, 91.0, 94.0, 87.0, 90.0]
let comparison = @stats.compare_paired(
"baseline",
"candidate",
baseline,
candidate,
2.0,
@model.confirmatory_interval(),
)
inspect(comparison.relative_delta_pct, content="-9.5")
inspect(comparison.speedup, content="1.1049723756906078")
inspect(comparison.interval.low, content="-10")
inspect(comparison.interval.high, content="-9")
inspect(@stats.is_faster(comparison), content="true")
}
is_faster
is_faster 报告比较的决策是否为 Faster。
pub fn is_faster(Comparison) -> Bool
它为仪表盘和门禁提供便利。请在它旁边记录完整的 Comparison:false 同样涵盖 Slower、Equivalent、Invalid 和 Unknown。
自助法区间
BootstrapError
BootstrapError 解释为何无法计算自助法区间。
pub(all) enum BootstrapError {
EmptySamples
MismatchedPairs(Int, Int)
InvalidResamples(Int)
InvalidConfidence(Double)
NonFiniteSample(Int)
}
| 构造器 | 触发条件 |
|---|---|
EmptySamples | 没有可重抽样的内容 |
MismatchedPairs(baseline, candidate) | 成对数组长度不同 |
InvalidResamples(count) | 重抽样次数不是正数 |
InvalidConfidence(pct) | 置信度不是严格介于 0 和 100 之间的有限数 |
NonFiniteSample(index) | index 处的值为 NaN 或无穷大 |
bootstrap_interval
bootstrap_interval 为样本中位数计算带种子的百分位自助法区间。
pub fn bootstrap_interval(Array[Double], UInt64, Int, Double, @model.IntervalMode) -> Result[Interval, BootstrapError]
参数:值、种子、重抽样次数 、以百分比表示的置信水平 ,以及区间标签。该函数有放回地抽取 个大小为 的重抽样样本,取每个样本的中位数,并返回这些中位数在 和 处的第 7 型分位数,其中 。检查按空输入、重抽样次数、置信度、有限性的顺序进行,返回第一个失败。
结果只是其参数的函数:相同的值、种子、 和 在每个目标上都给出相同的边界。边界总是满足 low high 。种子 0 会被替换为一个固定的非零常数。代价: 时间和 空间。
test "bootstrap interval of a median" {
let deltas = [-10.0, -9.0, -10.0, -9.0, -8.0, -9.0, -10.0, -10.0]
let interval = @stats.bootstrap_interval(
deltas,
42UL,
1000,
95.0,
@model.confirmatory_interval(),
).unwrap()
inspect(interval.low, content="-10")
inspect(interval.high, content="-9")
let error = @stats.bootstrap_interval(
deltas,
42UL,
1000,
100.0,
@model.confirmatory_interval(),
)
inspect(error is Err(@stats.BootstrapError::InvalidConfidence(_)), content="true")
}
compare_paired_with_bootstrap
compare_paired_with_bootstrap 即把四分位区间替换为成对差值中位数的自助法区间的 compare_paired。
pub fn compare_paired_with_bootstrap(String, String, Array[Double], Array[Double], Double, @model.IntervalMode, UInt64, Int, Double) -> Result[Comparison, BootstrapError]
前六个参数与 compare_paired 相同;后三个是种子、重抽样次数和以百分比表示的置信度。与 compare_paired 不同,长度不等是错误(MismatchedPairs),空数组也是错误(EmptySamples)。relative_delta_pct、speedup、decision 和 valid_samples 与 compare_paired 的结果相同;只有 interval 不同。区间的单位与输入相同,而决策以百分比表示。
test "paired comparison with a bootstrap interval" {
let baseline = [100.0, 102.0, 98.0, 101.0, 99.0, 103.0, 97.0, 100.0]
let candidate = [90.0, 93.0, 88.0, 92.0, 91.0, 94.0, 87.0, 90.0]
let comparison = @stats.compare_paired_with_bootstrap(
"baseline",
"candidate",
baseline,
candidate,
2.0,
@model.confirmatory_interval(),
7UL,
2000,
95.0,
).unwrap()
inspect(comparison.decision is Faster, content="true")
inspect(comparison.interval.low, content="-10")
inspect(comparison.interval.high, content="-9")
}
离群值视图
filter_outliers
filter_outliers 按原始顺序返回离群值策略保留下来的值。
pub fn filter_outliers(Array[Double], @model.OutlierPolicy) -> Array[Double]
| 策略 | 保留的值 |
|---|---|
ReportOnly | 所有值(一个副本) |
TukeyFence | |
MADTrim |
输入数组永远不会被修改。当超过一半的值相等时,MAD 为 0,MADTrim 只保留等于中位数的值。请把结果用作派生视图;保留原始观测。
test "outlier views" {
let values = [10.0, 11.0, 10.0, 12.0, 11.0, 48.0]
debug_inspect(
@stats.filter_outliers(values, @model.OutlierPolicy::TukeyFence),
content="[10, 11, 10, 12, 11]",
)
inspect(values.length(), content="6")
}