2010-04-26 102 views
12

Mi objetivo es obtener un promedio ponderado de una tabla, basado en otra clave principal de tablas.Cálculo del promedio ponderado con LINQ

Ejemplo de datos:

Tabla1

Key  WEIGHTED_AVERAGE 

0200 0 

Tabla2

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

que necesito para obtener una media ponderada en base a la longitud de un segmento y necesito ignorar los valores de -1 . Sé cómo hacer esto en SQL, pero mi objetivo es hacer esto en LINQ. Se ve algo como esto en 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; 

todavía soy bastante nuevo en LINQ, y tener un tiempo difícil averiguar cómo iba a traducir esto. El promedio ponderado de resultados debería llegar a aproximadamente 55.3. Gracias.

Respuesta

33

hago esto basta con que he creado una método de extensión para 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"); 
} 

Después de obtener su subconjunto de datos, la llamada se ve así.

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

Esto ha llegado a ser extremadamente útil porque puedo obtener un promedio ponderado de cualquier grupo de datos en base a otro campo en el mismo registro.

actualización

ahora puedo comprobar la división por cero y lanzar una excepción más detallada en lugar de devolver 0. Permite al usuario detectar la excepción y manejar según sea necesario.

+1

Gracias, muy útil. Acabé haciendo de este un un trazador de líneas ... flotación pública WeightedAverage estática (IEnumerable esta artículos, Func valor, Func peso) { retorno items.Sum (item => valor (elemento) * Peso (punto))/items.Sum (peso); } – josefresno

+2

Tuve que agregar "If weightedSum.AlmostZero() return 0" después de los cálculos para proteger contra la división por cero cuando todos los pesos y/o todos los valores son cero. AlmostZero es una función de extensión que comprueba si un doble es cero. – derdo

4

Si está seguro de que para cada clave foránea en la Tabla 2 hay un registro correspondiente en la Tabla 1, entonces puede evitar la unión simplemente haciendo un grupo.

En ese caso, la consulta LINQ es así:

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

ACTUALIZACIÓN

Así es como se puede obtener el wheighted_average para una específica 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); 

El método ToList llamada cuando ir a buscar los registros, es evitar la ejecución de la consulta dos veces, mientras que la agregación de los registros en las dos operaciones Sum separadas.

+0

Esto devuelve un valor para cada ForeignKey diferente. Si solo desea el promedio de duración de una clave específica y solo ForeignKey, puede evitar GroupBy y simplemente filtrar los registros con la clave externa deseada y luego realizar las operaciones de agregación. Editaré mi respuesta para mostrarte cómo. – Fede

1

(Contestación comentario de jpérez a la respuesta anterior)

Si no desea desplazarse a través de alguna colección, puede intentar lo siguiente:

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

Para que lo sepas, mi solución asume que querías calcular el promedio ponderado sobre un conjunto de filas cuya clave externa coincide con el valor clave de cualquier fila en la primera tabla. La solución de Fede obtendrá filas para una clave externa específica. Por lo tanto, siéntase libre de elegir la solución más adecuada. –

Cuestiones relacionadas