为大量数据计算分位数的增量方法

Posted

技术标签:

【中文标题】为大量数据计算分位数的增量方法【英文标题】:incremental way of counting quantiles for large set of data 【发布时间】:2011-02-19 16:58:14 【问题描述】:

我需要计算大量数据的分位数。

假设我们只能通过某些部分(即大矩阵的一行)获取数据。要计算 Q3 分位数,需要获取数据的所有部分并将其存储在某处,然后对其进行排序并计算分位数:

List<double> allData = new List<double>();
// This is only an example; the portions of data are not really rows of some matrix
foreach(var row in matrix) 

    allData.AddRange(row);


allData.Sort();
double p = 0.75 * allData.Count;
int idQ3 = (int)Math.Ceiling(p) - 1;
double Q3 = allData[idQ3];

我想找到一种无需将数据存储在中间变量中即可获得分位数的方法。最好的解决办法是统计第一行的中间结果的一些参数,然后逐步调整下一行。

注意:

这些数据集非常大(每行大约 5000 个元素) 可以估算第三季度,它不必是精确值。 我将数据部分称为“行”,但它们可以有不同的长度!通常它变化不大(+/-几百个样本),但它会变化!

这个问题类似于“On-line” (iterator) algorithms for estimating statistical median, mode, skewness, kurtosis,但是我需要计算分位数。

此外,该主题的文章很少,即:

An Efficient Algorithm for the Approximate Median Selection Problem Incremental quantile estimation for massive tracking

在尝试实施这些方法之前,我想知道是否还有其他更快的方法来计算 0.25/0.75 分位数?

【问题讨论】:

您想搜索用于分位数计算的在线/流式传输算法。很多文献都是由数据库研究推动的。 Check this thread 【参考方案1】:

我赞同使用存储桶的想法。不要将自己限制在 100 个桶内——不妨使用 100 万个。棘手的部分是选择你的桶范围,这样所有东西都不会在一个桶中结束。估计存储桶范围的最佳方法可能是对数据进行合理的随机抽样,使用简单的排序算法计算 10% 和 90% 的分位数,然后生成相等大小的存储桶来填充该范围。它并不完美,但如果您的数据不是来自超奇怪的分布,它应该可以工作。

如果您不能进行随机抽样,您的麻烦就更大了。您可以根据预期的数据分布选择初始分桶猜测,然后在处理数据时,如果任何存储桶(通常是第一个或最后一个存储桶)过满,请使用新的存储桶范围重新开始。

【讨论】:

【参考方案2】:

对此有一种更新且更简单的算法,可以很好地估计极值分位数。

基本思想是在极端情况下使用较小的 bin,既限制了数据结构的大小,又保证了小或大 q 的更高准确性。该算法有多种语言和许多软件包。 MergingDigest 版本不需要动态分配......一旦 MergingDigest 被实例化,就不需要进一步的堆分配。

见https://github.com/tdunning/t-digest

【讨论】:

【参考方案3】:
    仅检索您真正需要的数据 - 即,任何值被用作/正在用作排序的键,而不是与之相关的所有其他值。 您或许可以使用 Tony Hoare 的 Select 算法来比对所有数据进行排序更快地找到分位数。

【讨论】:

【参考方案4】:

如果您的数据具有高斯分布,您可以根据标准差估计分位数。我假设你的数据不是高斯分布的,或者你只是在使用 SD。

如果您可以两次传递您的数据,我会执行以下操作:

第一次通过,计算最大值、最小值、SD 和平均值。 第二遍,将范围 [min,max] 分成若干个桶(例如 100);对 (mean - 2*SD,mean + 2*SD) 执行相同的操作(对于异常值有额外的桶)。然后再次遍历数据,将数字扔进这些桶中。 计数存储桶,直到达到数据的 25% 和 75%。如果你想获得额外的花哨,你可以在桶值之间进行插值。 (即,如果您需要 10% 的存储桶来达到第 25 个分位数,则假设该值是从下限到上限的 10%。)

这应该会为您提供一个非常好的线性时间算法,该算法适用于大多数非完全反常的数据集。

【讨论】:

【参考方案5】:

受this answer 的启发,我创建了一种可以很好地估计分位数的方法。对于我的目的来说,它是足够接近的近似值。

想法如下:0.75 分位数实际上是高于全局中位数的所有值的中位数。并且分别地,0.25 分位数是低于全局中位数的所有值的中位数。

因此,如果我们可以逼近中位数,我们就可以以类似的方式逼近分位数。

double median = 0;
double q1 = 0;
double q3 = 0;
double eta = 0.005;

foreach( var value in listOfValues) // or stream, or any other large set of data...

    median += eta * Math.Sign(p.Int - median);

// Second pass. We know the median, so we can count the quantiles.
foreach(var value in listOfValues)
 
    if(p.Int < median)
        q1 += eta*Math.Sign(p.Int - q1);
    else
        q3 += eta*Math.Sign(p.Int - q3);

备注:

如果您的数据分布很奇怪,您将需要更大的eta 以适应奇怪的数据。但准确度会更差。 如果分布很奇怪,但您知道集合的总大小(即 N),您可以通过以下方式调整 eta 参数:在开始时将 eta 设置为几乎等于某个较大的值(即 0.2)。随着循环通过,降低eta 的值,因此当您几乎到达集合的末尾时,eta 将几乎等于 0(例如,在循环中计算它:eta = 0.2 - 0.2*(i/N);

【讨论】:

【参考方案6】:

q-digest 是一种近似在线算法,可让您计算分位数:http://www.cs.virginia.edu/~son/cs851/papers/ucsb.sensys04.pdf

这是一个实现:

https://github.com/airlift/airlift/blob/master/stats/src/main/java/io/airlift/stats/QuantileDigest.java

【讨论】:

以上是关于为大量数据计算分位数的增量方法的主要内容,如果未能解决你的问题,请参考以下文章

pandas——数据计算

聊聊python的分位数

四分位数计算以及使用pandas计算

中位数的中位数 - 这是可能的还是有不同的方法

java如何计算中位数

计算 TB 数据集中分位数的高效算法