2010-04-26 11 views
12

Il mio obiettivo è ottenere una media ponderata da una tabella, basata su un'altra chiave primaria di tabelle.Calcolo della media ponderata con LINQ

Esempio Dati:

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 

ho bisogno di ottenere una media ponderata in base alla lunghezza di un segmento e ho bisogno di ignorare i valori di -1 . So come farlo in SQL, ma il mio obiettivo è farlo in LINQ. Sembra qualcosa di simile 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; 

Sono ancora abbastanza nuovo per LINQ, e avendo un periodo difficile capire come avrei tradurre questo testo. La media ponderata dei risultati dovrebbe attestarsi a circa 55,3. Grazie.

risposta

33

faccio questo abbastanza che ho creato un metodo di estensione per 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"); 
} 

Dopo aver ottenuto il sottoinsieme di dati, la chiamata è simile a questa.

double weightedAverage = records.WeightedAverage(x => x.Value, x => x.Length); 

Questo è diventato estremamente utile perché è possibile ottenere una media ponderata di qualsiasi gruppo di dati in base a un altro campo all'interno dello stesso record.

Aggiornamento

ora verificare la presenza di divisione per zero e un'eccezione più dettagliata invece di restituire 0. permette all'utente di catturare l'eccezione e maniglia a seconda delle necessità.

+1

Grazie, molto utile. Finii per rendere questo un uno di linea ... pubblica float WeightedAverage static (questo IEnumerable articoli, Func valore, Func peso) { ritorno items.Sum (item => valore (voce) * Peso (articolo))/items.Sum (peso); } – josefresno

+2

Ho dovuto aggiungere "Se weightedSum.AlmostZero() restituisce 0" dopo i calcoli per proteggere dalla divisione per zero quando tutti i pesi e/o tutti i valori sono zero. AlmostZero è una funzione di estensione che verifica se un doppio è zero. – derdo

4

Se si è certi che per ciascuna chiave esterna in Tabella2 è presente un record corrispondente in Tabella1, è possibile evitare il join effettuando un solo gruppo.

In questo caso, la query LINQ è come questo:

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

In questo modo è possibile ottenere il wheighted_average per una specifica foreign_key.

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

Procedimento ToList chiamato quando il recupero dei record, è quello di evitare l'esecuzione della query due volte mentre aggregando i record nelle due operazioni distinte Sum.

+0

Questo restituisce un valore per ogni ForeignKey diverso. Se si desidera solo la media stimata per uno specifico e solo ForeignKey, è possibile evitare GroupBy e filtrare semplicemente i record con la chiave esterna desiderata ed eseguire successivamente le operazioni di aggregazione. Modificherò la mia risposta per mostrarti come. – Fede

1

(Risposta a commento di jsmith alla risposta precedente)

Se non si desidera passare qualche raccolta, si può provare il seguente:

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

Solo così sai, la mia soluzione presuppone che tu voglia calcolare la media ponderata su un insieme di righe la cui chiave esterna corrisponde al valore chiave di qualsiasi riga nella prima tabella. La soluzione di Fede ti fornirà le righe per una chiave straniera specifica. Quindi, sentiti libero di scegliere quale sia la soluzione più appropriata. –