如何在一组大数字中找到平均值?

我有一大堆数字,可能在几千兆字节范围内。 第一个问题是我无法将所有这些存储在内存中。 其次,任何添加这些的尝试都会导致溢出。 我想要使​​用更多的滚动平均值,但它需要准确。 有任何想法吗?

这些都是浮点数。

这不是从数据库中读取的,而是从多个源收集的CSV文件。 它必须准确,因为它存储为秒的一部分(例如; 0.293482888929),滚动平均值可以是.2和.3之间的差值。

它是一组#,表示用户响应某些表单操作的时间。 例如,在显示消息框时,按“确定”或“取消”需要多长时间。 数据发送给我存储为秒。部分秒; 例如1.2347秒。 将它转换为毫秒,我溢出int,long等…相当快。 即使我不转换它,我仍然会很快溢出它。 我想下面的一个答案是正确的,也许我不必100%准确,只是在一个特定的StdDev内部的某个范围内看,我会足够接近。

您可以从您的集合(“ 人口 ”)中随机抽样以获得平均值(“ 平均值 ”)。 准确度将取决于样品的变化程度(由“ 标准偏差 ”或方差确定)。

优点是你有数十亿的观察结果,你只需要对其中的一小部分进行采样,以获得相当的准确度或你选择的“ 置信区间 ”。 如果条件合适,这将减少您将要做的工作量。

这是一个包含随机序列生成器的C# 数值库 。 只需创建一个随机的数字序列,引用元素数组中的索引(从1到x ,数组中的元素数)。 取消引用以获取值,然后计算您的平均值和标准差。

如果您想测试数据的分布,请考虑使用Chi-Squared Fit测试或KS测试,您可以在许多电子表格和统计软件包(例如R )中找到它。 这将有助于确认这种方法是否可用。

整数还是花车?

如果它们是整数,则需要通过读取数字并记录您看到的每个值的数量来累积频率分布。 这很容易平均化。

对于浮点,这有点问题。 鉴于浮动的总体范围和实际分布,您必须计算出一个bin大小,以保留您想要的精度而不保留所有数字。


编辑

首先,您需要对数据进行采样以获得均值和标准差。 几千点应该足够好了。

然后,您需要确定一个可敬的范围。 人们在平均值周围选择±6σ(标准偏差)之类的东西。 您可以将此范围划分为尽可能多的铲斗。

实际上,桶的数量决定了平均值中的有效位数。 因此,选择10,000或100,000个桶来获得4或5位精度。 由于这是一种测量,因此您的测量只有两位或三位数的几率很高。


编辑

你会发现你的初始样本的平均值非常接近任何其他样本的平均值。 任何样本均值接近人口均值。 你会注意到你的大多数(但不是全部)手段彼此之间有1个标准差。

您应该发现测量误差和误差大于标准偏差。

这意味着样本均值与总体均值一样有用。

滚动平均值不会像其他任何东西一样准确(折扣舍入错误,我的意思)? 由于所有分裂,它可能有点慢。

您可以对批量数字进行分组并递归地对它们求平均值。 像平均100个数字100次,然后平均结果。 这将是更少的颠簸和大多数添加。

实际上,如果你一次添加256或512,你可以将结果按8位或9位移位(我相信你可以通过简单地改变浮点尾数来做到这一点) – 这会使你的程序非常快,它可以只用几行代码递归写入(不计算尾数移位的不安全操作)。

也许除以256就已经使用了这种优化? 我可能要加速测试除以255对256,看看是否有一些大的改进。 我猜不是。

你的意思是32位和64位数字。 但为什么不使用合适的Rational Big Num库呢? 如果您有这么多数据并且想要一个精确的均值,那么只需编码即可。

class RationalBignum { public Bignum Numerator { get; set; } public Bignum Denominator { get; set; } } class BigMeanr { public static int Main(string[] argv) { var sum = new RationalBignum(0); var n = new Bignum(0); using (var s = new FileStream(argv[0])) { using (var r = new BinaryReader(s)) { try { while (true) { var flt = r.ReadSingle(); rat = new RationalBignum(flt); sum += rat; n++; } } catch (EndOfStreamException) { break; } } } Console.WriteLine("The mean is: {0}", sum / n); } } 

请记住,除了编译器为您提供的数字类型之外,还有更多的数字类型。

您可以将数据分成多组,例如1000个数字,平均值,然后平均值。

这是一个典型的分而治之的问题。

问题在于,一组大数字的平均值与该组的前半部分的平均值相同,平均为该组的后半部分的平均值。

换一种说法:

 AVG(A[1..N]) == AVG( AVG(A[1..N/2]), AVG(A[N/2..N]) ) 

这是一个简单的C#递归解决方案。 它通过了我的测试,应该是完全正确的。

 public struct SubAverage { public float Average; public int Count; }; static SubAverage AverageMegaList(List aList) { if (aList.Count <= 500) // Brute-force average 500 numbers or less. { SubAverage avg; avg.Average = 0; avg.Count = aList.Count; foreach(float f in aList) { avg.Average += f; } avg.Average /= avg.Count; return avg; } // For more than 500 numbers, break the list into two sub-lists. SubAverage subAvg_A = AverageMegaList(aList.GetRange(0, aList.Count/2)); SubAverage subAvg_B = AverageMegaList(aList.GetRange(aList.Count/2, aList.Count-aList.Count/2)); SubAverage finalAnswer; finalAnswer.Average = subAvg_A.Average * subAvg_A.Count/aList.Count + subAvg_B.Average * subAvg_B.Count/aList.Count; finalAnswer.Count = aList.Count; Console.WriteLine("The average of {0} numbers is {1}", finalAnswer.Count, finalAnswer.Average); return finalAnswer; } 

诀窍是你担心溢出。 在这种情况下,这一切都归结为执行顺序。 基本公式是这样的:

鉴于:

A = current avg
C = count of items

V = next value in the sequence 

下一个平均值(A 1 )是:

       (C * A)+ V.
 A 1 = -----------
         C + 1

危险的是你担心在整个序列的过程中, A应该保持相对可管理的C将变得非常大。
最终C * A将溢出整数或双精度类型。

我们可以尝试的一件事就是像这样重写它,以减少溢出的可能性:

 A 1 = C /(C + 1)* A /(C + 1)+ V /(C + 1)

通过这种方式,我们永远不会乘以C * A而只处理较小的数字。 但现在关注的是分工操作的结果。 如果C非常大,则在约束到正常浮点表示时, C/C+1 (例如)可能没有意义。 我能建议的最好是在这里使用C的最大类型。

这是在伪代码中执行此操作的一种方法:

平均=第一
计数= 1
更多:
  计数+ = 1
   DIFF =下一个均
  平均+ = DIFF /计数
回报率

对于迟到的评论感到抱歉,但不是由Joel Coehoorn提供的上述公式错误地重写了吗?

我的意思是,基本公式是对的:

鉴于:

A =当前平均值C =项目数V =序列中的下一个值

下一个平均值(A1)是:

A1 =((C * A)+ V)/(C + 1)

而不是:

A1 = C /(C + 1)* A /(C + 1)+ V /(C + 1)

我们不应该:

A1 = C /(C + 1)* A + V /(C + 1)

这可以解释kastermester的post:

“我的数学在这里徘徊 – 你有C,你说”走向无限“或者至少是一个非常大的数字,然后:C /(C + 1)走向1. A /(C + 1)走向0. V /(C + 1)趋向于0.总而言之:A1 ​​= 1 * 0 + 0所以很快就把A1推向0 – 似乎有点偏.- kastermester“

因为我们会有A1 = 1 * A + 0,即A1走向A,这是正确的。

我一直用这种方法计算平均值很长时间,上述精度问题对我来说从来都不是问题。

根据数字的范围,最好有一个数组,其中下标是你的数字,值是这个数字的数量,你可以从这里做你的计算

如果数字是int,则累计总数为long。 如果数字很长……你用的是哪种语言? 在Java中,您可以在BigInteger中累积总数,BigInteger是一个整数,它将增长到需要的大小。 您可以随时编写自己的类来重现此function。 它的要点只是制作一个整数数组来保存每个“大数字”。 添加两个数字时,从低位值开始循环。 如果相加的结果设置了高位,则清除该位并将该位移到下一列。

另一个选择是一次找到1000个数字的平均值。 保持这些中间结果,然后当你完成它们的所有平均值。

为什么浮点数的总和溢出? 为了实现这一点,您需要具有接近最大浮点值的值,这听起来很奇怪。

如果你正在处理整数,我建议使用BigInteger,或者将集合分成多个子集,递归地平均子集,然后对平均值求平均值。

如果你正在处理花车,它会有点奇怪。 滚动平均值可能变得非常不准确。 我建议使用滚动平均值,只有在遇到溢出exception或集合结束时才会更新。 因此,有效地将集合划分为非溢出集。

我的两个想法:

  • 如果数字是整数,则使用像IntX这样的任意精度库 – 但这可能太慢了
  • 如果数字是浮点数并且您知道总金额,则可以将每个条目除以该数字并将结果相加。 如果使用double,则精度应该足够。

为什么不在计算平均值之前缩放数字(向下)?