Czy LINQ modeluje zagregowaną funkcję SQL STDDEV()
(odchylenie standardowe)?
Jeśli nie, jaki jest najprostszy / sprawdzony sposób obliczenia tego?
Przykład:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
linq
standard-deviation
Steven
źródło
źródło
Odpowiedzi:
Możesz stworzyć własne rozszerzenie obliczające to
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; } }
Jeśli masz próbkę populacji, a nie całą populację, powinieneś użyć
ret = Math.Sqrt(sum / (count - 1));
.Przekształcony w rozszerzenie z dodawania odchylenia standardowego do LINQ przez Chrisa Bennetta .
źródło
stdev = g.Select(o => o.number).StdDev()
.- 1
? Według tego- 1
jest wymagane.Odpowiedź Dynami działa, ale dokonuje wielu przejść przez dane, aby uzyskać wynik. Jest to metoda jednoprzebiegowa, która oblicza odchylenie standardowe próbki :
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; }
Jest to odchylenie standardowe próbki, ponieważ dzieli się przez
n - 1
. W przypadku normalnego odchylenia standardowego należyn
zamiast tego podzielić przez .Wykorzystuje to metodę Welforda, która ma większą dokładność numeryczną w porównaniu z tą
Average(x^2)-Average(x)^2
metodą.źródło
this IEnumerable<double?> values
ival in values.Where(val => val != null)
. Zaznaczę również, że ta metoda (metoda Welforda) jest dokładniejsza i szybsza niż metoda powyżej.To konwertuje odpowiedź Davida Clarke'a na rozszerzenie, które ma tę samą postać, co inne funkcje agregujące LINQ, takie jak Średnia.
Sposób użycia będzie:
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; } }
źródło
Average
/Min
/Max
/ etc mają przeciążenia zi bez funkcji selektora. Mają też przeciążenia dla typów całkowitych, zmiennoprzecinkowych itp.var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));
źródło
Prosto do rzeczy (i C #> 6,0), odpowiedź Dynamis wygląda tak:
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); }
Edycja 2020-08-27:
Wziąłem komentarze @David Clarke, aby wykonać testy wydajności i oto wyniki:
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; }
Przetestowałem to z listą miliona losowych dubli
. Oryginalna implementacja miała czas działania ~ 48 ms,
a implementacja zoptymalizowana pod kątem wydajności 2-3 ms,
więc jest to znacząca poprawa.
Kilka interesujących szczegółów:
pozbycie się Math.Pow zapewnia przyspieszenie o 33 ms!
List zamiast IEnumerable 6ms
ręcznie Obliczanie średniej 4ms
For-loop zamiast ForEach-loop 2ms
Array zamiast List przynosi tylko poprawę o ~ 2%, więc pominąłem to
używając single zamiast double nic nie daje
Dalsze obniżanie kodu i używanie goto (tak, GOTO ... nie używałem tego od asemblera z lat 90 ...) zamiast pętli for nie opłaca się, dzięki Bogu!
Przetestowałem również obliczenia równoległe, ma to sens na liście> 200.000 pozycji. Wygląda na to, że sprzęt i oprogramowanie wymagają dużo inicjalizacji i jest to dla małych list nieproduktywne.
Wszystkie testy zostały wykonane dwa razy z rzędu, aby pozbyć się czasu rozgrzewki.
źródło
Count()
,Average()
orazSum()
. To jest w porządku w przypadku małych wartości,count
ale może wpłynąć na wydajność, jeślicount
jest duże.(this IList<double> values)
, testy wydajności pokazałyby wpływ, a ile elementów robi znaczącą różnicęCount
,Average
,Sum
) każda iteracyjne kolekcji więc jeszcze trzy pełne powtórzeń do wytworzenia rezultatu.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))) }
źródło
count
.Proste 4 wiersze, użyłem listy podwójnych, ale można było użyć
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; }
źródło