Вычисление взвешенного среднего с помощью LINQ
Моя цель - получить средневзвешенное значение из одной таблицы на основе первичного ключа других таблиц.
Пример данных:
Таблица1
Key WEIGHTED_AVERAGE
0200 0
Table2
ForeignKey Length Value
0200 105 52
0200 105 60
0200 105 54
0200 105 -1
0200 47 55
Мне нужно получить средневзвешенное значение, основанное на длине сегмента, и мне нужно игнорировать значения -1. Я знаю, как это сделать в SQL, но моя цель - сделать это в LINQ. В 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;
Я все еще довольно новичок в LINQ, и мне трудно понять, как я буду это переводить. Средневзвешенное значение результата должно составлять примерно 55,3. Спасибо.
Ответы
Ответ 1
Я делаю это достаточно, чтобы создать метод расширения для 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");
}
После получения вашего подмножества данных вызов выглядит следующим образом.
double weightedAverage = records.WeightedAverage(x => x.Value, x => x.Length);
Это стало очень удобно, потому что я могу получить средневзвешенное значение для любой группы данных на основе другого поля в пределах одной записи.
Обновление
Теперь я проверяю деление на ноль и бросаю более подробное исключение вместо возврата 0. Позволяет пользователю улавливать исключение и обрабатывать по мере необходимости.
Ответ 2
Если вы уверены, что для каждого внешнего ключа в таблице 2 есть соответствующая запись в таблице 1, вы можете избежать объединения, просто создавая группу.
В этом случае запрос LINQ выглядит следующим образом:
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
Вот как вы можете получить wheighted_average
для определенного 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);
Метод ToList, вызываемый при извлечении записей, заключается в том, чтобы избежать выполнения запроса дважды при агрегировании записей в двух отдельных операциях Sum.
Ответ 3
(Ответ jsmith на ответ выше)
Если вы не хотите перебирать какую-либо коллекцию, вы можете попробовать следующее:
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);