С# Вычисление движущейся медианы временных рядов SortedList‹DateTime, double› - повысить производительность?

У меня есть метод, который вычисляет скользящее медианное значение временного ряда. Как и скользящее среднее, он использует фиксированное окно или период (иногда называемый периодом ретроспективного анализа). Если период равен 10, будет создан массив из первых 10 значений (0-9), а затем найдено их среднее значение. Он будет повторять это, увеличивая окно на 1 шаг (теперь значения 1-10) и так далее... отсюда и движущаяся часть этого. Это процесс точно такой же, как скользящее среднее.

Среднее значение находится по формуле:

  1. Сортировка значений массива
  2. Если в массиве нечетное количество значений, берется среднее значение. Медиана отсортированного массива из 5 значений будет третьим значением.
  3. Если в массиве четное количество значений, возьмите два значения по обе стороны от середины и усредните их. Медиана отсортированного массива из 6 значений будет (2-й + 3-й) / 2.

Я создал функцию, которая вычисляет это, заполняя List<double>, вызывая List<>.Sort() и затем находя соответствующие значения.

Вычислительный это правильно, но мне было интересно, есть ли способ улучшить производительность этого расчета. Возможно, путем ручной сортировки double[] вместо использования списка.

Моя реализация выглядит следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Moving_Median_TimeSeries
{
    class Program
    {
        static void Main(string[] args)
        {
            // created a a sample test time series of 10 days
            DateTime Today = DateTime.Now;
            var TimeSeries = new SortedList<DateTime, double>();
            for (int i = 0; i < 10; i++)
                TimeSeries.Add(Today.AddDays(i), i * 10);

            // write out the time series
            Console.WriteLine("Our time series contains...");
            foreach (var item in TimeSeries) 
                Console.WriteLine("   {0}, {1}", item.Key.ToShortDateString(), item.Value);

            // calculate an even period moving median 
            int period = 6;
            var TimeSeries_MovingMedian = MovingMedian(TimeSeries, period);

            // write out the result of the calculation
            Console.WriteLine("\nThe moving median time series of {0} periods contains...", period);
            foreach (var item in TimeSeries_MovingMedian)
                Console.WriteLine("   {0}, {1}", item.Key.ToShortDateString(), item.Value);

            // calculate an odd period moving median 
            int period2 = 5;
            var TimeSeries_MovingMedian2 = MovingMedian(TimeSeries, period);

            // write out the result of the calculation
            Console.WriteLine("\nThe moving median time series of {0} periods contains...", period2);
            foreach (var item in TimeSeries_MovingMedian2)
                Console.WriteLine("   {0}, {1}", item.Key.ToShortDateString(), item.Value);
        }

        public static SortedList<DateTime, double> MovingMedian(SortedList<DateTime, double> TimeSeries, int period)
        {
            var result = new SortedList<DateTime, double>();

            for (int i = 0; i < TimeSeries.Count(); i++)
            {
                if (i >= period - 1)
                {
                    // add all of the values used in the calc to a list... 
                    var values = new List<double>();
                    for (int x = i; x > i - period; x--)
                        values.Add(TimeSeries.Values[x]);

                    // ... and then sort the list <- there might be a better way than this
                    values.Sort();

                    // If there is an even number of values in the array (example 10 values), take the two mid values
                    // and average them.  i.e. 10 values = (5th value + 6th value) / 2. 
                    double median;
                    if (period % 2 == 0) // is any even number
                        median = (values[(int)(period / 2)] + values[(int)(period / 2 - 1)]) / 2;
                    else // is an odd period
                    // Median equals the middle value of the sorted array, if there is an odd number of values in the array
                        median = values[(int)(period / 2 + 0.5)];

                    result.Add(TimeSeries.Keys[i], median);
                }
            }
            return result;
        }

    }
}

person Andre P.    schedule 09.03.2011    source источник
comment
Оптимизируйте только тогда, когда вам действительно нужна оптимизация. Кроме того, единственное, что я вижу, это то, что вы можете создать список значений вне цикла с желаемой емкостью, но я не думаю, что это даст вам гораздо лучшую скорость. просто выглядит лучше.   -  person ba__friend    schedule 09.03.2011


Ответы (2)


может быть лучший способ, чем этот

Вы правы в этом - вам не нужно сортировать весь список, если все, что вам нужно, это медиана. Следуйте ссылкам на этой странице Википедии, чтобы узнать больше.

person AakashM    schedule 09.03.2011

Для списка из N элементов и периода P ваш алгоритм, который повторно сортирует список для каждого элемента, равен O (N * P lgP). Существует алгоритм O(N * lg P), который использует 2 кучи. .

Он использует минимальную кучу, которая содержит P/2 элементов выше медианы, и максимальную кучу с элементами P-P/2 меньше или равными ей. Всякий раз, когда вы получаете новый элемент данных, замените самый старый элемент новым, а затем выполните просеивание вверх или вниз, чтобы переместить его в нужное место. Если новый элемент достигает корня одной из куч, сравните его с корнем другой и при необходимости замените и просейте. Для нечетного P медиана находится в корне максимальной кучи. Для четного P это среднее значение обоих корней.

В этом вопросе есть реализация c. Одна сложная часть в реализации — эффективное отслеживание самого старого элемента. Накладные расходы в этой части могут сделать прирост скорости незначительным для малых P.

person AShelly    schedule 20.06.2011