Độ lệch chuẩn trong LINQ


80

LINQ có mô hình hóa hàm SQL tổng hợp STDDEV() (độ lệch chuẩn) không?

Nếu không, cách đơn giản nhất / thực tiễn tốt nhất để tính toán nó là gì?

Thí dụ:

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


@Steven, bạn có thể muốn xem lại câu trả lời được chấp nhận tại đây. Có vấn đề với cách tiếp cận hiện được chọn mà những người không cuộn xuống và đọc thêm có thể không thấy.
Drew Noakes

Tại sao mọi người muốn làm điều này bằng cách sử dụng LINQ ?
Ant_222

Câu trả lời:


98

Bạn có thể tạo tiện ích mở rộng của riêng mình để tính toán nó

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

Nếu bạn có một mẫu dân số hơn là toàn bộ dân số, thì bạn nên sử dụng ret = Math.Sqrt(sum / (count - 1));.

Được chuyển thành phần mở rộng từ Thêm độ lệch chuẩn thành LINQ bởi Chris Bennett .


3
Tôi sẽ thực hiện thử nghiệm đó "giá trị.Count ()> 1", bởi vì nếu nó chính xác là 1, bạn sẽ có lỗi chia cho 0 khi bạn tính toán giá trị trả về.
duffymo

3
Math.pow (d-avg, 2)? Tôi sẽ bỏ qua lệnh gọi hàm và sử dụng (d-avg) * (d-avg)
duffymo

2
Dòng ret = Math.Sqrt ((sum) / giá trị.Count () - 1); thiếu dấu ngoặc quanh giá trị.Count () - 1, nó phải là ret = Math.Sqrt (sum / (giá trị.Count () - 1));
Alex Peck

1
Tôi đã tìm kiếm này và nó đã cho tôi một thời gian để tìm ra cách để sử dụng phần mở rộng, nhưng đây là cách để áp dụng các phương pháp đưa ra ở trên: stdev = g.Select(o => o.number).StdDev().
Andrew Mao

2
@Yevgeniy Rozhkov - Tại sao bạn lại xóa - 1? Theo này các - 1là bắt buộc.
John Mills

61

Câu trả lời của Dynami hoạt động nhưng thực hiện nhiều lần chuyển qua dữ liệu để có được kết quả. Đây là một phương pháp vượt qua đơn để tính toán độ lệch chuẩn mẫu :

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

Đây là độ lệch chuẩn mẫu vì nó chia cho n - 1. nThay vào đó, đối với độ lệch chuẩn thông thường, bạn cần chia cho .

Điều này sử dụng phương pháp của Welford có độ chính xác số cao hơn so với Average(x^2)-Average(x)^2phương pháp này.


1
Bạn có thể không lặp lại toàn bộ chuỗi nhiều lần, nhưng phương thức của bạn sẽ vẫn thực hiện hai lệnh gọi tới GetEnumerator (có thể đang kích hoạt một truy vấn SQL phức tạp). Tại sao không bỏ qua điều kiện và kiểm tra n ở cuối vòng lặp?
Gideon Engelberth

Cảm ơn Gideon, cũng loại bỏ một cấp độ lồng nhau. Bạn nói đúng về SQL, nó không liên quan đến những gì tôi đang làm nên tôi đã không xem xét hàm ý.
David Clarke

3
Bạn đang thiếu định nghĩa của n. Cũng cần lưu ý rằng chia cho tổng (n-1) thay vì n điều này làm cho một độ lệch chuẩn mẫu
Neil

3
Để làm cho điều này sao chép cẩn thận hơn phương thức SQL, tôi đã thay đổi this IEnumerable<double?> valuesval in values.Where(val => val != null). Ngoài ra, tôi sẽ lưu ý rằng phương pháp này (phương pháp của Welford) chính xác hơn và nhanh hơn phương pháp trên.
Andrew Mao

2
Tôi đã chỉnh sửa câu trả lời của bạn để làm rõ rằng bạn đang tính độ lệch chuẩn mẫu , không phải độ lệch chuẩn thông thường .
CodesInChaos 17/12/13

31

Điều này chuyển đổi câu trả lời của David Clarke thành một phần mở rộng có cùng dạng với các hàm LINQ tổng hợp khác như Trung bình.

Cách sử dụng sẽ là: 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
Lưu ý rằng Average/ Min/ Max/ etc có quá tải có và không có chức năng bộ chọn. Họ cũng có quá tải với nhiều loại không thể thiếu, phao vv
Drew Noakes

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

2

Đi thẳng vào vấn đề (và C #> 6.0), câu trả lời của Dynamis trở thành như sau:

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

Chỉnh sửa 2020-08-27:

Tôi đã lấy nhận xét của @David Clarke để thực hiện một số bài kiểm tra hiệu suất và đây là kết quả:

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

Tôi đã thử nghiệm điều này với danh sách một triệu nhân đôi ngẫu nhiên,
quá trình triển khai ban đầu có thời gian chạy ~ 48ms,
việc triển khai được tối ưu hóa hiệu suất 2-3ms,
vì vậy đây là một cải tiến đáng kể.

Một số chi tiết thú vị:
loại bỏ Math.Pow sẽ tăng 33ms!
Danh sách thay vì IEnumerable 6ms
theo cách thủ công Tính toán trung bình 4ms
Vòng lặp thay vì ForEach vòng lặp 2ms
Mảng thay vì Danh sách chỉ mang lại sự cải thiện ~ 2% vì vậy tôi đã bỏ qua điều này
bằng cách sử dụng đơn thay vì kép không mang lại gì

Tiếp tục hạ thấp mã và sử dụng goto (vâng GOTO ... đã không sử dụng điều này kể từ những năm 90 ...) thay vì vòng lặp for không trả tiền, Cảm ơn trời!

Tôi cũng đã thử nghiệm tính toán song song, điều này có ý nghĩa với danh sách> 200.000 mục Có vẻ như Phần cứng và Phần mềm cần khởi tạo rất nhiều và điều này đối với các danh sách nhỏ thì không hiệu quả.

Tất cả các thử nghiệm được thực hiện hai lần liên tiếp để loại bỏ thời gian khởi động.


Hãy nhận biết điều này làm cho nhiều đi qua các dữ liệu khi đánh giá Count(), Average()Sum(). Điều đó tốt cho các giá trị nhỏ countnhưng có khả năng ảnh hưởng đến hiệu suất nếu countlớn.
David Clarke

@ David, vì vậy giải pháp đơn giản nhất theo ý kiến của tôi sẽ là để thay thế cho chữ ký với (this IList<double> values), kiểm tra hiệu suất sẽ thấy tác động, và có bao nhiêu mặt hàng làm cho một sự khác biệt đáng kể
Ernst Greiner

Vâng đó không giải quyết vấn đề - những phương pháp khuyến nông ( Count, Average, Sum) mỗi lặp bộ sưu tập vì vậy bạn vẫn có ba đầy đủ lặp để tạo ra một kết quả.
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)))
}

Lưu ý rằng điều này vẫn tạo ra nhiều lần chuyển qua dữ liệu - ok nếu một tập dữ liệu nhỏ nhưng không tốt cho các giá trị lớn của count.
David Clarke

0

4 dòng đơn giản, tôi đã sử dụng Danh sách đôi nhưng một dòng có thể sử dụng 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.