Chris Maunder Ответов: 4

Задача кодирования: скользящее среднее и стандартное отклонение


Учитывая поток данных с плавающей запятой, который может никогда не заканчиваться (подумайте о речи политика, преобразованной в двоичный код и приведенной к 4-байтовым поплавкам), вычислите скользящее среднее и стандартное отклонение. Некоторую информацию об этом можно найти по адресу Эффективное и точное стандартное отклонение качения-внимательный программист[^]. Ваша задача состоит в том, чтобы кодировать это эффективно. Или смехотворно. Ваш звонок.

Прошлая неделя:

Первое: огромное спасибо Гриффу за его головоломку. Очень милый.

Грэм в очередной раз прибил его в вызове на прошлой неделе. Нужно ли давать ему фору? Может быть, что-то вроде "ваш код не может содержать букву Е или знак+. Звучит справедливо?

Bryian Tan

Да пожалуйста :), не-а, он просто очень талантливый чувак.

Graeme_Grant

Спасибо за добрые слова, Брайан. Формального образования у меня не было, все самоучки. :)

Maciej Los

Так что вы заслужили за громкие бравады!

Graeme_Grant

Спасибо :)

Patrice T

Как насчет того, чтобы установить ограничение на количество раз, когда кто-то может выиграть вызов за определенный период времени ?
Например, ограничение до 6 раз в год. Когда один выигрывает, он не может выиграть снова в течение следующих 2 месяцев.

Jon McKee

Просто ограничьте его одним решением на человека =P давайте посмотрим, сможет ли Грэм когерентно вместить все эти решения в один пост, хе-хе < 3

Patrice T

Хорошая идея: только 1 решение.

Graeme_Grant

Уже спланировано. ;)

Graeme_Grant

На прошлой неделе для вызова Гриффа я представил одно решение, один язык...

Graeme_Grant

Просто делать. ;)

Jon McKee

Nice :thumbsup: сегодня утром я принял решение о решении WPF, над которым буду работать сегодня вечером :)

Graeme_Grant

Итак, мое решение WPF вдохновило вас? ;)

Jon McKee

На самом деле я уже планировал использовать WPF для своего решения, прежде чем увидел ваше :) хотел проект с графикой.

Graeme_Grant

о, действительно...

PIEBALDconsult

https://www.codeproject.com/kb/recipes/simplemovingaverage.aspx

Patrice T

Привет Крис,
А как насчет вторичной цены за интересные / оригинальные решения ?
Цена может быть просто принятым решением.

Graeme_Grant

Крис,
Вот предложение: Если вы предложили знак признания [для страницы профиля пользователя] для еженедельного победителя, я думаю, что у вас будет больше участников...

Maciej Los

Отличная идея!

Graeme_Grant

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

4 Ответов

Рейтинг:
2

Richard Deeming

Вот версия C#, которую я обрюхатил некоторое время назад:

public struct StandardDeviationData
{
    private readonly double _sum;
    private readonly double _sumOfSquares;

    private StandardDeviationData(uint count, double sum, double sumOfSquares)
    {
        Count = count;
        _sum = sum;
        _sumOfSquares = sumOfSquares;
    }

    public uint Count { get; }
    
    public double Average => (Count == 0) ? double.NaN : _sum / Count;
    
    // The uncorrected sample standard deviation:
    // https://en.wikipedia.org/wiki/Standard_deviation#Uncorrected_sample_standard_deviation
    public double UncorrectedSampleStandardDeviation
    {
        get
        {
            if (Count == 0) return double.NaN;

            var diff = _sumOfSquares - (_sum * _sum / Count);
            return Math.Sqrt(diff / Count);
        }
    }

    // The corrected sample standard deviation:
    // https://en.wikipedia.org/wiki/Standard_deviation#Corrected_sample_standard_deviation
    public double CorrectedSampleStandardDeviation
    {
        get
        {
            if (Count < 2) return double.NaN;

            var diff = _sumOfSquares - (_sum * _sum / Count);
            return Math.Sqrt(diff / (Count - 1));
        }
    }

    public StandardDeviationData Add(double value)
    {
        return new StandardDeviationData(checked(Count + 1), _sum + value, _sumOfSquares + (value * value));
    }

    public static StandardDeviationData operator +(StandardDeviationData data, double value)
    {
        return data.Add(value);
    }

    public static StandardDeviationData Create(IReadOnlyCollection<double> values)
    {
        double sum = 0, sumOfSquares = 0;
        foreach (double x in values)
        {
            sum += x;
            sumOfSquares += x * x;
        }
        
        return new StandardDeviationData((uint)values.Count, sum, sumOfSquares);
    }
}

public static class Extensions
{
    private static IEnumerable<StandardDeviationData> RollingStandardDeviationIterator(IEnumerable<double> source)
    {
        var result = default(StandardDeviationData);
        foreach (double value in source)
        {
            result += value;
            yield return result;
        }
    }

    public static IEnumerable<StandardDeviationData> RollingStandardDeviation(this IEnumerable<double> source)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        return RollingStandardDeviationIterator(source);
    }
    
    public static IEnumerable<StandardDeviationData> RollingStandardDeviation(this IEnumerable<double?> source)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        return RollingStandardDeviationIterator(source.Where(n => n != null).Select(n => n.GetValueOrDefault()));
    }
    
    private static IEnumerable<StandardDeviationData> RollingStandardDeviationIterator(IEnumerable<double> source, int windowSize)
    {
        var window = new Queue<double>(windowSize);
        
        foreach (double value in source)
        {
            if (window.Count == windowSize)
            {
                window.Dequeue();
            }
            
            window.Enqueue(value);
            yield return StandardDeviationData.Create(window);
        }
    }

    public static IEnumerable<StandardDeviationData> RollingStandardDeviation(this IEnumerable<double> source, int windowSize)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (windowSize < 2) throw new ArgumentOutOfRangeException(nameof(windowSize));
        return RollingStandardDeviationIterator(source, windowSize);
    }
    
    public static IEnumerable<StandardDeviationData> RollingStandardDeviation(this IEnumerable<double?> source, int windowSize)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (windowSize < 2) throw new ArgumentOutOfRangeException(nameof(windowSize));
        return RollingStandardDeviationIterator(source.Where(n => n != null).Select(n => n.GetValueOrDefault()), windowSize);
    }
}

РЕДАКТИРОВАТЬ: Теперь обновлено для поддержки скользящего окна. И да, это проходит Испытание Гарольда[^] из гостиной. :)


Slacker007

Почему вы используете структуру вместо класса? Просто любопытно. Обычно я не помещаю код реализации в свои структуры, но это только я.

Richard Deeming

Чтобы избежать накладных расходов на создание нового экземпляра класса для каждого элемента в исходном коде, просто удерживайте два doubleS и int.

Это 20 байт, так что это технически над рекомендуемый максимальный размер[^] из 16 байт. Но только что.

И struct просто мне показалось, что это правильный выбор. :)

OriginalGriff

В дополнение к тому, что сказал Ричард, это может усложниться:
https://www.codeproject.com/Articles/728836/Using-struct-and-class-whats-that-all-about
Пробует объяснить.

Jon McKee

Хорошо сказано в этой статье :большой палец вверх:

Graeme_Grant

Мне нравится то, что вы сделали. Бегущие калькуляторы выглядят хорошо однако когда я запускаю его оконные результаты не соответствуют тесту Гарольда для меня:

[править] удалить демонстрационные данные.

Richard Deeming

Эти результаты выглядят правильными. Как ты думаешь, где это не соответствует тесту Гарольда?

- С окном, скажем, в 5." - Итак," оконный " тест.
"Каково среднее значение, когда 1E30f покидает окно?" - то есть: этот шестой результат вперед.
"Надеюсь, 1, ..." - так оно и есть.

Какие ценности вы ожидали увидеть?

Graeme_Grant

Я вижу 0s как пред выводом выше, а не 1s.

Richard Deeming

Ты видишь 0 для стандартного отклонения, которое правильно - все пять входов имеют одинаковое значение, поэтому отклонения нет.

В среднем это 1- согласно сообщению Гарольда.

Graeme_Grant

Извиняюсь... Я исправляюсь. Спасибо, что указал на это, Ричард.

H2O-au

Похоже, вы используете формулу суммы квадратов для дисперсии? Будьте осторожны с этим, это имеет тенденцию быть численно неустойчив из-за катастрофической отмены... Обнаружил это сам на собственном горьком опыте несколько месяцев назад!

Jeremy Falcon

Ты его обрюхатил? Как будто хочешь сказать, что твой код забеременел?

Richard Deeming

Ха! :)

Нет, "обрюхатить" как в, "скинули в спешке".

Рейтинг:
1

H2O-au

Ура, обожаю перечисления! :Д

Вот мой подход, немного отличающийся в нескольких отношениях:

  • Я никогда не был поклонником скользящих средних, которые смотрят только назад, поэтому мой использует окно сосредоточенный вместо этого на текущем элементе.
  • Я унаследовал его от обычного оконщика. К сожалению, это означает, что циклы статистики повторяются чаще (порядок: input-length times window-length vs just input-length). Однако я предпочитаю его, потому что он помогает избежать кумулятивных-числовых-ошибок-катастроф, таких как Испытание Гарольда.
  • Я решил пойти с методом Уэлфорда для стандартного отклонения, предпочитая повторять только один раз с помощью операции деления, а не повторять IEnumerable (что, вероятно, было бы не так уж плохо в нашем случае).
Использование:
IEnumerable<double> series = Enumerable.Range(0, 10).Select(a => (double)a);
var stats = series.RollingStats(n);
Кадрирование:
/// <summary>
/// Enumerates this series and returns some basic stats.
/// </summary>
/// <param name="sourceSeries">The series to analyse</param>
public static SeriesStats GetStats(this IEnumerable<double> sourceSeries)
{
    return new SeriesStats(sourceSeries);
}

/// <summary>
/// Returns a rolling window around an element.
/// </summary>
/// <param name="elementsAround">
/// The number of elements to return before or after the current element.
/// Must be greater than or equal to zero.
/// Total number of elements in the window is between elementsAround + 1
/// and elementsAround * 2 + 1.
/// </param>
public static IEnumerable<IEnumerable<T>> RollingWindow<T>(
    this IEnumerable<T> source, int elementsAround)
{
    if (elementsAround < 0)
        throw new ArgumentOutOfRangeException(nameof(elementsAround));

    var preq = new Queue<T>(elementsAround + 1);
    var postq = new Queue<T>(elementsAround + 1);
    var fullq = preq.Concat(postq);
    var e = source.GetEnumerator();

    // preload elements into post-value queue
    for (int i = 0; i < elementsAround; i++)
    {
        if (!e.MoveNext()) break;
        postq.Enqueue(e.Current); // add look-ahead to post buffer
    }

    // send 
    while (e.MoveNext())
    {
        postq.Enqueue(e.Current); // add look-ahead to post buffer
        preq.Enqueue(postq.Dequeue()); // move the central value to pre buffer
        if (preq.Count > elementsAround + 1)
            preq.Dequeue(); // trim lookback
        yield return fullq;
    }

    // dump lookback
    while (postq.Count > 0)
    {
        preq.Enqueue(postq.Dequeue()); // move the central value to pre buffer
        if (preq.Count > elementsAround + 1)
            preq.Dequeue(); // trim lookback
        yield return fullq;
    }
}

/// <summary>
/// Returns stats for a rolling window around an element.
/// </summary>
/// <param name="elementsAround">
/// The number of elements to return before or after the current element.
/// Must be greater than or equal to zero.
/// Total number of elements in the window is between elementsAround + 1
/// and elementsAround * 2 + 1.
/// </param>
public static IEnumerable<SeriesStats> RollingStats(
    this IEnumerable<double> source, int elementsAround)
{
    return source.RollingWindow(elementsAround).Select(a => a.GetStats());
}
Статистика:
/// <summary>
/// Some basic stats about a series.
/// </summary>
public struct SeriesStats
{
    /// <summary>
    /// Enumerates a series and prepares some basic stats.
    /// </summary>
    /// <param name="sourceSeries">The series to analyse</param>
    public SeriesStats(IEnumerable<double> sourceSeries)
    {
        var n = 0;
        var sumx = 0d;
        var sumx2 = 0d;
        var m1 = 0d;
        var m2 = 0d;
        foreach (var x in sourceSeries)
        {
            n++;
            sumx += x;
            sumx2 += x * x;

            // Welford's method for stdev
            var d1 = (x - m1);
            m1 += d1 / n;
            var d2 = x - m1;
            m2 += d1 * d2;
        }
        this.Count = n;
        this.Variance = n < 2 ? double.NaN : m2 / (n - 1);
        this.Sum = sumx;
        this.SumSquares = sumx2;
    }

    /// <summary>
    /// The number of elements in the source series
    /// </summary>
    public int Count { get; private set; }

    /// <summary>
    /// The sum of the elements in the source series
    /// </summary>
    public double Sum { get; private set; }

    /// <summary>
    /// The sum of the squares of the elements in the source series
    /// </summary>
    public double SumSquares { get; private set; }

    /// <summary>
    /// The (estimated) variance of the elements in the source series
    /// </summary>
    public double Variance { get; private set; }

    /// <summary>
    /// The average of the elements in the source series
    /// </summary>
    public double Average { get { return Sum / Count; } }

    /// <summary>
    /// The (estimated) standard deviation
    /// of the elements in the source series
    /// </summary>
    public double StandardDeviation { get { return Math.Sqrt(Variance); } }
}


Graeme_Grant

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

Вот набор данных:

Running

  avg=9.4799               std=NaN
  avg=9.18795              std=0.412879649534825
  avg=8.99436666666667     std=0.444587812848411
  avg=8.661525             std=0.758225900265437
  avg=8.3624               std=0.937314629673516
  avg=7.94515              std=1.32190481767788
  avg=7.57648571428571     std=1.5516401359299
  avg=7.2305125            std=1.73816737270486
  avg=6.7323               std=2.20850941700505
  avg=6.29686              std=2.4963268612548
  avg=5.90666363636364     std=2.69875309032624
  avg=5.545375             std=2.86138195509697
  avg=5.23908461538462     std=2.95377620424155
  avg=4.97297142857143     std=3.00750445883129
  avg=4.73782              std=3.03783617450692
  avg=4.50514375           std=3.07886838172052


Windowed

  avg=1.51895              std=0.316239660700552
  avg=1.64165714285714     std=0.434440908360554
  avg=1.779775             std=0.560703613456215
  avg=2.11632222222222     std=1.13774613576335
  avg=2.44114              std=1.48516078979872
  avg=2.75184545454545     std=1.74557984312585
  avg=3.31101818181818     std=2.08562508031438
  avg=3.87622727272727     std=2.35466726103325
  avg=4.52110909090909     std=2.60133818887685
  avg=5.18769090909091     std=2.70508354120702
  avg=5.90666363636364     std=2.69875309032624
  avg=6.29686              std=2.4963268612548
  avg=6.7323               std=2.20850941700505
  avg=7.2305125            std=1.73816737270486
  avg=7.57648571428571     std=1.5516401359299
  avg=7.94515              std=1.32190481767788
И вот что я ожидал увидеть:
Running

  avg=1.015                std=NaN
  avg=1.23035              std=0.304550890657045
  avg=1.32473333333333     std=0.270370603678236
  avg=1.38445              std=0.250993685179527
  avg=1.4218               std=0.232859367430214
  avg=1.51895              std=0.31623966070055
  avg=1.64165714285714     std=0.434440908360553
  avg=1.779775             std=0.560703613456214
  avg=2.11632222222222     std=1.13774613576335
  avg=2.44114              std=1.48516078979872
  avg=2.75184545454545     std=1.74557984312585
  avg=3.11968333333333     std=2.09611569243222
  avg=3.46916923076923     std=2.36968022082608
  avg=3.83617142857143     std=2.65877796956206
  avg=4.17349333333333     std=2.87592690585306
  avg=4.50514375           std=3.07886838172052


Windowed

  avg=1.015                std=NaN
  avg=1.23035              std=0.304550890657045
  avg=1.32473333333333     std=0.270370603678236
  avg=1.38445              std=0.250993685179527
  avg=1.4218               std=0.232859367430214
  avg=1.61974              std=0.22095185674712
  avg=1.80618              std=0.376163590742112
  avg=2.0528               std=0.514931369213414
  avg=2.70182              std=1.25600065963359
  avg=3.46048              std=1.52018821597853
  avg=4.23132              std=1.57369314099033
  avg=5.18892              std=1.6200035253048
  avg=6.1722               std=1.20619160584046
  avg=6.9319               std=1.3232502654449
  avg=7.6382               std=1.21558758014386
  avg=8.3624               std=0.937314629673514

Graeme_Grant

Вот используемый тестовый код:

class Program
{
    static void Main(string[] args)
    {
        double[] data = { 1.0150, 1.4457, 1.5135, 1.5636, 1.5712, 2.0047, 2.3779, 2.7466, 4.8087, 5.3645, 5.8589, 7.1659, 7.6630, 8.6072, 8.8960, 9.4799 };
        IEnumerable<double> series = data.Select(a => (double)a);

        Console.WriteLine("Running\r\n");
        for (int i = 0; i < data.Length; i++)
        {
            var stats = series.RollingStats(i).ToList().Last();
            Console.WriteLine($"  avg={stats.Average,-20} std={stats.StandardDeviation,-20}");
        }

        Console.WriteLine("\r\n\r\nWindowed\r\n");
        foreach (var stats in series.RollingStats(5))
        {
            Console.WriteLine($"  avg={stats.Average,-20} std={stats.StandardDeviation,-20}");
        }
        Console.ReadKey();
    }
}

H2O-au

Похоже, мы просто ожидаем разных размеров окон! Мой параметр-симметричное полуокно, поэтому для окон общего размера 5 вам нужно вставить 2 (т. е. текущий элемент +/- 2 элемента). Так что попробуй series.RollingStats(2), и вы должны получить то, что ожидаете (смещение на 2 элемента).

Graeme_Grant

Я вижу, что запустить total невозможно. Кроме того, окна могут быть только нечетного размера и 3 или больше.

Кроме того, после изменения в соответствии с рекомендациями значения все еще отсутствуют. Вот новые результаты:Скрыть   скопировать код

Windowed

  avg=1.32473333333333     std=0.270370603678235
  avg=1.38445              std=0.250993685179528
  avg=1.4218               std=0.232859367430215
  avg=1.61974              std=0.22095185674712
  avg=1.80618              std=0.376163590742113
  avg=2.0528               std=0.514931369213413
  avg=2.70182              std=1.25600065963358
  avg=3.46048              std=1.52018821597853
  avg=4.23132              std=1.57369314099033
  avg=5.18892              std=1.62000352530481
  avg=6.1722               std=1.20619160584047
  avg=6.9319               std=1.3232502654449
  avg=7.6382               std=1.21558758014386
  avg=8.3624               std=0.937314629673516
  avg=8.661525             std=0.758225900265437
  avg=8.99436666666667     std=0.444587812848411

H2O-au

Да, именно так, мое центрированное окно разрушает все стандартные тесты! : D таким образом, ваши новые результаты являются ожидаемыми результатами, но пропускают первые два ожидаемых элемента и добавляют два дополнительных элемента в конце (так как моя функция смотрит вперед на 2 элемента).

Graeme_Grant

Я вижу, что вы пропускаете первые 2 оконных результата и имеете дополнительные два нежелательных результата, прикрепленных к концу. Очень неинтуитивно.

H2O-au

Нежеланный? Нет! Разыскивается! И интуитивно! :D Я нахожу окна, которые смотрят только назад, неинтуитивными. Когда мне нужны сглаженные данные, я ожидаю симметричного окна. Я понимаю, что только оглядываясь назад, кажется, что это "стандарт" (например, это все, что делают диаграммы Excel), но лично я никогда не понимал статистической логики этого. Может быть, у людей есть и другие причины отдавать предпочтение асимметричным окнам?

Рейтинг:
0

Graeme_Grant

Думал, что я брошу свою шляпу на ринг в своем обычном (многоязычном) стиле, но только одно решение... ;)

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


using System;
using System.Linq;

namespace RollingSD
{
    class Program
    {
        static void Main(string[] args)
        {
            double[] data = { 1E30f, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };

            Console.WriteLine("Running");
            Console.WriteLine("-------\r\n");

            int count = 0;
            var avg = 0.0;
            var t = new Tuple<double, double, int>(0.0, 0.0, 0);
            for (int i = 0; i < data.Length; i++)
            {
                avg = avg.Average(data[i], ref count);
                Console.WriteLine($"Dataset: {{{string.Join(", ", data.Take(i + 1))}}}");
                Console.WriteLine($"Results: Avg = {avg,-20} Std Dev = {data[i].StandardDeviation(ref t),-20}\r\n");
            }

            Console.WriteLine("\r\n\r\nWindowed");
            Console.WriteLine("--------\r\n");

            var window = new CircularBuffer<double>(5);
            foreach (var item in data)
            {
                window.Add(item);
                Console.WriteLine($"Dataset: {window}");
                Console.WriteLine($"Results: Avg = {window.Average(),-20} Std Dev = {window.StandardDeviation(),-20}\r\n");
            }

            Console.WriteLine("-- Press any ket to exit --");
            Console.ReadKey();
        }
    }

    public static class HelperExtensions
    {
        public static double StandardDeviation(this CircularBuffer<double> data)
        {
            var s1 = 0.0;
            var t = new Tuple<double, double, int>(0.0, 0.0, 0);
            for (int i = 0; i < data.Length; i++)
                s1 = data[i].StandardDeviation(ref t);
            return s1;
        }

        public static double StandardDeviation(this double value, ref Tuple<double, double, int> t)
        {
            var c = t.Item3 + 1;
            double m, s = m = 0.0;
            if (c == 1)
                m = value;
            else
            {
                m = t.Item1 + (value - t.Item1) / c;
                s = t.Item2 + (value - t.Item1) * (value - m);
            }
            t = new Tuple<double, double, int>(m, s, c);
            return Math.Sqrt(c > 1 ? s / (c - 1) : 0);
        }

        public static double Average(this CircularBuffer<double> data)
        {
            var a1 = 0.0;
            int count = 0;
            for (int i = 0; i < data.Length; i++)
                a1 = a1.Average(data[i], ref count);
            return a1;
        }

        public static double Average(this double average, double value, ref int count)
            => (average * count + value) / ++count;
    }

    public class CircularBuffer<T>
    {
        T[] buffer;
        int nextFree, len;

        public CircularBuffer(int length)
        {
            buffer = new T[length];
            nextFree = 0;
        }

        public void Add(T o)
        {
            buffer[nextFree] = o;
            nextFree = (nextFree + 1) % buffer.Length;
            if (len < buffer.Length) len++;
        }

        public T this[int index]
            => index >= 0 && index <= buffer.Length ? buffer[index] : default(T);

        public int Length => len;

        public override string ToString()
            => $"{{{string.Join(", ", ToList())}}}";

        public IEnumerable<T> ToList()
        {
            foreach (var item in nextFree < len
                ? buffer.Skip(nextFree)
                        .Take(len - nextFree)
                        .Concat(buffer.Take(nextFree))
                : buffer.Take(len))
                yield return item;
        }
    }
}

Imports System.Runtime.CompilerServices

Module Module1

    Sub Main()

        Dim data As Double() = {1.0E+30F, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}

        Console.WriteLine("Running")
        Console.WriteLine("-------{0}", vbCrLf)

        Dim count As Integer = 0
        Dim avg As Double = 0F
        Dim t = New Tuple(Of Double, Double, Integer)(0.0, 0.0, 0)
        For i As Integer = 0 To data.Length - 1
            avg = avg.Average(data(i), count)
            Console.WriteLine("Dataset: {{{0}}}", String.Join(", ", data.Take(i + 1)))
            Console.WriteLine("Results: Avg = {0,-20} Std Dev = {1,-20}{2}",
                              avg, data(i).StandardDeviation(t), vbCrLf)
        Next

        Console.WriteLine("{0}{0}Windowed", vbCrLf)
        Console.WriteLine("--------{0}", vbCrLf)

        Dim window = New CircularBuffer(Of Double)(5)
        For Each item In data
            window.Add(item)
            Console.WriteLine("Dataset: {0}", window)
            Console.WriteLine("Results: Avg = {0,-20} Std Dev = {1,-20}{2}",
                              window.Average(), window.StandardDeviation(), vbCrLf)
        Next
        Console.WriteLine("-- Press any ket to exit --")
        Console.ReadKey()
    End Sub

End Module

Module HelperExtensions

    <Extension>
    Public Function StandardDeviation(data As CircularBuffer(Of Double)) As Double
        Dim s1 = 0.0
        Dim t = New Tuple(Of Double, Double, Integer)(0.0, 0.0, 0)
        For i As Integer = 0 To data.Length - 1
            s1 = data(i).StandardDeviation(t)
        Next
        Return s1
    End Function

    <Extension>
    Public Function StandardDeviation(value As Double, ByRef t As Tuple(Of Double, Double, Integer)) As Double
        Dim c = t.Item3 + 1
        Dim m = 0.0
        Dim s = 0.0
        If c = 1 Then
            m = value
        Else
            m = t.Item1 + (value - t.Item1) / c
            s = t.Item2 + (value - t.Item1) * (value - m)
        End If
        t = New Tuple(Of Double, Double, Integer)(m, s, c)
        Return Math.Sqrt(IIf(c > 1, s / (c - 1), 0))
    End Function

    <Extension>
    Public Function Average(data As CircularBuffer(Of Double)) As Double
        Dim a1 = 0.0
        Dim count As Integer = 0
        For i As Integer = 0 To data.Length - 1
            a1 = a1.Average(data(i), count)
        Next
        Return a1
    End Function

    <Extension>
    Public Function Average(avg As Double, value As Double, ByRef count As Integer) As Double
        count += 1
        Dim ret As Double = (avg * (count - 1) + value) / count
        Return ret
    End Function

End Module

Public Class CircularBuffer(Of T)

    Private buffer As T()
    Private nextFree As Integer, len As Integer

    Public Sub New(length As Integer)
        buffer = New T(length - 1) {}
        nextFree = 0
    End Sub

    Public Sub Add(o As T)
        buffer(nextFree) = o
        nextFree = (nextFree + 1) Mod buffer.Length
        If len < buffer.Length Then
            len += 1
        End If
    End Sub

    Default Public ReadOnly Property Item(index As Integer) As T
        Get
            Return If(index >= 0 AndAlso index <= buffer.Length, buffer(index), Nothing)
        End Get
    End Property

    Public ReadOnly Property Length() As Integer
        Get
            Return len
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return String.Format("{{{0}}}", String.Join(", ", ToList()))
    End Function

    Public Iterator Function ToList() As IEnumerable(Of T)
        For Each Item As T In If(nextFree < len,
            buffer.Skip(nextFree) _
                  .Take(len - nextFree) _
                  .Concat(buffer.Take(nextFree)), buffer.Take(len))
            Yield Item
        Next
    End Function

End Class


Выход:
Running
-------

Dataset: {1.00000001504747E+30}
Results: Avg = 1.00000001504747E+30 Std Dev = 0

Dataset: {1.00000001504747E+30, 1}
Results: Avg = 5.00000007523733E+29 Std Dev = 7.07106791826713E+29

Dataset: {1.00000001504747E+30, 1, 1}
Results: Avg = 3.33333338349155E+29 Std Dev = 5.77350277877284E+29

Dataset: {1.00000001504747E+30, 1, 1, 1}
Results: Avg = 2.50000003761867E+29 Std Dev = 5.00000007523733E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1}
Results: Avg = 2.00000003009493E+29 Std Dev = 4.47213602229389E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1}
Results: Avg = 1.66666669174578E+29 Std Dev = 4.08248296606965E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.42857145006781E+29 Std Dev = 3.77964478696635E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.25000001880933E+29 Std Dev = 3.53553395913357E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.11111112783052E+29 Std Dev = 3.33333338349155E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.00000001504747E+29 Std Dev = 3.16227770775265E+29

Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 9.09090922770424E+28 Std Dev = 3.01511349114745E+29



Windowed
--------

Dataset: {1.00000001504747E+30}
Results: Avg = 1.00000001504747E+30 Std Dev = 0

Dataset: {1.00000001504747E+30, 1}
Results: Avg = 5.00000007523733E+29 Std Dev = 7.07106791826713E+29

Dataset: {1.00000001504747E+30, 1, 1}
Results: Avg = 3.33333338349155E+29 Std Dev = 5.77350277877284E+29

Dataset: {1.00000001504747E+30, 1, 1, 1}
Results: Avg = 2.50000003761867E+29 Std Dev = 5.00000007523733E+29

Dataset: {1.00000001504747E+30,1,1,1,1}
Results: Avg = 2.00000003009493E+29 Std Dev = 4.47213602229389E+29

Dataset: {1,1,1,1,1}
Results: Avg = 1                    Std Dev = 0

Dataset: {1,1,1,1,1}
Results: Avg = 1                    Std Dev = 0

Dataset: {1,1,1,1,1}
Results: Avg = 1                    Std Dev = 0

Dataset: {1,1,1,1,1}
Results: Avg = 1


Richard Deeming

Пссст... что-то пошло не так с твоими расчетами! :)

Для входной последовательности {1, 1, 1, 1, 1}:

* среднее значение равно 1;
* для каждого числа вычтите среднее значение и возведите результат в квадрат.: (1 - 1)*(1 - 1) = 0*0 = 0;
* среднее значение квадрата разности равно 0;
* квадратный корень из этого также 0;
* таким образом, стандартное отклонение должно быть 0, нет 1.

:)

Graeme_Grant

Да, мой короткий путь в Формуле имел недостаток. Теперь исправлено и исправление проводки.

Рейтинг:
0

Arthur V. Ratz

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <math.h>

#define N 16

int cmpfloats(const void * a, const void * b)
{
	if (*(float*)a <  *(float*)b) return -1;
	if (*(float*)a >  *(float*)b) return 1;
	
	return 0;
}

static const int sample_size = 5;

int main(int argc, char* argv[])
{
	float population[N] = { 0 };

	for (int i = 0; i < N; i++)
		population[i] = rand() % 10 + rand() / (float)RAND_MAX;

	qsort((void*)population, N, sizeof(float), cmpfloats);

	printf("dataset:\n");

	for (int i = 0; i < N; i++)
		printf("%6.4f ", population[i]);

	printf("\n\n");

	float avg = 0.00F;
	for (int t = 0; t < sample_size; t++)
		avg += population[t] / (float)sample_size;

	printf("average = %6.4f\n\n", avg);

	float var = 0.00F;
	float avg_old = 0.00F, avg_new = avg_old;
	for (int i = 0; i < N - sample_size + 1; i++)
	{
		int wn_old = i; avg_old = avg;
		int wn_new = i + sample_size;

		float* temp = (float*)calloc(sample_size, sizeof(float));
		memcpy((void*)temp, (const void*)&population[wn_old], sizeof(float) * sample_size);

		printf("window sample #%d (wn_start = %d wn_end = %d):\n", i, wn_old, wn_new);
		for (int t = 0; t < sample_size; t++)
			printf("%6.4f ", temp[t]);

		printf("\n");

		avg = avg_new = avg_old + (population[wn_new] - population[wn_old]) / sample_size;

		float sum = 0.00F, sumsq = 0.00F;
		for (int t = 0; t < sample_size; t++)
		{
			sum += temp[t];
			sumsq += temp[t] * temp[t];
		}

		float mu = sum / sample_size;
		float delta = (sumsq / sample_size) - float(pow((double)mu, 2));
		float stdev_pop = float(sqrt(delta));

		float var_sampl = 0.00F;
		for (int t = 0; t < sample_size; t++)
			var_sampl += float(pow((double)(temp[t] - mu), 2));

		var_sampl /= (sample_size - 1);

		float stdev_sampl = float(sqrt(var_sampl));

		printf("average = %6.4f variance_pop = %6.4f stdev_pop = %6.4f varience_sampl = %6.4f stdev_sampl = %6.4f\n", avg_old, delta, stdev_pop, var_sampl, stdev_sampl);
	}

	_getch();

    return 0;
}

dataset:
1.0150 1.4457 1.5135 1.5636 1.5712 2.0047 2.3779 2.7466 4.8087 5.3645 5.8589 7.1659 7.6630 8.6072 8.8960 9.4799

average = 1.4218

window sample #0 (wn_start = 0 wn_end = 5):
1.0150 1.4457 1.5135 1.5636 1.5712
average = 1.4218 variance_pop = 0.0434 stdev_pop = 0.2083 varience_sampl = 0.0542 stdev_sampl = 0.2329
window sample #1 (wn_start = 1 wn_end = 6):
1.4457 1.5135 1.5636 1.5712 2.0047
average = 1.6197 variance_pop = 0.0391 stdev_pop = 0.1976 varience_sampl = 0.0488 stdev_sampl = 0.2209
window sample #2 (wn_start = 2 wn_end = 7):
1.5135 1.5636 1.5712 2.0047 2.3779
average = 1.8062 variance_pop = 0.1132 stdev_pop = 0.3364 varience_sampl = 0.1415 stdev_sampl = 0.3761
window sample #3 (wn_start = 3 wn_end = 8):
1.5636 1.5712 2.0047 2.3779 2.7466
average = 2.0528 variance_pop = 0.2121 stdev_pop = 0.4606 varience_sampl = 0.2652 stdev_sampl = 0.5149
window sample #4 (wn_start = 4 wn_end = 9):
1.5712 2.0047 2.3779 2.7466 4.8087
average = 2.7018 variance_pop = 1.2621 stdev_pop = 1.1234 varience_sampl = 1.5776 stdev_sampl = 1.2560
window sample #5 (wn_start = 5 wn_end = 10):
2.0047 2.3779 2.7466 4.8087 5.3645
average = 3.4605 variance_pop = 1.8488 stdev_pop = 1.3597 varience_sampl = 2.3110 stdev_sampl = 1.5202
window sample #6 (wn_start = 6 wn_end = 11):
2.3779 2.7466 4.8087 5.3645 5.8589
average = 4.2313 variance_pop = 1.9812 stdev_pop = 1.4076 varience_sampl = 2.4765 stdev_sampl = 1.5737
window sample #7 (wn_start = 7 wn_end = 12):
2.7466 4.8087 5.3645 5.8589 7.1659
average = 5.1889 variance_pop = 2.0995 stdev_pop = 1.4490 varience_sampl = 2.6244 stdev_sampl = 1.6200
window sample #8 (wn_start = 8 wn_end = 13):
4.8087 5.3645 5.8589 7.1659 7.6630
average = 6.1722 variance_pop = 1.1639 stdev_pop = 1.0789 varience_sampl = 1.4549 stdev_sampl = 1.2062
window sample #9 (wn_start = 9 wn_end = 14):
5.3645 5.8589 7.1659 7.6630 8.6072
average = 6.9319 variance_pop = 1.4008 stdev_pop = 1.1836 varience_sampl = 1.7510 stdev_sampl = 1.3233
window sample #10 (wn_start = 10 wn_end = 15):
5.8589 7.1659 7.6630 8.6072 8.8960
average = 7.6382 variance_pop = 1.1821 stdev_pop = 1.0872 varience_sampl = 1.4776 stdev_sampl = 1.2156
window sample #11 (wn_start = 11 wn_end = 16):
7.1659 7.6630 8.6072 8.8960 9.4799
average = 8.3624 variance_pop = 0.7028 stdev_pop = 0.8383 varience_sampl = 0.8785 stdev_sampl = 0.9373


Graeme_Grant

У вас есть среднее значение и дисперсия, как насчет стандартного отклонения?

Arthur V. Ratz

Исправлено. Теперь он также вычисляет стандартное отклонение.

Graeme_Grant

Пара вещей:
1. вы пропустили последний тест для вашего массива значений:

Набор данных: {7.1659,7.663,8.6072,8.896,9.4799}

2. я получаю совсем другие результаты:

Набор данных: {5.8589,7.1659,7.663,8.6072,8.896}
Результаты: СР = 7.6382
Стандартное Отклонение = 1.21558758014386

Поэтому я проверил его с Ричардом и его доходами с теми же результатами, что и я. Я думаю, что в ваших расчетах есть ошибка.

Arthur V. Ratz

Я исправил код. Я полагаю, что этот вычислит среднее и стандартное отклонение правильно.

Graeme_Grant

:)

Arthur V. Ratz

Вы можете проверить это с помощью http://www.calculator.net/standard-deviation-calculator.html?numberinputs=5.8589%2C7.1659%2C7.6630%2C8.6072%2C8.8960&x=44&y=13