Среднее по перекрывающимся окнам в Python

Я пытаюсь вычислить скользящее среднее, но с заданным размером шага между каждым средним. Например, если бы я вычислял среднее значение окна из 4 элементов через каждые 2 элемента:

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Это должно дать среднее значение [1, 2, 3, 4], [3, 4, 5, 6], [5, 6, 7, 8], [7, 8, 9, 10].

window_avg = [2.5, 4.5, 6.5, 8.5]

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

Я немного читал о том, как делать скользящие средние в Python, и, кажется, много использования itertools; однако итераторы идут по одному элементу за раз, и я не могу понять, как установить размер шага между каждым вычислением среднего значения. (Как рассчитать скользящее среднее в Python 3? )

Я также смог сделать это раньше в MATLAB, создав матрицу индексов, которые перекрываются, а затем индексируют вектор данных и выполняют среднее значение по столбцу (Создать матрицу, многократно перекрывая вектор). Однако, поскольку этот вектор довольно велик (~70 000 элементов, окно 450 отсчетов, в среднем каждые 30 отсчетов), вычисление, вероятно, потребует слишком много памяти.

Любая помощь будет принята с благодарностью. Я использую Python 2.7.


person limi44    schedule 13.01.2014    source источник
comment
Я бы попробовал что-то вроде n=4; s==2; [sum(data[s*i:s*i+n])/n for i, datum in enumerate(data[::s])], но, возможно, это не то, что вы ищете (datum здесь не нужно, но range(len(data)) просто выглядит так не на Python).   -  person    schedule 13.01.2014


Ответы (2)


Один из способов вычислить среднее значение скользящего окна по списку в Python — использовать понимание списка. Ты можешь использовать

>>> range(0, len(data), 2)
[0, 2, 4, 6, 8]

чтобы получить начальные индексы каждого окна, а затем функция numpy mean для получения среднего значения каждого окна. Смотрите демо ниже:

>>> import numpy as np
>>> window_size = 4
>>> stride = 2
>>> window_avg = [ np.mean(data[i:i+window_size]) for i in range(0, len(data), stride)
                   if i+window_size <= len(data) ]
>>> window_avg
[2.5, 4.5, 6.5, 8.5]

Обратите внимание, что у понимания списка есть условие, гарантирующее, что оно вычисляет только среднее значение «полных окон» или подсписков, содержащих ровно window_size элементов.

При запуске на наборе данных размера, обсуждаемого в ОП, этот метод вычисляет мой MBA чуть более чем за 200 мс:

In [5]: window_size = 450
In [6]: data = range(70000)
In [7]: stride = 30
In [8]: timeit [ np.mean(data[i:i+window_size]) for i in range(0, len(data), stride)
                 if i+window_size <= len(data) ]
1 loops, best of 3: 220 ms per loop

На моей машине это примерно в два раза быстрее, чем подход itertools, представленный @Abhijit:

In [9]: timeit map(np.mean, izip(*(islice(it, i, None, stride) for i, it in enumerate(tee(data, window_size)))))
1 loops, best of 3: 436 ms per loop
person mdml    schedule 13.01.2014
comment
какова эффективность для массовых данных? - person zhangxaochen; 13.01.2014
comment
@zhangxaochen: я добавил тест скорости для набора данных, упомянутого в ОП. - person mdml; 13.01.2014
comment
кажется, что решение Abhijit намного быстрее: 6,19 мс на цикл - person zhangxaochen; 13.01.2014
comment
@zhangxaochen: интересно, я тоже пытался сравнить его подход и обнаружил обратное. Можете ли вы проверить, что вы получили, глядя на мой обновленный ответ? - person mdml; 13.01.2014
comment
@zhangxaochen: Я полагаю, что с вашим бенчмаркингом может быть что-то не так, подход mdml значительно быстрее, согласно моему тесту - person Abhijit; 13.01.2014

Следующий подход использует itertools в полной мере для создания окна скользящего среднего размера 4. Поскольку тогда все выражение является генератором, который оценивается при вычислении среднего значения, оно имеет сложность O (n).

>>> import numpy as np
>>> from itertools import count, tee, izip, islice
>>> map(np.mean, izip(*(islice(it,i,None,2)
                      for i, it in enumerate(tee(data, 4)))))
[2.5, 4.5, 6.5, 8.5]

Интересно отметить, как отдельные функции itertools работают согласованно.

  1. itertools.tee n-копирует итератор, в данном случае 4 раза
  2. enumerate создает объект перечислителя, который дает кортеж индекса и элемента (который является итератором)
  3. разрезать итератор с шагом 2, начиная с позиции индекса.
person Abhijit    schedule 13.01.2014
comment
Спасибо за вашу помощь! Это больше похоже на то, что я ожидал от реализации itertools, и очень информативно. Я удивлен, что это на самом деле медленнее, чем другой ответ, поскольку я понимаю, что во многих случаях itertools быстрее, чем использование понимания списка. При этом я выбираю ответ mdml, так как он быстрее и читабельнее. - person limi44; 13.01.2014