87

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

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

例子:

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

8 回答 8

108

您可以制作自己的扩展来计算它

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 的扩展。

于 2010-02-12T17:52:34.777 回答
68

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

这使用了Welford 方法,该方法与该方法相比具有更高的数值精度Average(x^2)-Average(x)^2

于 2010-05-20T21:29:19.587 回答
33

这会将David Clarke 的答案转换为与其他聚合 LINQ 函数(如 Average)采用相同形式的扩展。

用法是: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: https://stackoverflow.com/questions/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; 

    }
} 
于 2012-09-14T17:20:27.813 回答
5
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));
于 2015-03-06T12:03:24.323 回答
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;
    }

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

一些有趣的细节:
摆脱 Math.Pow 带来了 33 毫秒的提升!
手动使用List 而不是 IEnumerable 6ms
平均计算 4ms
For-loops 而不是 ForEach-loops 2ms
Array 而不是 List 只带来了约 2% 的改进,所以我跳过了这个
使用 single 而不是 double 什么都没有

进一步降低代码并使用 goto (是的 GOTO ......自 90 年代汇编程序以来就没有使用过这个......)而不是 for-loops 没有报酬,谢天谢地!

我还测试了并行计算,这对列表 > 200.000 项有意义。似乎硬件和软件需要进行很多初始化,这对于小列表会产生反效果。

所有测试连续执行两次以摆脱预热时间。

于 2019-07-23T09:07:30.773 回答
1

简单的 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;
}
于 2020-10-28T22:34:21.947 回答
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)))
}
于 2016-04-25T00:26:06.837 回答
0

一般情况下,我们希望一次性计算:如果文件或 RDBMS游标 可以StdDev在计算平均值和总和之间更改怎么办?我们将得到不一致的结果。下面的代码只使用了一次:values

// Population StdDev
public static double StdDev(this IEnumerable<double> values) {
  if (null == values)
    throw new ArgumentNullException(nameof(values));

  double N = 0;
  double Sx = 0.0;
  double Sxx = 0.0;

  foreach (double x in values) {
    N += 1;
    Sx += x;
    Sxx += x * x;
  }

  return N == 0
    ? double.NaN // or throw exception
    : Math.Sqrt((Sxx - Sx * Sx / N) / N);
}

sample 的想法是一样的StdDev

// Sample StdDev
public static double StdDev(this IEnumerable<double> values) {
  if (null == values)
    throw new ArgumentNullException(nameof(values));

  double N = 0;
  double Sx = 0.0;
  double Sxx = 0.0;

  foreach (double x in values) {
    N += 1;
    Sx += x;
    Sxx += x * x;
  }

  return N <= 1
    ? double.NaN // or throw exception
    : Math.Sqrt((Sxx - Sx * Sx / N) / (N - 1));
}
于 2021-05-04T14:46:45.013 回答