вычислить среднюю функцию нескольких функций - PullRequest
6 голосов
/ 18 февраля 2011

У меня есть несколько упорядоченных списков пар X / Y, и я хочу рассчитать упорядоченный список пар X / Y, представляющий среднее значение этих списков.

Все эти списки (включая «средний список») будут затем нарисованы на графике (см. Пример изображения ниже).

У меня есть несколько проблем:

  1. Различные списки не имеют одинаковое количество значений
  2. Значения X и Y могут увеличиваться, уменьшаться и увеличиваться (и т. Д.) (См. Пример изображения ниже)

Мне нужно реализовать это в C #, хотя я думаю, что это не очень важно для самого алгоритма.

Example of Lines

Извините, я не могу объяснить мою проблему более формально или математически.

РЕДАКТИРОВАТЬ: я заменил термин «функция» на «Список пар X / Y», что не так запутанно.

Ответы [ 5 ]

4 голосов
/ 18 февраля 2011

Я бы использовал метод, предложенный Джастином, с одной настройкой.Он предлагает использовать отображаемую таблицу с дробными индексами, хотя я бы предложил целочисленные индексы.Это может звучать немного математически, но не стыдно читать следующее дважды (мне бы тоже пришлось).Предположим, что точка по индексу i в списке пар A искала самые близкие точки в другом списке B, и эта самая близкая точка находится по индексу j.Чтобы найти ближайшую точку в B к A [i + 1], вы должны рассматривать только точки в B с индексом, равным или большим, чем j.Вероятно, это будет j + 1, но может быть j или j + 2, j + 3 и т. Д., Но никогда не ниже j.Даже если точка, ближайшая к A [i + 1], имеет индекс меньше j, вам все равно не следует использовать эту точку для интерполяции, поскольку это приведет к неожиданному среднему значению и графику.Я возьму момент, чтобы создать пример кода для вас.Я надеюсь, вы видите, что эта оптимизация имеет смысл.

РЕДАКТИРОВАТЬ: При реализации этого я понял, что j не только ограничен снизу (описанным выше методом), но и ограничен сверху.Когда вы пробуете расстояние от A [i + 1] до B [j], B [j + 1], B [j + 2] и т. Д., Вы можете прекратить сравнение, когда расстояние A [i + 1] до B [j +...] перестает уменьшаться.Нет смысла искать дальше в B. Те же рассуждения применимы, как и в случае, когда j ограничен снизу: даже если какая-то точка в другом месте в B будет ближе, это, вероятно, не та точка, с которой вы хотите интерполировать.Это может привести к неожиданному графику, возможно, менее гладкому, чем вы ожидаете.И дополнительным бонусом этой второй границы является улучшенная производительность.Я создал следующий код:

IEnumerable<Tuple<double, double>> Average(List<Tuple<double, double>> A, List<Tuple<double, double>> B)
{
    if (A == null || B == null || A.Any(p => p == null) || B.Any(p => p == null)) throw new ArgumentException();
    Func<double, double> square = d => d * d;//squares its argument
    Func<int, int, double> euclidianDistance = (a, b) => Math.Sqrt(square(A[a].Item1 - B[b].Item1) + square(A[a].Item2 - B[b].Item2));//computes the distance from A[first argument] to B[second argument]

    int previousIndexInB = 0;
    for (int i = 0; i < A.Count; i++)
    {
        double distance = euclidianDistance(i, previousIndexInB);//distance between A[i] and B[j - 1], initially 
        for (int j = previousIndexInB + 1; j < B.Count; j++)
        {
            var distance2 = euclidianDistance(i, j);//distance between A[i] and B[j]
            if (distance2 < distance)//if it's closer than the previously checked point, keep searching. Otherwise stop the search and return an interpolated point.
            {
                distance = distance2;
                previousIndexInB = j;
            }
            else
            {
                break;//don't place the yield return statement here, because that could go wrong at the end of B.
            }
        }
        yield return LinearInterpolation(A[i], B[previousIndexInB]);
    }
}
Tuple<double, double> LinearInterpolation(Tuple<double, double> a, Tuple<double, double> b)
{
    return new Tuple<double, double>((a.Item1 + b.Item1) / 2, (a.Item2 + b.Item2) / 2);
}

Для вашей информации, функция «Среднее» возвращает то же количество интерполированных точек, которые содержит список А, что, вероятно, хорошо, но вы должны подумать об этом для своего конкретногоприложение.Я добавил несколько комментариев, чтобы прояснить некоторые детали, и я описал все аспекты этого кода в тексте выше.Я надеюсь, что это понятно, и в остальном не стесняйтесь задавать вопросы.

ВТОРОЕ РЕДАКТИРОВАНИЕ: Я неправильно прочитал и подумал, что у вас есть только два списка пунктов.Я создал обобщенную функцию вышеупомянутого принятия нескольких списков.Он по-прежнему использует только те принципы, которые описаны выше.

IEnumerable<Tuple<double, double>> Average(List<List<Tuple<double, double>>> data)
{
    if (data == null || data.Count < 2 || data.Any(list => list == null || list.Any(p => p == null))) throw new ArgumentException();
    Func<double, double> square = d => d * d;
    Func<Tuple<double, double>, Tuple<double, double>, double> euclidianDistance = (a, b) => Math.Sqrt(square(a.Item1 - b.Item1) + square(a.Item2 - b.Item2));

    var firstList = data[0];
    for (int i = 0; i < firstList.Count; i++)
    {
        int[] previousIndices = new int[data.Count];//the indices of points which are closest to the previous point firstList[i - 1]. 
        //(or zero if i == 0). This is kept track of per list, except the first list.
        var closests = new Tuple<double, double>[data.Count];//an array of points used for caching, of which the average will be yielded.
        closests[0] = firstList[i];
        for (int listIndex = 1; listIndex < data.Count; listIndex++)
        {
            var list = data[listIndex];
            double distance = euclidianDistance(firstList[i], list[previousIndices[listIndex]]);
            for (int j = previousIndices[listIndex] + 1; j < list.Count; j++)
            {
                var distance2 = euclidianDistance(firstList[i], list[j]);
                if (distance2 < distance)//if it's closer than the previously checked point, keep searching. Otherwise stop the search and return an interpolated point.
                {
                    distance = distance2;
                    previousIndices[listIndex] = j;
                }
                else
                {
                    break;
                }
            }
            closests[listIndex] = list[previousIndices[listIndex]];
        }
        yield return new Tuple<double, double>(closests.Select(p => p.Item1).Average(), closests.Select(p => p.Item2).Average());
    }
}

На самом деле то, что я сделал конкретный случай для двух списков по отдельности, могло бы быть хорошей вещью: это легко объяснить и предлагает шаг до понимания обобщенной версии.Кроме того, квадратный корень может быть удален, так как он не меняет порядок расстояний при сортировке, только длины.

ТРЕТЬЕ РЕДАКТИРОВАНИЕ: В комментариях стало ясно, что может быть ошибка.Я думаю, что нет ничего, кроме упомянутой маленькой ошибки, которая не должна иметь никакого значения, кроме как в конце графиков.В качестве доказательства того, что это действительно работает, это результат этого (пунктирная линия - среднее значение): enter image description here

4 голосов
/ 18 февраля 2011

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

p1(t) = (x1(t), y1(t))
p2(t) = (x2(t), y2(t))
p3(t) = (x3(t), y3(t))

Важнейшая проблема заключается в том, что машины мчатся на разных скоростях , что означает, что p1(10) может быть вдвое большедалеко вниз по гоночной трассе как p2(10).Если вы взяли наивное среднее из этих двух точек, и на трассе между машинами была резкая кривая, среднее значение может быть далеко от трассы.

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

Один из способов сделать это - выбрать самый медленный автомобиль (т. Е. Тот, у которого наибольшее количество образцов).Затем, для каждого образца позиции самого медленного автомобиля, посмотрите на все пути других автомобилей, найдите две самые близкие точки и выберите точку на интерполированной линии, которая является самой близкой к позиции самого медленного автомобиля.Затем усредните эти точки вместе.Как только вы сделаете это для всех образцов медленной машины, у вас будет средний путь.

Я предполагаю, что все машины начинаются и заканчиваются примерно в одинаковых местах;если какой-либо из автомобилей просто проезжает небольшую часть трассы, вам нужно будет добавить еще немного логики, чтобы обнаружить это.


Возможное улучшение (как для производительности, так и для точности), состоит в том, чтобы отслеживатьсамой последней выборки, которую вы используете для каждой машины, и скорости каждой машины (относительная частота выборки).Для вашей самой медленной машины это была бы простая карта: 1 => 1, 2 => 2, 3 => 3, ... Однако для других автомобилей она могла бы быть больше похожа на: 1 => 0,3, 2 => 0,7, 3 => 1,6 (дробные значения обусловлены интерполяцией).Скорость будет обратно пропорциональна изменению номера выборки (например, медленный автомобиль будет иметь скорость 1, а другой автомобиль будет иметь скорость 1 / (1,6-0,7) = 1,11).Тогда вы можете быть уверены, что случайно не вернетесь ни на одну из машин.Вы также можете улучшить скорость вычисления, потому что вам не нужно искать весь набор всех точек на каждом пути;вместо этого вы можете предположить, что следующий семпл будет где-то близко к текущему семплу плюс 1 / скорость.

2 голосов
/ 18 февраля 2011

Поскольку это не y=f(x) функции, возможно, они что-то вроде (x,y)=f(t)?

Если это так, вы можете интерполировать по t и вычислять avg (x) и avg (y) для каждого t.

РЕДАКТИРОВАТЬ Это, конечно, предполагает, что t может быть сделан доступным для вашего кода - так что у вас есть упорядоченный список троек T / X / Y.

0 голосов
/ 18 февраля 2011

например, у вас есть 2 «функции» с

fc1 = { {1,0.3} {2, 0.5} {3, 0.1} }
fc1 = { {1,0.1} {2, 0.8} {3, 0.4} }

Требуется среднее арифметическое (сленг: «среднее») двух функций.Для этого вы просто рассчитываете точечное арифметическое среднее:

fc3 = { {1, (0.3+0.1)/2} ... }

Оптимизация: Если у вас большое количество точек, вы должны сначала преобразовать «упорядоченный список пар X / Y» в матрицу ИЛИ хотя бы хранитьточки по столбцам выглядят так: {0,3, 0,1}, {0,5, 0,8}, {0,1, 0,4}

0 голосов
/ 18 февраля 2011

Есть несколько способов сделать это.Одним из них является объединение всех ваших данных в один набор точек и создание кривой наилучшего соответствия в объединенном наборе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...