LINQ中的标准偏差


80

LINQ是否对聚合SQL函数STDDEV() (标准偏差)建模?

如果没有,最简单/最佳实践的计算方法是什么?

例:

  SELECT test_id, AVERAGE(result) avg, STDDEV(result) std 
    FROM tests
GROUP BY test_id


@Steven,您可能想在这里重新访问已接受的答案。当前选择的方法存在一些问题,那些没有向下滚动和深入阅读的人可能看不到。
Drew Noakes 2015年

为什么有人要使用LINQ做到这一点?
Ant_222

Answers:


98

您可以让自己的扩展程序进行计算

public static class Extensions
{
    public static double StdDev(this IEnumerable<double> values)
    {
       double ret = 0;
       int count = values.Count();
       if (count  > 1)
       {
          //Compute the Average
          double avg = values.Average();

          //Perform the Sum of (value-avg)^2
          double sum = values.Sum(d => (d - avg) * (d - avg));

          //Put it all together
          ret = Math.Sqrt(sum / count);
       }
       return ret;
    }
}

如果您具有总体而不是整个总体的样本,则应使用ret = Math.Sqrt(sum / (count - 1));

克里斯·本内特(Chris Bennett)将标准偏差添加到LINQ转换为扩展。


3
我会将该测试设为“ values.Count()> 1”,因为如果正好是1,则在计算返回值时会有除以零的错误。
duffymo

3
Math.pow(d-avg,2)?我将跳过函数调用并使用(d-avg)*(d-avg)
duffymo 2010年

2
行ret = Math.Sqrt((sum)/ values.Count()-1); 在values.Count()-1周围缺少括号,应为ret = Math.Sqrt(sum /(values.Count()-1));
亚历克斯·派克

1
我一直在寻找它,花了我一段时间才弄清楚如何使用扩展名,但是这是应用上面给出的方法的方法:stdev = g.Select(o => o.number).StdDev()
安德鲁·毛

2
@Yevgeniy Rozhkov-为什么删除了- 1?根据- 1需要。
约翰·米尔斯

61

Dynami的答案有效,但需要多次遍历数据以获得结果。这是一种用于计算样本标准偏差的单次通过方法:

public static double StdDev(this IEnumerable<double> values)
{
    // ref: http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/
    double mean = 0.0;
    double sum = 0.0;
    double stdDev = 0.0;
    int n = 0;
    foreach (double val in values)
    {
        n++;
        double delta = val - mean;
        mean += delta / n;
        sum += delta * (val - mean);
    }
    if (1 < n)
        stdDev = Math.Sqrt(sum / (n - 1));

    return stdDev;
}

这是样本标准偏差,因为它除以n - 1。对于正常的标准偏差,您需要除以n

这使用了与方法相比具有更高数值精度的韦尔福德Average(x^2)-Average(x)^2方法。


1
您可能没有多次迭代整个序列,但是您的方法仍将对GetEnumerator进行两次调用(这可能会触发复杂的SQL查询)。为什么不跳过条件并在循环结束时检查n?
基迪恩·英

感谢Gideon,也消除了嵌套的层次。您对SQL是正确的,它与我正在研究的内容无关,因此我没有考虑到含义。
戴维·克拉克

3
您缺少n的定义。还应注意,将总和除以(n-1)而不是n会使其成为样本标准差
Neil

3
为了更仔细地复制SQL方法,我更改了this IEnumerable<double?> valuesval in values.Where(val => val != null)。另外,我将注意到这种方法(威尔福德方法)比上面的方法更准确,更快。
安德鲁·毛

2
我已经对您的答案进行了编辑,以明确说明您是在计算样品标准偏差,而非正常标准偏差
CodesInChaos 2013年

31

这会将David Clarke的答案转换为扩展名,该扩展名的形式与其他汇总LINQ函数(例如平均值)相同。

用法是: var stdev = data.StdDev(o => o.number)

public static class Extensions
{
    public static double StdDev<T>(this IEnumerable<T> list, Func<T, double> values)
    {
        // ref: /programming/2253874/linq-equivalent-for-standard-deviation
        // ref: http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/ 
        var mean = 0.0;
        var sum = 0.0;
        var stdDev = 0.0;
        var n = 0;
        foreach (var value in list.Select(values))
        {
            n++;
            var delta = value - mean;
            mean += delta / n;
            sum += delta * (value - mean);
        }
        if (1 < n)
            stdDev = Math.Sqrt(sum / (n - 1));

        return stdDev; 

    }
} 

1
请注意,Average//MinMax/ etc具有选择器功能的重载。他们也有整型,浮点型,等过载
德鲁诺克斯

5
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));

2

直截了当(C#> 6.0),Dynamis答案变为:

    public static double StdDev(this IEnumerable<double> values)
    {
        var count = values?.Count() ?? 0;
        if (count <= 1) return 0;

        var avg = values.Average();
        var sum = values.Sum(d => Math.Pow(d - avg, 2));

        return Math.Sqrt(sum / count);
    }

编辑2020-08-27:

我接受@David Clarke的评论进行了一些性能测试,结果如下:

    public static (double stdDev, double avg) StdDevFast(this List<double> values)
    {
        var count = values?.Count ?? 0;
        if (count <= 1) return (0, 0);

        var avg = GetAverage(values);
        var sum = GetSumOfSquareDiff(values, avg);

        return (Math.Sqrt(sum / count), avg);
    }

    private static double GetAverage(List<double> values)
    {
        double sum = 0.0;
        for (int i = 0; i < values.Count; i++) 
            sum += values[i];
        
        return sum / values.Count;
    }
    private static double GetSumOfSquareDiff(List<double> values, double avg)
    {
        double sum = 0.0;
        for (int i = 0; i < values.Count; i++)
        {
            var diff = values[i] - avg;
            sum += diff * diff;
        }
        return sum;
    }

我用一百万个随机双倍
的列表进行了测试,原始实现的运行时间约为48ms
,性能优化的实现为2-3ms,
因此这是一个显着的改进。

一些有趣的细节:
摆脱Math.Pow可以提高33ms!
用List代替IEnumerable 6ms
手动平均计算4ms用
For-loops代替ForEach-loops 2ms
Array代替List仅带来〜2 %的改善,所以我跳过了
使用单而不是double的情况

进一步降低代码并使用goto(是的,GOTO ...自90年代的汇编程序以来就没有使用过...),而不是for循环没有用,谢谢!

我还测试了并行计算,这在列表> 200.000项上很有意义。看来硬件和软件需要初始化很多,而这对于小清单却适得其反。

所有测试都连续执行两次以消除预热时间。


请注意,在评估和时Count(),这会使数据多次通过。较小的值可以,但是如果较大,则可能会影响性能。Average()Sum()countcount
戴维·克拉克

@大卫,所以我认为最简单的解决方案是将签名替换为 (this IList<double> values),性能测试将显示影响,以及多少项目产生了显着差异
恩斯特·格赖纳

是啊,这并不能解决问题-这些扩展方法(CountAverageSum)每个迭代的集合,所以你仍然有三个完整的迭代产生结果。
David Clarke

0
public static double StdDev(this IEnumerable<int> values, bool as_sample = false)
{
    var count = values.Count();
    if (count > 0) // check for divide by zero
    // Get the mean.
    double mean = values.Sum() / count;

    // Get the sum of the squares of the differences
    // between the values and the mean.
    var squares_query =
        from int value in values
        select (value - mean) * (value - mean);
    double sum_of_squares = squares_query.Sum();
    return Math.Sqrt(sum_of_squares / (count - (as_sample ? 1 : 0)))
}

请注意,如果数据集较小,则仍会多次遍历数据-如果值不大,则不好count
大卫·克拉克

0

简单的4行,我使用了一个双打列表,但是一个可以使用 IEnumerable<int> values

public static double GetStandardDeviation(List<double> values)
{
    double avg = values.Average();
    double sum = values.Sum(v => (v - avg) * (v - avg));
    double denominator = values.Count - 1;
    return denominator > 0.0 ? Math.Sqrt(sum / denominator) : -1;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.