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
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
Câu trả lời:
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 .
stdev = g.Select(o => o.number).StdDev()
.
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
. n
Thay 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)^2
phương pháp này.
this IEnumerable<double?> values
và val 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.
Đ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;
}
}
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
Đ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.
Count()
, Average()
và Sum()
. Điều đó tốt cho các giá trị nhỏ count
nhưng có khả năng ảnh hưởng đến hiệu suất nếu count
lớn.
(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ể
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ả.
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
.
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;
}