2010-04-26 11 views
12

Mein Ziel ist es, einen gewichteten Durchschnitt aus einer Tabelle basierend auf einem anderen Tabellenprimärschlüssel zu erhalten.Berechnen des gewichteten Durchschnitts mit LINQ

Beispieldaten:

Table1

Key  WEIGHTED_AVERAGE 

0200 0 

Table2

ForeignKey Length Value 
0200   105  52 
0200   105  60 
0200   105  54 
0200   105  -1 
0200   47  55 

Ich brauche einen gewichteten Mittelwert zu erhalten, basierend auf der Länge eines Segments und ich brauche zu ignorieren Werte von -1 . Ich weiß, wie man das in SQL macht, aber mein Ziel ist, dies in LINQ zu tun. Es sieht so etwas wie dies in SQL:

SELECT Sum(t2.Value*t2.Length)/Sum(t2.Length) AS WEIGHTED_AVERAGE 
FROM Table1 t1, Table2 t2 
WHERE t2.Value <> -1 
AND t2.ForeignKey = t1.Key; 

Ich bin immer noch ziemlich neu in LINQ, und eine harte Zeit, herauszufinden, wie ich das übersetzen würde. Der ergebnisgewichtete Durchschnitt sollte bei etwa 55,3 liegen. Vielen Dank.

Antwort

33

Ich tue dies genug, dass ich ein erstellt Erweiterungsmethode für LINQ.

public static double WeightedAverage<T>(this IEnumerable<T> records, Func<T, double> value, Func<T, double> weight) 
{ 
    double weightedValueSum = records.Sum(x => value(x) * weight(x)); 
    double weightSum = records.Sum(x => weight(x)); 

    if (weightSum != 0) 
     return weightedValueSum/weightSum; 
    else 
     throw new DivideByZeroException("Your message here"); 
} 

Nachdem Sie Ihre Teilmenge der Daten erhalten haben, sieht der Anruf so aus.

Dies ist extrem praktisch geworden, weil ich einen gewichteten Durchschnitt einer beliebigen Gruppe von Daten basierend auf einem anderen Feld innerhalb des gleichen Datensatzes erhalten kann.

aktualisieren

jetzt Division durch Null I überprüfen und eine detailliertere Ausnahme werfen statt 0 zurückzukehren Ermöglicht es dem Benutzer, die Ausnahme zu fangen und nach Bedarf zu behandeln.

+1

Danke, sehr hilfreich. Ich zog so dass dies ein Einzeiler ... public static float WeightedAverage (diese IEnumerable Artikel, Func Wert, Func Gewicht) { return items.Sum (item => Wert (Artikel) * Gewicht (Artikel))/items.Sum (Gewicht); } – josefresno

+2

Ich musste hinzufügen "Wenn WeightedSum.AlmostZero() 0 zurückgeben" nach den Berechnungen zum Schutz vor Division durch Null, wenn alle Gewichte und oder alle Werte Null sind. AlmostZero ist eine Erweiterungsfunktion, die prüft, ob ein Double gleich Null ist. – derdo

4

Wenn Sie sicher sind, dass für jeden Fremdschlüssel in Tabelle2 ein entsprechender Datensatz in Tabelle1 vorhanden ist, können Sie den Join vermeiden, indem Sie einfach eine Gruppe erstellen.

In diesem Fall ist die LINQ-Abfrage wie folgt aus:

IEnumerable<int> wheighted_averages = 
    from record in Table2 
    where record.PCR != -1 
    group record by record.ForeignKey into bucket 
    select bucket.Sum(record => record.PCR * record.Length)/
     bucket.Sum(record => record.Length); 

UPDATE

Dies ist, wie Sie die wheighted_average für einen bestimmten foreign_key bekommen kann.

IEnumerable<Record> records = 
    (from record in Table2 
    where record.ForeignKey == foreign_key 
    where record.PCR != -1 
    select record).ToList(); 
int wheighted_average = records.Sum(record => record.PCR * record.Length)/
    records.Sum(record => record.Length); 

Die ToList Methode aufgerufen, wenn die Datensätze zu holen, ist zweimal die Ausführung der Abfrage zu vermeiden, während die Datensätze in den beiden getrennten Summenoperationen aggregieren.

+0

Dies gibt einen Wert für jeden anderen ForeignKey zurück. Wenn Sie nur den gewichteten Durchschnitt für einen bestimmten und nur ForeignKey möchten, können Sie die GroupBy vermeiden und nur die Datensätze mit dem gewünschten Fremdschlüssel filtern und anschließend die Aggregationsvorgänge durchführen. Ich werde meine Antwort bearbeiten, um Ihnen zu zeigen, wie. – Fede

1

(jsmith Kommentar zu der Antwort Beantwortung oben)

Wenn Sie durch irgendeine Sammlung Zyklus nicht wünschen, können Sie versuchen, die folgenden:

var filteredList = Table2.Where(x => x.PCR != -1) 
.Join(Table1, x => x.ForeignKey, y => y.Key, (x, y) => new { x.PCR, x.Length }); 

int weightedAvg = filteredList.Sum(x => x.PCR * x.Length) 
    /filteredList.Sum(x => x.Length); 
+0

Nur damit Sie wissen, geht meine Lösung davon aus, dass Sie den gewichteten Durchschnitt über eine Reihe von Zeilen berechnen möchten, deren Fremdschlüssel mit dem Schlüsselwert einer Zeile in der ersten Tabelle übereinstimmt. Fede's Lösung wird Ihnen Zeilen für einen bestimmten Fremdschlüssel liefern. Also, wählen Sie die passende Lösung. –

Verwandte Themen