【问题标题】:Standard Deviation in LINQLINQ 中的标准差
【发布时间】:2011-01-16 06:26:05
【问题描述】:

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

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

例子:

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

【问题讨论】:

标签: linq standard-deviation


【解决方案1】:

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

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));

Adding Standard Deviation to LINQ by Chris Bennett转化为扩展。

【讨论】:

  • Math.pow(d-avg, 2)?我会跳过函数调用并使用 (d-avg)*(d-avg)
  • 行 ret = Math.Sqrt((sum) / values.Count()-1); values.Count()-1 周围缺少括号,应该是 ret = Math.Sqrt(sum / (values.Count()-1));
  • 这样做的缺点是对输入进行两次评估,这对于IEnumerable&lt;T&gt; 参数是不鼓励的。
  • @Yevgeniy Rozhkov - 为什么要删除 - 1?根据this - 1 是必需的。
【解决方案2】:

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's method,与Average(x^2)-Average(x)^2 方法相比,它具有更高的数值精度。

【讨论】:

  • 您可能没有多次迭代整个序列,但您的方法仍会调用 GetEnumerator 两次(这可能会触发复杂的 SQL 查询)。为什么不跳过条件,在循环结束时检查 n?
  • 感谢 Gideon,也删除了一层嵌套。您对 SQL 的看法是正确的,它与我的工作无关,所以我没有考虑其含义。
  • 您缺少 n 的定义。还应注意,将总和除以 (n-1) 而不是 n 使其成为样本标准偏差
  • 为了更仔细地复制 SQL 方法,我更改了this IEnumerable&lt;double?&gt; valuesval in values.Where(val =&gt; val != null)。另外,我会注意到,这种方法(Welford 方法)比上述方法更准确、更快。
  • 我已经编辑了您的答案,以明确您计算的是样本标准差,而不是正常的标准差。跨度>
【解决方案3】:

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

用法为:var stdev = data.StdDev(o =&gt; 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; 

    }
} 

【讨论】:

  • 请注意,Average/Min/Max/etc 具有带和不带选择器功能的重载。它们还具有整数类型、浮点数等的重载。
  • 由于某种原因,我一直使用这个解决方案得到零
【解决方案4】:
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));

【讨论】:

    【解决方案5】:

    直截了当(并​​且 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 cmets 进行了一些性能测试 结果如下:

        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-3ms
    所以这是一个显着的改进。

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

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

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

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

    【讨论】:

    • 请注意,在评估 Count()Average()Sum() 时,这会使数据多次传递。这对于较小的 count 值是可以的,但如果 count 较大,则可能会影响性能。
    • @david,所以我认为最简单的解决方案是将签名替换为(this IList&lt;double&gt; values),性能测试会显示影响,以及有多少项目会产生显着差异
    • 是的,这并不能解决问题 - 这些扩展方法(CountAverageSum)每个都会迭代集合,因此您仍然有三个完整的迭代来产生结果。
    【解决方案6】:

    简单的 4 行,我使用了一个双打列表,但可以使用 IEnumerable&lt;int&gt; 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;
    }
    

    【讨论】:

      【解决方案7】:
      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 的大值。
      【解决方案8】:

      一般的情况下,我们想在单程中计算StdDev:如果values文件或RDBMS 光标 在计算平均值和总和之间可以更改?我们将得到不一致的结果。这 下面的代码只使用了一次:

      // 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));
      }
      

      【讨论】:

        猜你喜欢
        • 2014-02-27
        • 2017-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-12
        • 2022-11-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多