#背景
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);
}
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ミリ秒
キャストに若干の時間は掛かっていそうですが、
標準偏差の計算時間の方が大きいので、
それほど気にせずともよいレベルと思われます。