Чи моделює LINQ сукупну функцію SQL STDDEV() (стандартне відхилення)?
Якщо ні, то який найпростіший / найкращий спосіб його обчислити?
Приклад:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
Чи моделює LINQ сукупну функцію SQL STDDEV() (стандартне відхилення)?
Якщо ні, то який найпростіший / найкращий спосіб його обчислити?
Приклад:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
Відповіді:
Ви можете зробити власне розширення, розрахувавши його
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 .
stdev = g.Select(o => o.number).StdDev().
Відповідь Динамі працює, але робить кілька проходів через дані, щоб отримати результат. Це метод одного проходу, який обчислює стандартне відхилення вибірки :
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методом.
this IEnumerable<double?> valuesі val in values.Where(val => val != null). Крім того, я зазначу, що цей метод (метод Велфорда) є більш точним і швидшим, ніж метод вище.
Це перетворює відповідь Девіда Кларка на розширення, яке відповідає тій самій формі, що й інші сукупні функції 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;
}
}
Average/ Min/ Max/ etc мають перевантаження з функціями селектора та без них. У них також є перевантаження для цілісних типів, поплавків тощо.
Прямо до точки (і C #> 6.0) відповідь "Динамісу" стає такою:
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 мс!
Список замість IEnumerable 6ms
вручну Середній розрахунок 4ms
For-Loops замість ForEach-Loops 2ms
Array замість List приносить лише поліпшення на ~ 2%, тому я пропустив це,
використовуючи single замість double не приносить нічого
Подальше зниження коду та використання goto (так GOTO ... не використовував це з 90-х років асемблер ...) замість for-loops не платить, дякую Богу!
Я протестував також паралельний розрахунок, це має сенс у списку> 200 000 пунктів. Здається, що апаратне та програмне забезпечення має багато ініціалізувати, і це для невеликих списків контрапродуктивно.
Всі тести проводились два рази поспіль, щоб позбутися часу розминки.
Count(), Average()і Sum(). Це нормально для малих значень, countале може мати вплив на ефективність, якщо countвона велика.
(this IList<double> values), тести продуктивності показали б вплив і те, скільки предметів суттєво впливає
Count, Average, Sum) кожен ітерацію колекції , так що ви все ще є три повних ітерацій для отримання результату.
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 рядки, я використовував список пар, але один міг використовувати 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;
}