LINQ의 표준 편차


80

LINQ는 집계 SQL 함수 STDDEV() (표준 편차)를 모델링합니까 ?

그렇지 않다면, 그것을 계산하는 가장 간단하고 모범적 인 방법은 무엇입니까?

예:

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


@Steven, 여기에서 수락 된 답변을 다시 방문하고 싶을 수 있습니다. 현재 선택된 접근 방식에는 아래로 스크롤하지 않고 더 이상 읽지 않는 사람들이 볼 수없는 문제가 있습니다.
Drew Noakes 2015-06-19

LINQ를 사용하여이 작업을 수행하려는 이유는 무엇 입니까?
Ant_222

답변:


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
정확히 1이면 반환 값을 계산할 때 0으로 나누기 오류가 발생하기 때문에 "values.Count ()> 1"테스트를 수행합니다.
duffymo

3
Math.pow (d-avg, 2)? 함수 호출을 건너 뛰고 (d-avg) * (d-avg) 사용
duffymo

2
라인 ret = Math.Sqrt ((sum) / values.Count ()-1); values.Count ()-1 주위에 괄호가 없습니다. ret = Math.Sqrt (sum / (values.Count ()-1));
Alex Peck

1
나는 이것을 찾고 있었고 확장을 사용하는 방법을 알아내는 데 시간이 걸렸지 만 여기에 위에 주어진 방법을 적용하는 방법이 stdev = g.Select(o => o.number).StdDev()있습니다.
Andrew Mao 2012 년

2
@Yevgeniy Rozhkov-왜 제거 - 1했습니까? 에 따르면 - 1 필요합니다.
John Mills

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대신 나눌 필요가 있습니다.

이것은 방법에 비해 수치 정확도가 더 높은 Welford의 방법 을 사용합니다 Average(x^2)-Average(x)^2.


1
전체 시퀀스를 두 번 이상 반복하지 않을 수 있지만 메서드는 여전히 GetEnumerator를 두 번 호출합니다 (복잡한 SQL 쿼리를 트리거 할 수 있음). 조건을 건너 뛰고 루프 끝에서 n을 확인하지 않는 이유는 무엇입니까?
Gideon Engelberth

감사합니다 Gideon, 중첩 수준도 제거합니다. 당신은 SQL에 대해 맞습니다. 제가 작업하는 것과 관련이 없기 때문에 그 의미를 고려하지 않았습니다.
David Clarke

3
n의 정의가 누락되었습니다. 또한 합계를 n 대신 (n-1)로 나누면 샘플 표준 편차가됩니다
Neil

3
SQL 메서드를보다 신중하게 복제하기 위해 this IEnumerable<double?> valuesval in values.Where(val => val != null). 또한이 방법 (Welford의 방법)이 위의 방법보다 더 정확하고 빠릅니다.
Andrew Mao

2
나는 당신이 정규 표준 편차가 아니라 표본 표준 편차를 계산하고 있음을 분명히하기 위해 당신의 대답을 편집했습니다 .
CodesInChaos

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/ 등와 선택 기능이없는 과부하가 있습니다. 또한 적분 유형, 플로트 등에 대한 과부하가 있습니다.
Drew Noakes

5
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));

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


원래 구현의 런타임이 ~ 48ms
인 성능 최적화 구현이 2-3ms 였으므로 무작위로 100 만 두 배 의 목록으로 이것을 테스트
했으므로 이것은 상당한 개선입니다.

흥미로운 세부 사항 :
Math.Pow를 제거하면 33ms가 늘어납니다! 수동으로
IEnumerable 6ms 대신 목록
평균 계산
ForEach-loops 대신 4ms For 루프
List 대신 2ms 배열은 ~ 2 %의 개선을 가져 오므
로 이중 대신 단일을 사용하여 건너 뛰면 아무것도 가져 오지 않습니다

코드를 더 낮추고 for-loops 대신 goto (예 GOTO ... 90 년대 어셈블러 이후로 이것을 사용하지 않았습니다 ...)를 사용하는 것은 지불하지 않습니다.

나는 또한 병렬 계산을 테스트했는데, 이것은 목록> 20,000 개 항목에서 의미가 있습니다. 하드웨어 및 소프트웨어를 많이 초기화해야하는 것으로 보이며 이것은 반 생산적인 작은 목록을위한 것입니다.

모든 테스트는 워밍업 시간을 없애기 위해 두 번 연속 실행되었습니다.


평가할 때이 데이터를 통해 다중 패스를하게 알고 있어야합니다 Count(), Average()하고 Sum(). 값이 작 으면 괜찮지 count만 값 count이 크면 성능에 영향을 미칠 수 있습니다.
David Clarke

제 생각에 가장 간단한 솔루션과 함께 서명을 교체하는 것입니다 다윗 @ (this IList<double> values), 성능 테스트는 영향을 보여줄 것이며, 얼마나 많은 항목은 유의 한 차이 확인
에른스트 라이너

그래, 그 문제가 해결되지 않습니다 - 그 확장 방법 ( Count, Average, Sum) 각으로 반복 당신이 여전히 결과를 생성하는 세 전체 반복을 가질 수 있도록 컬렉션을.
David Clarke

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.
David Clarke

0

간단한 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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.