LoginSignup
6
3

More than 3 years have passed since last update.

LINQでStdev (標準偏差)

Last updated at Posted at 2020-03-28

背景

LINQの標準ライブラリには標準偏差を求めるメソッドがない。
なので拡張メソッドを自作
詳しくはこちら
※ちなみに、ここで求めているのは母集団標準偏差(ExcelのStdev.P)です

コード

アルゴリズムはこちらを参照させていただきました。
自乗和を先に求めるのがポイント

public static double Stdev<T>(this IEnumerable<T> src)
{
    if(!src.Any()) throw new InvalidOperationException("Cannot compute median for an empty set.");
    //Doubleにキャストして処理を進める
    var doubleList = src.Select(a => Convert.ToDouble(a)).ToArray();

    //平均値算出
    double mean = doubleList.Average();
    //自乗和算出
    double sum2 = doubleList.Select(a => a * a).Sum();
    //分散 = 自乗和 / 要素数 - 平均値^2
    double variance = sum2 / doubleList.Count - mean * mean;
    //標準偏差 = 分散の平方根
    return Math.Sqrt(variance);
}

※以前のコードからキャスト法を変更しました(詳細は下記)
※四則演算可否確認処理が冗長だったので、削除しました

結果

List<int> iList = new List<int> { 1, 2, 3, 4, 5 };
List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 };
//標準偏差(int)
Console.WriteLine(iList.Stdev().ToString());
//標準偏差(double)
Console.WriteLine(dList.Stdev().ToString());
1.4142135623731
0.28613807855649

正常に標準偏差が出せていそうです

苦労したところ

・Generic → doubleへのキャスト
平均や二乗和を求めるためのAverage()やSum()がGeneric型では使えない。
doubleにキャストしようとしたが、ここに書いてあるobjectキャストを挟む方法ではエラー発生
ここに書いてるConvert.ToDouble()でキャストできた

(foreachの中なので、キャスト部分の動作速度が若干不安ではありますが)

追記 キャストの速度について

キャスト法を変更&ジェネリックからのキャストの速度が気になったので、測定してみました。

テスト1:ジェネリックなしで処理を直打ち(下のテストコード参照)

テスト2:上の記事でのキャスト法

テスト3:以前のキャスト法

以前のキャスト法
public static double Stdev2<T>(this IEnumerable<T> src)
        {
            if(!src.Any()) throw new InvalidOperationException("Cannot compute median for an empty set.");
            //Doubleにキャストして処理を進める
            var doubleList = new List<double>();
            foreach (var srcRow in src) doubleList.Add(Convert.ToDouble(srcRow));

            //平均値算出
            double mean = doubleList.Average();
            //自乗和算出
            double sum2 = doubleList.Select(a => a * a).Sum();
            //分散 = 自乗和 / 要素数 - 平均値^2
            double variance = sum2 / doubleList.Count - mean * mean;
            //標準偏差 = 分散の平方根
            return Math.Sqrt(variance);
        }
テストコード(100万要素の標準偏差計算)
var testList = Enumerable.Range(0, 1000000).ToArray();
for (int i = 0; i < 10; i++)
{
    //テストその1(ジェネリックからキャスト)
    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    var test1 = testList.Stdev();
    sw.Stop();
    Console.WriteLine(sw.Elapsed.TotalMilliseconds);
    //テストその2(ジェネリックからキャスト(旧))
    sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    var test2 = testList.Stdev2();
    sw.Stop();
    Console.WriteLine(sw.Elapsed.TotalMilliseconds);
    //テストその3(ジェネリックなしでキャスト)
    sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    var doubleList = testList.Select(c => (double)c).ToArray();
    //平均値算出
    double mean = doubleList.Average();
    //自乗和算出
    double sum2 = doubleList.Select(c => c * c).Sum();
    //分散 = 自乗和 / 要素数 - 平均値^2
    double variance = sum2 / doubleList.Length - mean * mean;
    //標準偏差 = 分散の平方根
    double stdev = Math.Sqrt(variance);
    sw.Stop();
    Console.WriteLine(sw.Elapsed.TotalMilliseconds);
}


結果
テスト1 | テスト2 | テスト3
31.6994 |57.3438 |48.5285
32.7673 |45.8568 |49.1214
32.5674 |45.5782 |49.4263
32.9343 |41.131 |49.9514
32.1911 |44.8735 |49.0177
32.3129 |41.1157 |46.1472
33.2736 |43.896 |59.9206
36.8521 |40.2454 |45.7436
39.7002 |43.5034 |48.8821
33.6299 |42.0819 |47.0015

テスト1(ジェネリックのキャストなし):平均33.8ミリ秒
テスト2(新キャスト法):平均44.6ミリ秒
テスト3(旧キャスト法):平均49.4ミリ秒

キャストに若干の時間は掛かっていそうですが、
標準偏差の計算時間の方が大きいので、
それほど気にせずともよいレベルと思われます。

6
3
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3