¿LINQ modela la función SQL agregada? STDDEV()
(desviación estándar)?
Si no es así, ¿cuál es la forma más sencilla o con las mejores prácticas de calcularlo?
Ejemplo:
SELECT test_id, AVERAGE(result) avg, STDDEV(result) std
FROM tests
GROUP BY test_id
linq
standard-deviation
Steven
fuente
fuente
Respuestas:
Puedes hacer tu propia extensión calculándola
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; } }
Si tiene una muestra de la población en lugar de toda la población, debe usar
ret = Math.Sqrt(sum / (count - 1));
.Transformado en extensión de Agregar desviación estándar a LINQ por Chris Bennett .
fuente
stdev = g.Select(o => o.number).StdDev()
.- 1
? De acuerdo con esto el- 1
que se requiere.La respuesta de Dynami funciona, pero hace varios pases a través de los datos para obtener un resultado. Este es un método de un solo paso que calcula la desviación estándar de la muestra :
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; }
Ésta es la desviación estándar de la muestra, ya que divide por
n - 1
. Para la desviación estándar normal, debe dividir porn
.Esto utiliza el método de Welford, que tiene una mayor precisión numérica en comparación con el
Average(x^2)-Average(x)^2
método.fuente
this IEnumerable<double?> values
yval in values.Where(val => val != null)
. Además, señalaré que este método (método de Welford) es más preciso y más rápido que el método anterior.Esto convierte la respuesta de David Clarke en una extensión que sigue la misma forma que las otras funciones LINQ agregadas como Promedio.
El uso sería:
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; } }
fuente
Average
/Min
/Max
/ etc tienen sobrecargas con y sin funciones de selector. También tienen sobrecargas para tipos integrales, flotación, etc.var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));
fuente
Directamente al grano (y C #> 6.0), la respuesta de Dynamis se convierte en esta:
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); }
Editar 2020-08-27:
Tomé los comentarios de @David Clarke para hacer algunas pruebas de rendimiento y estos son los resultados:
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; }
Probé esto con una lista de un millón de dobles aleatorios;
la implementación original tenía un tiempo de ejecución de ~ 48 ms;
la implementación optimizada para el rendimiento, 2-3 ms
por lo que esta es una mejora significativa.
Algunos detalles interesantes: ¡
deshacerse de Math.Pow trae un impulso de 33ms!
List en lugar de IEnumerable 6ms
manualmente Cálculo promedio 4ms
For-loops en lugar de ForEach-loops 2ms
Array en lugar de List trae solo una mejora de ~ 2%, así que omití esto
usando single en lugar de double no trae nada
Bajar aún más el código y usar goto (sí, GOTO ... no he usado esto desde el ensamblador de los 90 ...) en lugar de bucles for no paga, ¡Gracias a Dios!
También he probado el cálculo paralelo, esto tiene sentido en la lista> 200.000 elementos. Parece que el hardware y el software necesitan inicializarse mucho y esto es contraproducente para listas pequeñas.
Todas las pruebas se ejecutaron dos veces seguidas para eliminar el tiempo de calentamiento.
fuente
Count()
,Average()
ySum()
. Eso está bien para valores pequeños de,count
pero tiene potencial para afectar el rendimiento sicount
es grande.(this IList<double> values)
, las pruebas de rendimiento mostrarían el impacto y cuántos elementos marcan una diferencia significativaCount
,Average
,Sum
) cada uno iterar la colección por lo que aún tiene tres iteraciones completos para producir un resultado.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))) }
fuente
count
.4 líneas simples, utilicé una lista de dobles pero se podría usar
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; }
fuente