3 个稳定版本
1.0.2 | 2024 年 2 月 21 日 |
---|---|
1.0.1 | 2023 年 2 月 16 日 |
1.0.0 | 2023 年 2 月 15 日 |
#68 in 数学
每月 98 次下载
在 2 crates 中使用
90KB
2K SLoC
incr_stats
使用 Rust 的快速、可扩展、增量描述性统计
incr_stats
提供以下特性:时间优化和内存优化的可扩展描述性统计
- 增量更新(也称为
streaming
)意味着不存储源数据 - 在请求时计算统计数据几乎是瞬时的
- 比非增量等效项更快,同时需要远少于存储空间
- 允许快速更新已计算的统计数据
- 特别适合内存占用低的程序或数据集过大无法存储的应用程序,例如连续数据生成
- 提供包括偏度和峰度的描述性统计
- 提供总体和样本统计
- 防止因 NaN 和 Inf 造成的错误
- 进行了广泛的准确性测试
- 与 R 和 Octave 验证
- 为每个统计量提供了额外的优化批量函数
- 为每个统计量提供了等效的教科书批量函数
- 速度基准比较了增量计算和批量计算
包含的统计数据
incr_stats
统计包提供了以下描述性统计
count()
min()
max()
sum()
mean()
population_variance()
sample_variance()
population_standarddeviation()
sample_standarddeviation()
population_skewness()
sample_skewness()
population_kurtosis()
sample_kurtosis()
示例
incr_stats
统计包在 f64
数据上运行,易于使用。
增量/流式处理
无需存储整个数据流即可计算其描述性统计。
use incr_stats::incr::Stats;
let mut s = Stats::new();
// Update the stats as data becomes available, without storing it.
s.update(1.2)?;
s.update(0.2)?;
// ...
// Calculate the descriptive statistics as needed.
println!("The skewness is {:.4}", s.sample_skewness()?);
println!("The kurtosis is {:.4}", s.sample_kurtosis()?);
一些计算是通过每个 update()
来进行的,因此它们比简单地存储值要慢。然而,生成所需的统计信息只需要进行很少的计算,所以例如 sample_kurtosis()
,与传统在整个数组上操作的传统算法相比几乎是瞬时的。
还提供了一个 array_update()
函数,以便可以对一组值执行增量更新。
incr_stats
包含了其他两个相同计算的版本,用于比较。通常,增量版本是最快的,但它们都产生相同的结果。
此示例在 examples/incr_example.ps
中,可以使用以下命令运行:$ cargo run --example incr_example
。
记忆化
此版本需要存储的数据,但进行了优化并提供相同的精度。描述性统计之间相互依赖,例如偏度依赖于方差,方差依赖于均值。此版本确保在请求任何统计信息时仅执行所需的最小计算。此外,后续请求不会重复已完成的计算。
use incr_stats::vec::Stats;
let a = vec![1.2, -1.0, 2.3, 10.0, -3.0, 3.2, 0.33, 0.23, 0.23, 1.0];
let mut s = Stats::new(&a)?;
println!("The skewness is {:.4}", d.sample_skewness()?);
println!("The kurtosis is {:.4}", d.sample_kurtosis()?);
此示例在 examples/vec_example.ps
中,可以使用以下命令运行:$ cargo run --example vec_example
。
批处理
最后,第三个版本使用传统的、教科书中的计算方法。这些计算没有其他开销。它们主要包含在比较和测试中,但在只需要一个或两个统计信息时,对于存储的数据可能是最快的。
use incr_stats::batch;
let a = vec![1.2, -1.0, 2.3, 10.0, -3.0, 3.2, 0.33, 0.23, 0.23, 1.0];
println!("The skewness is {:.4}", batch::sample_skewness(&a)?);
println!("The kurtosis is {:.4}", batch::sample_kurtosis(&a)?);
此示例在 examples/batch_example.ps
中,可以使用以下命令运行:$ cargo run --example batch_example
。
如何选择?
首先选择 incr
统计,除非你的用途符合下面描述的优化。
incr
,增量统计
incr
统计不存储数据,而是只存储在请求完整统计信息时所需的一些中间值。它一次或几次更新一个或几个值。
增量统计在计算上最终与批量计算等价,只是在单个数据更新上分摊了计算。这是计算大型数据集描述性统计的最快方式。
如果以下情况也适用:
- 内存有限
- 数据无限,例如在流式或连续数据应用程序中
- 你不想存储和管理所有数据
- 当前的统计信息需要更新
- 数据一次只出现一个或几个点,但需要整体统计信息
- 请求统计信息时必须快速计算,即没有时间在整个数据集上计算。
vec
,优化向量统计
消除数据存储并使最终统计计算快速进行的权衡是,对每个update()
执行中间计算比简单地存储数据要慢。
vec
统计结构在执行存储数据上的计算时缓存中间结果。这种优化确保了当其他统计需要时,依赖计算(如均值和方差)被重复使用。重复调用变为查找而不是重新计算。
当以下情况发生时,使用vec
统计:
- 应用程序不能为增量
update()
提供足够的时间,但只有时间存储数据 - 你只对一两个统计感兴趣,因此
update()
计算的开销浪费在不会请求的统计上 - 如果最终统计的计算速度慢,因为是在整个数据集上操作,这也是可以接受的。
batch
,未优化的教科书方程
batch
函数主要包括用于准确度比较和测试,但它们可能比具有检查先前计算值的开销的vec
版本略快。如果只需要一个统计量,则不会在统计量之间进行重复使用,这会使vec
版本更快。
当以下情况发生时,使用batch
统计:
- 需要绝对最快的单个统计量(例如,
sample_kurtosis
)的计算
总体和样本统计量
incr_stats
提供总体和样本统计量。
代码记录了R和GNU Octave统计包中的相应函数,明确了它们的命名和参数差异。
准确性:R和Octave验证
incr_stats
单元测试包括与R和GNU Octave统计包结果匹配至13位小数的示例。
请参阅相应R和GNU Octave函数的代码。
速度
数据处理分为两部分:获取和处理。对于基于数组的系统,获取意味着只是存储数据。然后,当请求统计时,处理所有数据。相比之下,增量统计在获取时进行一些处理,使得最终统计计算非常快。
因此,关于速度,实际上是一个问题,即你想在哪里花费计算时间
- 随着数据的收集逐渐一点一点地?那么更新慢,但最终统计快。
- 在请求统计时一次性完成?那么更新快,只是存储数据,但最终统计慢。
增量统计在每次数据点更新时都会进行统计矩的计算,这带来了额外的开销。这种开销使得在任何时候都可以快速计算出完整的描述性统计,而不必存储整个数据集。对于单个数据点来说,这看起来是相当大的计算量。而且,与存储数组的情况相比,对所有点的计算量似乎要大得多。但实际上,对于存储数组版本,也需要进行几乎相同的计算,因此在处理时间方面,两者几乎是相同的。存储数组版本会多次遍历数据;增量版本则会展开这些循环,将每个循环计算分解成在值获取时执行的单独的 update()
操作。
这就是为什么人们更喜欢存储数组统计的唯一原因:
- 数据必须在采集时尽可能快地处理;只有存储它的功夫,或者
- 只需要一个或两个统计量,例如,当不需要计算偏度时,就没有必要在每次
update()
时计算第四阶矩的中间值。
基准测试
Criterion 基准测试包括增量、记忆化和批量统计计算的比较。实际的实验时间会因机器和操作系统而异,所以我们这里考虑这些结果具有代表性,并做出相对比较。
对于具有 10 和 1,000,000 个随机值的数据集,以下是计算总时间的比较 [越低越好]
Kurtosis
表示仅计算了 sample_kurtosis
,显示了如果只需要一个统计量时的时间。《code>All stats 表示计算了所有统计量(count
、min/max
、sum
、mean
、{samp/pop}_variance
、{samp/pop}_standard_deviation
、{samp/pop}_skewness
、{samp/pop}_kurtosis
)。
方法 | 10, Kurtosis | 10, All stats | 1M, Kurtosis | 1M, All stats |
---|---|---|---|---|
incr | 81.736 ns | 101.35 ns | 7.004 ms | 7.105 ms |
batch | 40.124 ns | 228.20 ns | 3.830 ms | 29.128 ms |
vec | 68.094 ns | 106.80 ns | 4.124 ms | 8.411 ms |
分析
当需要多个统计量时,无论数据集大小如何,增量(incr
)统计是最快的。
如上所述,增量版本和优化的存储数组(vec
)版本的计算量相似,但增量版本比优化存储数组版本快 5.1%(10 个数据点)到 15.5%(1M 个数据点)。
正如预期的那样,对于只计算一个统计量,未经优化的存储数组版本(batch
)代码最快,不受数据集大小的影响。这是因为它只为一个统计量执行了最少的计算。
请注意,对于大型(1M)数据集的增量版本,计算所有统计数据(7.105 毫秒
)所需的时间与仅计算样本峰度的计算时间(7.004 毫秒
)非常接近(1.4%),这是因为在这个基准测试运行中,增量版本的多数工作量都花在单个点更新计算上,这包括所有统计数据所需的中间值。因此,计算最终 all stats
与任何单个统计量所需的时间趋于一致。
incr
更新和最终时间
增量统计包在每次 update()
上都会进行一些计算。这些计算有多昂贵?incr
的最终计算时间有多快?
方法 | 10 个值 | 1M 个值 |
---|---|---|
更新 | 11.722 纳秒 | 11.843 纳秒 |
最终 | 2.591 纳秒 | 2.584 纳秒 |
此表显示了每次单个 update()
和 sample_kurtosis()
最终计算的 10 个和 1,000,000 个值的时间。
正如预期的那样,由于数据集的大小,时间上没有显著的差异。
我们看到每个更新都需要 <12 纳秒来计算中间时刻。这项工作分散在所有更新中,使得最终计算 sample_kurtosis()
只需要 2.6 纳秒,大约是更新时间的五分之一。
错误处理
incr_stats
crate 以简单和一致的方式处理错误。只有三种类型的错误
NotEnoughData
:这个错误仅仅意味着需要更多的数据来允许计算统计量。例如,样本偏度计算包括除以n-1
,因此至少需要 2 个数据点来避免除以 0.0。Undefined
:即使有足够的有效数据,一些统计量也会产生未定义的结果。例如,如果所有数据都是相同的值,则方差为 0.0。偏度和峰度是衡量围绕中心趋势的分布的度量,它们不存在。这一事实通过除以方差(即除以 0.0)反映在计算中。因此,这些是未定义的。InvalidData
:浮点数据被检查是否存在来自IEEE 754
标准的 NaNs 和 Infs。
不需要进行这些区分的调用者只需对任何错误做出反应。
许可
根据您的选择,在 Apache License, Version 2.0 或 MIT 许可证 下许可。除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交给此 crate 的任何贡献都应按上述方式双许可,而无需任何额外条款或条件。
依赖关系
~0.7–1.5MB
~30K SLoC