LINQ是否对聚合SQL函数STDDEV()
(标准偏差)建模?
如果没有,最简单/最佳实践的计算方法是什么?
例:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
LINQ是否对聚合SQL函数STDDEV()
(标准偏差)建模?
如果没有,最简单/最佳实践的计算方法是什么?
例:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
Answers:
您可以让自己的扩展程序进行计算
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));
。
stdev = g.Select(o => o.number).StdDev()
。
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
方法。
this IEnumerable<double?> values
和val in values.Where(val => val != null)
。另外,我将注意到这种方法(威尔福德方法)比上面的方法更准确,更快。
这会将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;
}
}
Average
//Min
和Max
/ etc具有选择器功能的重载。他们也有整型,浮点型,等过载
直截了当(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()
count
count
(this IList<double> values)
,性能测试将显示影响,以及多少项目产生了显着差异
Count
,Average
,Sum
)每个迭代的集合,所以你仍然有三个完整的迭代产生结果。
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
。
简单的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;
}