Standardabweichung in LINQ

80

Modelliert LINQ die aggregierte SQL-Funktion STDDEV() (Standardabweichung)?

Wenn nicht, wie lässt sich dies am einfachsten / am besten berechnen?

Beispiel:

  SELECT test_id, AVERAGE(result) avg, STDDEV(result) std 
    FROM tests
GROUP BY test_id
Steven
quelle
@Steven, vielleicht möchten Sie die akzeptierte Antwort hier noch einmal überprüfen. Es gibt Probleme mit dem aktuell ausgewählten Ansatz, die Personen, die nicht nach unten scrollen und weiterlesen, möglicherweise nicht sehen.
Drew Noakes
Warum sollte jemand dies mit LINQ tun wollen ?
Ant_222

Antworten:

98

Sie können Ihre eigene Erweiterung erstellen, um sie zu berechnen

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;
    }
}

Wenn Sie eine Stichprobe der Bevölkerung und nicht der gesamten Bevölkerung haben, sollten Sie diese verwenden ret = Math.Sqrt(sum / (count - 1));.

Von Chris Bennett in Erweiterung von Hinzufügen von Standardabweichung zu LINQ umgewandelt .

Dynami Le Savard
quelle
3
Ich würde diesen Test "values.Count ()> 1" durchführen, denn wenn es genau 1 ist, wird bei der Berechnung des Rückgabewerts ein Fehler durch Null geteilt.
Duffymo
3
Math.pow (d-Durchschnitt, 2)? Ich würde den Funktionsaufruf überspringen und (d-avg) * (d-avg)
duffymo
2
Die Zeile ret = Math.Sqrt ((Summe) / Werte.Count () - 1); Fehlt Klammern um values.Count () - 1, sollte es ret = Math.Sqrt (sum / (values.Count () - 1)) sein;
Alex Peck
1
Ich habe danach gesucht und es hat eine Weile gedauert, bis ich herausgefunden habe, wie die Erweiterung verwendet wird. Hier ist jedoch die Möglichkeit, die oben angegebenen Methoden anzuwenden : stdev = g.Select(o => o.number).StdDev().
Andrew Mao
2
@ Yevgeniy Rozhkov - Warum hast du das entfernt - 1? Nach dieser die - 1erforderlich ist .
John Mills
61

Die Antwort von Dynami funktioniert, durchläuft jedoch mehrere Daten, um ein Ergebnis zu erhalten. Dies ist eine Single-Pass-Methode, mit der die Standardabweichung der Stichprobe berechnet wird :

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;
}

Dies ist die Standardabweichung der Stichprobe, da sie durch dividiert wird n - 1. Für die normale Standardabweichung müssen Sie nstattdessen durch dividieren .

Dies verwendet die Welford-Methode, die im Vergleich zur Average(x^2)-Average(x)^2Methode eine höhere numerische Genauigkeit aufweist .

David Clarke
quelle
1
Möglicherweise haben Sie die gesamte Sequenz nicht mehr als einmal durchlaufen, aber Ihre Methode ruft immer noch zwei Aufrufe von GetEnumerator auf (was möglicherweise eine komplexe SQL-Abfrage auslöst). Warum nicht die Bedingung überspringen und n am Ende der Schleife überprüfen?
Gideon Engelberth
Dank Gideon wird auch eine Verschachtelungsebene entfernt. Sie haben Recht mit SQL, es ist nicht relevant für das, woran ich arbeite, also hatte ich die Implikation nicht berücksichtigt.
David Clarke
3
Ihnen fehlt eine Definition von n. Es sollte auch beachtet werden, dass das Teilen der Summe durch (n-1) anstelle von n dies zu einer Standardabweichung macht
Neil
3
Um die SQL-Methode sorgfältiger zu replizieren, habe ich this IEnumerable<double?> valuesund geändert val in values.Where(val => val != null). Ich werde auch bemerken, dass diese Methode (Welfords Methode) genauer und schneller ist als die obige Methode.
Andrew Mao
2
Ich habe Ihre Antwort bearbeitet, um zu verdeutlichen, dass Sie die Standardabweichung der Stichprobe berechnen , nicht die normale Standardabweichung .
CodesInChaos
31

Dies wandelt die Antwort von David Clarke in eine Erweiterung um, die der gleichen Form wie die anderen aggregierten LINQ-Funktionen wie Average folgt.

Verwendung wäre: 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; 

    }
} 
Will Mathies
quelle
1
Beachten Sie, dass Average/ Min/ Max/ etc Überladungen mit und ohne Auswahlfunktionen haben. Sie haben auch Überlastungen für integrale Typen, Schwimmer usw.
Drew Noakes
5
var stddev = Math.Sqrt(data.Average(z=>z*z)-Math.Pow(data.Average(),2));
Vitas
quelle
2

Die Antwort von Dynamis lautet direkt auf den Punkt (und 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);
    }

Bearbeiten 2020-08-27:

Ich habe @ David Clarke Kommentare genommen, um einige Leistungstests durchzuführen, und dies sind die Ergebnisse:

    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;
    }

Ich habe dies mit einer Liste von einer Million zufälligen Doppeln getestet.
Die ursprüngliche Implementierung hatte eine Laufzeit von ~ 48 ms.
Die leistungsoptimierte Implementierung hat 2-3 ms.
Dies ist also eine signifikante Verbesserung.

Einige interessante Details:
Math.Pow loszuwerden bringt einen Schub von 33ms!
Liste statt IEnumerable 6ms
manuell Durchschnittliche Berechnung 4ms
For-Schleifen anstelle von ForEach-Schleifen 2ms
Array statt Liste bringt nur eine Verbesserung von ~ 2%, also habe ich dies übersprungen,
indem ich einfach statt doppelt verwendet habe, bringt nichts

Eine weitere Senkung des Codes und die Verwendung von goto (ja, GOTO ... habe dies seit dem Assembler der 90er Jahre nicht mehr verwendet ...) anstelle von for-Schleifen zahlt sich nicht aus, Gott sei Dank!

Ich habe auch parallele Berechnungen getestet, dies ist bei Listen> 200.000 Elementen sinnvoll. Es scheint, dass Hardware und Software viel initialisiert werden müssen und dies ist für kleine Listen kontraproduktiv.

Alle Tests wurden zweimal hintereinander ausgeführt, um die Aufwärmzeit zu verringern.

Ernst Greiner
quelle
Beachten Sie dies mehrere Durchgänge durch die Daten macht bei der Bewertung Count(), Average()und Sum(). Dies ist für kleine Werte von countin Ordnung, kann jedoch die Leistung beeinträchtigen, wenn sie countgroß sind.
David Clarke
@ David, die einfachste Lösung wäre meiner Meinung nach, die Signatur durch zu ersetzen (this IList<double> values). Leistungstests würden die Auswirkungen zeigen und wie viele Elemente einen signifikanten Unterschied machen
Ernst Greiner
Ja , das löst nicht das Problem - diese Erweiterungsmethoden ( Count, Average, Sum) jeweils Iterierte die Sammlung , so dass Sie immer noch drei volle Iterationen haben ein Ergebnis zu erzeugen.
David Clarke
0
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)))
}
duc14s
quelle
Beachten Sie, dass dies immer noch mehrere Durchgänge durch die Daten macht - ok, wenn ein kleiner Datensatz, aber nicht gut für große Werte von count.
David Clarke
0

Einfache 4 Zeilen, ich habe eine Liste von Doppel verwendet, aber man könnte verwenden 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;
}
Baddack
quelle