Чи моделює 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;
}