Стандартное отклонение в LINQ
Моделирует ли LINQ агрегатную функцию SQL STDDEV()
(стандартное отклонение)?
Если нет, то какой самый простой / лучший метод расчета?
Пример:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
8 ответов
Вы можете сделать свое собственное расширение, рассчитав его
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));
,
Преобразовано в расширение от добавления стандартного отклонения к LINQ Крисом Беннеттом.
Ответ 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
вместо.
При этом используется метод Уэлфорда, который имеет более высокую числовую точность по сравнению с Average(x^2)-Average(x)^2
метод.
Это преобразует ответ Дэвида Кларка в расширение, которое следует той же форме, что и другие агрегатные функции 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: https://stackru.com/questions/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;
}
}
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),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;
}
Я тестировал это со списком из миллиона случайных двойников:
исходная реализация имела время выполнения ~48 мс,
реализация с оптимизацией производительности 2-3 мс,
так что это значительное улучшение.
Некоторые интересные детали:
избавление от Math.Pow дает ускорение на 33 мс!
List вместо IEnumerable 6 мс
вручную Среднее вычисление 4 мс
For-loops вместо ForEach-loops 2ms
Array вместо List дает только улучшение ~2%, поэтому я пропустил это,
используя single вместо double, ничего не приносит
Дальнейшее снижение кода и использование goto (да, GOTO... не использовал это с ассемблера 90-х...) вместо циклов for не окупается, слава богу!
Я также протестировал параллельный расчет, это имеет смысл для списка> 200000 пунктов. Кажется, что аппаратное и программное обеспечение требует много инициализации, и это для небольших списков нецелесообразно.
Все тесты проводились два раза подряд, чтобы избавиться от времени прогрева.
В общем случае мы хотим вычислить за один проход : что, если
values
это файл или курсор СУБД, который можно переключать между вычислением среднего и суммы? У нас будет противоречивый результат . В приведенном ниже коде используется всего один проход:
// Population StdDev
public static double StdDev(this IEnumerable<double> values) {
if (null == values)
throw new ArgumentNullException(nameof(values));
double N = 0;
double Sx = 0.0;
double Sxx = 0.0;
foreach (double x in values) {
N += 1;
Sx += x;
Sxx += x * x;
}
return N == 0
? double.NaN // or throw exception
: Math.Sqrt((Sxx - Sx * Sx / N) / N);
}
Сама идея для образца
StdDev
:
// Sample StdDev
public static double StdDev(this IEnumerable<double> values) {
if (null == values)
throw new ArgumentNullException(nameof(values));
double N = 0;
double Sx = 0.0;
double Sxx = 0.0;
foreach (double x in values) {
N += 1;
Sx += x;
Sxx += x * x;
}
return N <= 1
? double.NaN // or throw exception
: Math.Sqrt((Sxx - Sx * Sx / N) / (N - 1));
}
Простые 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;
}
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)))
}