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
回答:
あなたはそれを計算するあなた自身の拡張を作ることができます
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への拡張に変換されました。
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の回答が、Averageなどの他の集約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;
}
私はこれを100万のランダムダブルのリストでテストしました。
元の実装の実行時間は約48ミリ秒で
、パフォーマンスが最適化された実装は2〜3ミリ秒
だったので、これは大幅な改善です。
いくつかの興味深い詳細:
Math.Powを取り除くと、33ミリ秒のブーストがもたらされます!
IEnumerableの代わりにリストを
手動で6ms平均計算
ForEachループの代わりに4msForループ
リストの代わりに2ms配列を使用すると、約2%の改善が見られるため
、doubleの代わりにsingleを使用してこれをスキップしても何も起こりません
コードをさらに下げて、forループの代わりにgotoを使用すると(はい、GOTO ...は90年代のアセンブラー以来これを使用していません...)、支払いはありません。よろしくお願いします。
並列計算もテストしました。これは200.000を超えるアイテムのリストで意味があります。ハードウェアとソフトウェアは多くの初期化が必要なようで、これは小さなリストの場合は逆効果です。
ウォームアップ時間をなくすために、すべてのテストを2回続けて実行しました。
Count()
、Average()
とSum()
。の値が小さい場合は問題ありcount
ませんが、値count
が大きい場合はパフォーマンスに影響を与える可能性があります。
(this IList<double> values)
。パフォーマンステストは影響を示し、アイテムの数が大きな違いを生む
Count
、Average
、Sum
)各反復にあなたがまだ結果を生成するための3回の完全な反復を持っているので、コレクションを。
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行、ダブルのリストを使用しましたが、1つは使用できます 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;
}