LINQの標準偏差


80

LINQは集約SQL関数STDDEV() (標準偏差)をモデル化しますか?

そうでない場合、それを計算するための最も簡単な/ベストプラクティスの方法は何ですか?

例:

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


@スティーブン、あなたはここで受け入れられた答えを再訪したいかもしれません。現在選択されているアプローチには、下にスクロールしてさらに読んでいない人には表示されない可能性のある問題があります。
ドリューノアケス2015年

なぜ誰もがLINQを使用してこれを実行したいのですか?
Ant_222 2018年

回答:


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 2010

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()
Andrew Mao

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
シーケンス全体を2回以上繰り返していない場合でも、メソッドはGetEnumeratorを2回呼び出します(複雑なSQLクエリをトリガーする可能性があります)。条件をスキップして、ループの最後でnをチェックしてみませんか?
ギデオンエンゲル

ギデオンに感謝し、ネストのレベルも削除します。あなたはSQLについて正しいです、それは私が取り組んでいるものとは関係がないので、私はその意味を考慮していませんでした。
デビッドクラーク

3
nの定義がありません。また、合計をnではなく(n-1)で除算すると、これがサンプルの標準偏差になることに注意してください
Neil

3
これをより注意深くSQLメソッドを複製するために、とを変更this IEnumerable<double?> valuesしましたval in values.Where(val => val != null)。また、この方法(ウェルフォードの方法)は、上記の方法よりも正確で高速であることに注意してください。
Andrew Mao

2
回答を編集して、通常の標準偏差ではなく、サンプルの標準偏差を計算していることを明確にしました。
CodesInChaos 2013

31

これにより、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; 

    }
} 

1
Average/ Min/ Max/ etcには、セレクター関数がある場合とない場合のオーバーロードがあることに注意してください。彼らはまた、整数型、浮動小数点数などのためのオーバーロードが持っている
ドリューNoakes


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

私はこれを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が大きい場合はパフォーマンスに影響を与える可能性があります。
デビッドクラーク

@ davidなので、私の意見では、署名をに置き換えるのが最も簡単な解決策です (this IList<double> values)。パフォーマンステストは影響を示し、アイテムの数が大きな違いを生む
ErnstGreiner20年

うん、それは問題を解決しない-これらの拡張メソッド(CountAverageSum)各反復にあなたがまだ結果を生成するための3回の完全な反復を持っているので、コレクションを。
デビッドクラーク

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
デビッドクラーク2017年

0

単純な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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.