Скользящее среднее арифметическое можно просто вычислить с помощью функции Numpy 'convolve', но как я могу эффективно создать массив текущих средних геометрических некоторого массива a
и заданного размера окна?
Приведем пример для массива:
[0.5 , 2.0, 4.0]
и размер окна 2 (размер окна уменьшается по краям)
Я хочу быстро сгенерировать массив:
[0.5, 1.0, 2.83, 4.0]
Может быть возможность хранить значения, продукты пар, продукты продуктов пар и т. д. в циклических буферах и объединять их для получения общего продукта. Однако вы все еще застряли с вычислениями pow(product,1.0/N) или чем-то подобным. Если вы можете ограничить его размером окна N=2^m, то sqrt() на современном оборудовании будет работать удивительно быстро.
Как насчет переключения в логарифмическое пространство, вычисления среднего арифметического и потенцирования обратно в исходное пространство? Также можем ли мы предположить, что числа положительны?
@simon Нет - это был просто способ сделать вопрос более конкретным, но любая разумная обработка краев (включая вычисление только средних географических значений, когда все окно заполнено и возврат меньшего массива) подойдет. Для уменьшающихся окон - да, я это имел в виду - и с другой стороны будет 3,2,1
@ ufghd34 Спасибо за разъяснения! Думаю, мой ответ теперь в любом случае охватывает уменьшающиеся окна: D См. ниже.
Насколько велик размер окон в ваших реальных сценариях использования? Насколько велики на самом деле массивы на практике? sliding_window_view
и convolve
неэффективны для больших окон.
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
from scipy.stats import gmean
window = 2
a = [0.5, 2.0, 4.0]
padded = np.pad(a, window - 1, mode = "constant", constant_values=np.nan)
windowed = sliding_window_view(padded, window)
result = gmean(windowed, axis=1, nan_policy = "omit")
print(result)
# >>> [0.5 1. 2.82842712 4. ]
nan
, что в сочетании с gmean(…, nan_policy = "omit")
приводит к уменьшению размера окна на границах.Если вам не нужны уменьшающиеся размеры окон на границах (имеется в виду ваш комментарий ), вы можете пропустить шаг заполнения и выбрать nan_policy
, который вам больше всего подходит.
Обновление: понимая, что gmean()
предоставляет аргумент weights
, мы можем заменить заполнение nan
эквивалентным массивом weights
(1 для фактических значений, 0 для дополненных значений), а затем снова можем выбирать nan_policy
по своему вкусу, даже в случай уменьшения размеров окон на границах. Это означает, что мы могли бы написать:
padded = np.pad(a, window - 1, mode = "constant", constant_values=1.)
windowed = sliding_window_view(padded, window)
weights = sliding_window_view(
np.pad(np.ones_like(a), window - 1, mode = "constant", constant_values=0.),
window)
result = gmean(windowed, axis=1, weights=weights)
– что даст точно такой же результат, как указано выше. Чутье подсказывает мне, что оригинальная версия быстрее, но я не проводил никаких тестов скорости.
Можно упростить до gmean(swv(np.pad(a, N-1, mode='constant', constant_values=np.nan), N), axis=1, nan_policy='omit')
;)
Будет ли gmean
более эффективным, чем простой prod
?
@mozway Ох, спасибо! Я не знал np.pad()
! @YvesDaoust Думаю, это нужно будет проверить
Идеального решения не существует.
Вы можете использовать функцию cumprod
, которая вычисляет префиксные продукты (все частичные продукты слева направо), а затем берет отношения продуктов, которые находятся на расстоянии одного окна, и корень k-й степени.
Например. a, b, c, d, f, e, g -> (a.b.c.d.e.f) / (a.b) = c.d.e.f
К сожалению, для большого количества элементов это может привести к переполнению емкости с плавающей запятой.
Альтернативно, возьмите логарифм всех элементов, поработайте с cumsum
и возьмите антилогарифм среднего значения. Но логарифм стоит дорого.
Вы также можете сформировать произведение первого элемента k
, затем итеративно умножить на следующий и разделить на первый.
Например. a.b.c.d -> (a.b.c.d.e)/a = b.c.d.e
К сожалению, могут накапливаться ошибки усечения, поскольку подразделения не будут точно компенсировать продукты.
Если у вас короткое окно, лучшим вариантом может быть prod
на скользящем срезе.
Нули также испортят подход «умножить, а затем разделить», который так хорошо работает, как «сложить, затем вычесть» для арифметических вычислений.
@Джейме: это правда. Обходной путь — сообщать о нулевом продукте при достижении нуля, но пропускать операции умножения и деления с нулевым членом.
Самый быстрый и простой:
>>> import pandas as pd
>>> a = [0.5 , 2.0, 4.0]
>>> pd.Series(a).rolling(2).gmean()[1:]
1 1.25
2 3.00
dtype: float64
Использовать:
pd.Series(a).rolling(window).mean()[window-1:]
В результате получается скользящее среднее арифметическое (сумма значений, деленная на количество значений n), а не среднее геометрическое (корень n-й степени из произведения значений)
Упс! Спасибо за исправление
Замена mean()
на gmean()
, кажется, не сработала: в последних версиях pandas (2.2.2) это выдает AttributeError: объект «Rolling» не имеет атрибута «gmean»
Вам нужен уменьшающийся размер по краям? Это очень усложняет ситуацию. И правильно ли я понимаю, что это означает, например, для размера окна 3: первое окно будет иметь размер 1, второе будет иметь размер 2, а следующие будут иметь размер 3?