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

时间:2010-05-14 20:14:39

标签: algorithm statistics numerical-methods quantile

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

假设我们只能通过某些部分(即大矩阵的一行)获取数据。要计算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个元素)
  • 可以估算Q3,它不一定是精确值。
  • 我将数据的各个部分称为“行”,但它们可以有不同的长度!通常它的变化不是很大(+/-几百个样本),但它会有所不同!

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

此主题中的文章很少,即:

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

6 个答案:

答案 0 :(得分:1)

我的第二个想法是使用桶。不要将自己限制在100个桶中 - 不妨使用100个桶。棘手的部分是选择您的铲斗范围,以便一切都不会在一个桶中结束。估计铲斗范围的最佳方法可能是采用合理的随机数据样本,使用简单排序算法计算10%和90%分位数,然后生成相等大小的铲斗以填充该范围。它并不完美,但如果您的数据不是来自超级奇怪的发行版,那么它应该可以正常工作。

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

答案 1 :(得分:1)

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

基本思想是在极端情况下使用较小的二进制位,这两种方式都限制了数据结构的大小,并保证了小或大q的更高精度。该算法有多种语言和许多包。 MergingDigest版本不需要动态分配...一旦实例化MergingDigest,就不需要进一步分配堆。

请参阅https://github.com/tdunning/t-digest

答案 2 :(得分:0)

  1. 只检索您真正需要的数据 - 即,用作排序键的任何值,而不是与其相关的所有其他值。
  2. 您可以使用Tony Hoare的Select算法比排序所有数据更快地找到分位数。

答案 3 :(得分:0)

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

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

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

这应该会为你提供一个非常好的线性时间算法,对大多数不完全反常的数据集都可以。

答案 4 :(得分:0)

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(例如,在循环中计算它如下:{{1 }}

答案 5 :(得分:0)