Вычисление взвешенного среднего с LINQ

Моя цель состоит в том, чтобы получить взвешенное среднее от одной таблицы, на основе другого первичного ключа таблиц.

Данные в качестве примера:

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

Я должен получить взвешенное среднее на основе длины сегмента, и я должен проигнорировать значения-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.Спасибо.

20
задан jsmith 1 December 2016 в 16:09
поделиться

3 ответа

Я сделал это достаточно, чтобы создать расширение метод для 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. Позволяет пользователю перехватывать исключение и обрабатывать его по мере необходимости.

55
ответ дан 29 November 2019 в 22:54
поделиться

(Отвечая на комментарий 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);
1
ответ дан 29 November 2019 в 22:54
поделиться

Если вы уверены, что для каждого внешнего ключа в таблице 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);

ОБНОВЛЕНИЕ

Таким образом вы можете получить 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.

4
ответ дан 29 November 2019 в 22:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: