如何在不存储列表的情况下计算或近似列表的中位数

时间:2009-03-12 10:29:00

标签: algorithm optimization median

我正在尝试计算一组值的中位数,但我不想存储所有值,因为这可能会破坏内存需求。有没有办法计算或逼近中位数而不存储和排序所有单个值?

理想情况下,我想编写我的代码,如下所示

var medianCalculator = new MedianCalculator();
foreach (var value in SourceData)
{
  medianCalculator.Add(value);
}
Console.WriteLine("The median is: {0}", medianCalculator.Median);

我需要的只是实际的MedianCalculator代码!

更新:有些人问我正在尝试计算中位数的值是否已知属性。答案是肯定的。一个值从约-25到-0.5以0.5为增量。另一个也是从-120到-60的0.5增量。我想这意味着我可以为每个值使用某种形式的直方图。

由于

尼克

11 个答案:

答案 0 :(得分:41)

如果值是离散的并且不同值的数量不是太高,您可以只累积每个值在直方图中出现的次数,然后从直方图计数中找出中位数(只需从直方图的顶部和底部,直到到达中间)。或者,如果它们是连续值,您可以将它们分配到箱中 - 这不会告诉您确切的中位数,但它会给您一个范围,如果您需要更精确地知道,您可以再次遍历列表,仅检查中央垃圾箱中的元素。

答案 1 :(得分:38)

有“补救者”统计数据。它首先设置k个数组,每个长度为b。数据值被输入到第一个数组中,当它满时,计算中值并将其存储在下一个数组的第一个pos中,之后重新使用第一个数组。当第二个数组满时,其值的中位数存储在第三个数组的第一个pos等等。你明白了这一点:)

它简单而且非常健壮。引用就在这里......

http://web.ipac.caltech.edu/staff/fmasci/home/astro_refs/Remedian.pdf

希望这有帮助

迈克尔

答案 2 :(得分:17)

我使用这些增量/递归均值和中值估计值,它们都使用常量存储:

mean += eta * (sample - mean)
median += eta * sgn(sample - median)

其中eta是一个小的学习速率参数(例如0.001),而sgn()是signum函数,它返回{-1,0,1}中的一个。

这种类型的增量平均估计似乎可以在所有地方使用,例如在无监督的神经网络学习规则中,尽管有其优点(对异常值的鲁棒性),但中位数似乎不太常见。似乎在许多应用中,中位数版本可以用作平均估计量的替代。

我很想看到类似形式的增量模式估算器......

(注意:我在这里也发布了类似的主题:"On-line" (iterator) algorithms for estimating statistical median, mode, skewness, kurtosis?

答案 3 :(得分:13)

您可以尝试这种疯狂的方法。这是流式算法中的经典问题。规则是

  1. 你的内存有限,比如说O(log n),其中n是你想要的项目数
  2. 您可以查看每个项目,然后做出决定然后如何处理它,如果您存储它,它会花费内存,如果你扔掉它就会永远消失。
  3. 找到中位数的想法很简单。从列表中随机抽取O(1 / a^2 * log(1 / p)) * log(n)个元素,您可以通过水库采样来实现(参见previous question)。现在只需使用经典方法从采样元素中返回中值。

    保证返回的项目的索引将为(1 +/- a) / 2,概率至少为1-p。因此存在失败概率p,您可以通过抽取更多元素来选择它。并且它不会返回中位数或保证返回的项目的值接近中位数,只是当您对列表进行排序时,返回的项目将接近列表的一半。

    此算法使用O(log n)个额外空格并以线性时间运行。

答案 4 :(得分:7)

一般来说这很难处理,特别是处理已经排序的退化系列,或者在列表的“开始”处有一堆值,但列表的末尾具有不同范围的值。 / p>

制作直方图的基本思想是最有希望的。这使您可以累积分布信息并从中回答查询(如中位数)。中位数将是近似值,因为您显然不存储所有值。存储空间是固定的,因此它可以按照您拥有的任何长度顺序工作。

但是你不能仅仅根据前100个值构建直方图并连续使用该直方图......更改数据可能会使直方图无效。因此,您需要一个动态直方图,可以动态更改其范围和容器。

制作一个有N个箱子的结构。您将存储每个槽转换的X值(总共N + 1个值)以及bin的总体。

输入您的数据。记录前N + 1个值。如果流在此之前结束,那么很好,您已经加载了所有值,您可以找到确切的中位数并返回它。否则使用这些值来定义第一个直方图。只需对值进行排序并将其用作bin定义,每个bin的总体数为1.可以使用dupes(0个宽度的bin)。

现在流入新值。对于每一个,二进制搜索以找到它所属的bin。 在常见情况下,您只需增加该容器的数量并继续。 如果您的样本超出直方图的边缘(最高或最低),只需扩展结束仓的范围以包含它。 当您的流完成后,您可以通过找到其两侧具有相等总体的bin来找到中值样本值,并线性插值剩余的bin宽度。

但这还不够......你仍然需要在直播中对数据进行ADAPT。当一个bin过满时,你会丢失有关该bin子分布的信息。 您可以通过基于某种启发式进行调整来解决这个问题...最容易和最强大的一个是如果一个bin达到某个特定的阈值总体(类似于10 * v / N,其中v =到目前为止在流中看到的值的数量,并且N是垃圾箱的数量,你拆分垃圾桶。在bin的中点添加一个新值,给每个原始bin的人口一半。但是现在你的垃圾箱太多了,所以你需要删除垃圾箱。一个好的启发式方法是找到人口和宽度最小的bin。删除它并将其与其左或右邻居合并(无论哪个邻居本身具有最小的宽度和总体积。)。完成! 请注意,合并或拆分垃圾箱会丢失信息,但这是不可避免的..您只有固定存储空间。

这个算法非常好用,因为它可以处理所有类型的输入流并提供良好的结果。如果您可以选择样本订单,那么最好随机抽样,因为这样可以最大限度地减少拆分和合并。

该算法还允许您查询任何百分位数,而不仅仅是中位数,因为您有完整的分布估算值。

我在很多地方使用这个方法在我自己的代码中,主要用于调试日志..你正在录制的某些统计信息具有未知的分布。使用此算法,您无需提前猜测。

缺点是不相等的bin宽度意味着你必须对每个样本进行二进制搜索,所以你的网络算法是O(NlogN)。

答案 5 :(得分:3)

我认为没有内存中的列表是不可能的。您显然可以使用

进行近似
  • 如果您知道数据是对称分布的平均值
  • 或计算一小部分数据(适合内存)的正确中位数 - 如果您知道您的数据在整个样本中具有相同的分布(例如,第一项与最后一项具有相同的分布)< / LI>

答案 6 :(得分:3)

大卫的建议似乎是近似中位数的最明智的方法。

针对同一问题运行平均值更容易计算:

  

M n = M n-1 +((V n - M n-1 )/ n)的

     

其中M n 是n个值的平均值,M n-1 是先前的平均值,V n 是新值

换句话说,新均值是现有均值加上新值和均值之间的差值除以值的数量。

在代码中,这看起来像是:

new_mean = prev_mean + ((value - prev_mean) / count)

虽然很明显你可能想要考虑特定于语言的东西,比如浮点舍入错误等。

答案 7 :(得分:2)

通过线性搜索查找包含N个项目的列表的最小值和最大值,并将它们命名为HighValue和LowValue 设MedianIndex =(N + 1)/ 2

第一顺序二进​​制搜索:

重复以下4个步骤,直到LowValue&lt;高价值。

  1. 大约获得MedianValue =(HighValue + LowValue)/ 2

  2. 获取NumberOfItemsWhichAreLessThanorEqualToMedianValue = K

  3. 是K = MedianIndex,然后返回MedianValue

  4. 是K&gt; MedianIndex?然后HighValue = MedianValue Else LowValue = MedianValue

  5. 不消耗内存会更快

    二阶二进制搜索:

    LowIndex = 1 HighIndex = N

    重复以下5个步骤,直到(LowIndex&lt; HighIndex)

    1. 获取近似DistrbutionPerUnit =(HighValue-LowValue)/(HighIndex-LowIndex)

    2. 获取近似MedianValue = LowValue +(MedianIndex-LowIndex)* DistributionPerUnit

    3. 获取NumberOfItemsWhichAreLessThanorEqualToMedianValue = K

    4. 是(K = MedianIndex)?返回MedianValue

    5. 是(K&gt; MedianIndex)?然后HighIndex = K和HighValue = MedianValue Else LowIndex = K和LowValue = MedianValue

    6. 如果不消耗内存,它将比第一顺序更快

      我们还可以考虑将HighValue,LowValue和MedianValue与HighIndex,LowIndex和MedianIndex拟合到抛物线,并且可以获得ThirdOrder二进制搜索,它将比第二顺序更快而不消耗内存等等......

答案 8 :(得分:0)

通常如果输入在一定范围内,比如1到1百万,那么很容易创建一个计数数组:在这里读取“quantile”和“ibucket”的代码:http://code.google.com/p/ea-utils/source/browse/trunk/clipper/sam-stats.cpp

这个解决方案可以通过使用函数将输入强制转换为某个范围内的整数来推广为近似值,然后在出路时反转:IE:foo.push((int)input / 1000000)和quantile(foo) )* 1000000。

如果您的输入是任意双精度数,那么您必须自动缩放直方图,因为值超出范围(见上文)。

或者您可以使用本文中描述的中位数 - 三元组方法:http://web.cs.wpi.edu/~hofri/medsel.pdf

答案 9 :(得分:0)

我接受了迭代分位数计算的想法。对于起点和eta具有良好的价值很重要,这些可能来自均值和sigma。因此,我对此进行了编程:

QuantitIterative函数(Var x:双精度数组; n:整数; p,均值,sigma:双精度):双精度;
变量eta,分位数,q1,dq:Double;
    i:整数;
开始
  分位数:=平均值+ 1.25 * sigma *(p-0.5);
  q1:=分位数;
  eta:= 0.2 * sigma / xy(1 + n,0.75); //不能太大!设置精度
  对于i:= 1到n做分位数:=分位数+ eta *(signum_smooth(x [i]-分位数,eta)+ 2 * p-1);
  dq:= abs(q1-quantile);
  如果dq> eta
     然后开始
          如果dq <3 * eta,则eta:= eta / 4;
          对于i:= 1到n做分位数:=分位数+ eta *(signum_smooth(x [i]-分位数,eta)+ 2 * p-1);
     结束;
  QuantileIterative:=分位数
结束;

因为两个元素的中位数是平均值,所以我使用了平滑的信号函数,而xy()是x ^ y。有更好的想法吗?当然,如果我们具有一些先验知识,则可以使用数组的最小值和最大值,偏斜等来添加代码。对于大数据,您可能不会使用数组,但是测试起来会更容易。

答案 10 :(得分:0)

在同构随机排序和足够大的列表上,这个伪代码可以工作:

# find min on the fly
if minDataPoint > dataPoint:
    minDataPoint = dataPoint  
# find max on the fly
if maxDataPoint < dataPoint:
    maxDataPoint = dataPoint

# estimate midium
mid = (maxDataPoint + minDataPoint) / 2
# if new dataPoint is closer to the mid? stor it
if abs(midDataPoint - mid) > abs(dataPoint - mid):
    dataPoint = dataPoint    

灵感来自@lakshmanaraj