【问题标题】:Standard deviation using LINQ gives different answer from iterative calculations使用 LINQ 的标准差给出了与迭代计算不同的答案
【发布时间】:2017-05-25 07:21:16
【问题描述】:

如果我使用从this SO question 修改的代码对样本进行标准偏差计算:

public double CalculateStandardDeviation(List<double> values, bool sample = false)
    {
        double mean = 0.0;
        double sum = 0.0;
        double stdDev = 0.0;
        int count = 0;
        foreach (double val in values)
        {
            count++;
            double delta = val - mean;
            mean += delta / count;
            sum += delta * (val - mean);
        }
        if (1 < count)
            stdDev = Math.Sqrt(sum / (count - (sample ? 1 : 0)));
        return stdDev;
    }

使用这个单元测试:

    [Test]
    public void Sample_Standard_Deviation_Returns_Expected_Value()
    {
        //original cite: http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/
        double expected = 2.23606797749979;
        double tolerance = 1.0 / System.Math.Pow(10, 13);
        var cm = new CommonMath();//a library of math functions we use a lot
        List<double> values = new List<double> { 4.0, 2.0, 5.0, 8.0, 6.0 };
        double actual = cm.CalculateStandardDeviation(values, true);
        Assert.That(actual, Is.EqualTo(expected).Within(tolerance));
    }

测试通过,结果值在指定的容差范围内。

但是,如果我使用这个 Linq-ified 代码,它会失败,返回一个 2.5 的值(好像它是一个总体标准差):

        double meanOfValues = values.Average();
        double sumOfValues = values.Sum();
        int countOfValues = values.Count;
        double standardDeviationOfValues = 
            Math.Sqrt(sumOfValues / (countOfValues - (sample ? 1 : 0)));

        return standardDeviationOfValues;

由于我从未统计过(所以请保持温和),列表中值的 Linq 化(这是一个词)似乎应该给我同样的结果,但他们没有,我不明白我做错了什么。 N 和 N-1 之间的决定动作在两者中是相同的,那为什么答案不一样呢?

【问题讨论】:

  • 这里的样品有什么用?
  • 你错过了一步:var standardDeviationOfValues = Math.Sqrt(values.Select(v=&gt;Math.Pow(v - meanOfValues,2)).Average()); 或者你不能使用sumOfValuessumOfDeltasSquared,这是你没有计算出来的。无论哪种情况,当样本为假时,您当前的公式都会计算平均值/平均值,而不是标准偏差。
  • 啊,感谢您的洞察力,这并不像我想象的那么简单。

标签: c# linq unit-testing statistics standard-deviation


【解决方案1】:

您的 LINQ 版本不计算标准偏差。标准差是基于与平均值的差的平方和,所以改为:

double meanOfValues = values.Average();
double sumOfValues = values.Select(v => (v-meanOfValues)*(v-meanOfValues)).Sum();
int countOfValues = values.Count;
double standardDeviationOfValues =
    Math.Sqrt(sumOfValues / (countOfValues - (sample ? 1 : 0)));

return standardDeviationOfValues;

要遍历一次值,可以使用Aggregate,但并不比普通函数好:

var g = values.Aggregate(new { mean = 0.0, sum = 0.0, count = 0 },
            (acc, val) => {
                var newcount = acc.count+1;
                double delta = val-acc.mean;
                var newmean = acc.mean + delta / newcount;
                return new { mean = newmean, sum = acc.sum+delta*(val-newmean), count = newcount };
         });
var stdDev = Math.Sqrt(g.sum / (g.count - (sample ? 1 : 0)));

【讨论】:

  • 干得好,我对人口和样本的所有单元测试都适用于您的修改,谢谢!
  • 请注意,LINQ 版本的效率不如多次遍历values
  • 谢谢。有什么办法可以减少遍历吗?还是 LINQ 处理数据的方式固有的?
  • 只有当你用 Aggregate 替换这一切,在这种情况下你只是在以稍微低效的方式做你的其他代码。我会把它添加到答案中。
【解决方案2】:

将样本设为假,你会得到相同的答案:2.23606797749979 如果你把 sample 设为 true,你会得到 2.5!

因此,您确实需要在两个位置放置相同的“样本”值。

【讨论】:

  • 这个想法是我可以使用相同的代码计算样本 (n) 或总体 (n-1) 标准差。三元表达式根据传入的“sample”的值进行确定。我希望从两段代码中得到相同的答案。
【解决方案3】:

让我们开始吧

values.Sum();

以及您从中获得的总和

sum += delta * (val - mean);

不一样。

下一次你可以从 TDD 开始解决这类问题,并以这种方式检查每个值。

编辑:Standard Deviation in LINQ

【讨论】:

  • 您的链接指向我的问题中链接的同一个 SO 问题,原始迭代代码源自该问题。我明白你关于总和差异的观点,迭代过程取 5 的平方根,而 LINQ 过程取 6.25 的平方根。
猜你喜欢
  • 1970-01-01
  • 2018-05-30
  • 2013-07-01
  • 2013-03-30
  • 1970-01-01
  • 2014-02-24
  • 2016-03-15
  • 1970-01-01
  • 2019-09-19
相关资源
最近更新 更多