Как эффективно вычислить среднее геометрическое массива Numpy?

Скользящее среднее арифметическое можно просто вычислить с помощью функции Numpy 'convolve', но как я могу эффективно создать массив текущих средних геометрических некоторого массива a и заданного размера окна?

Приведем пример для массива: [0.5 , 2.0, 4.0]

и размер окна 2 (размер окна уменьшается по краям)

Я хочу быстро сгенерировать массив: [0.5, 1.0, 2.83, 4.0]

Вам нужен уменьшающийся размер по краям? Это очень усложняет ситуацию. И правильно ли я понимаю, что это означает, например, для размера окна 3: первое окно будет иметь размер 1, второе будет иметь размер 2, а следующие будут иметь размер 3?

simon 04.07.2024 11:47

Может быть возможность хранить значения, продукты пар, продукты продуктов пар и т. д. в циклических буферах и объединять их для получения общего продукта. Однако вы все еще застряли с вычислениями pow(product,1.0/N) или чем-то подобным. Если вы можете ограничить его размером окна N=2^m, то sqrt() на современном оборудовании будет работать удивительно быстро.

Martin Brown 04.07.2024 11:48

Как насчет переключения в логарифмическое пространство, вычисления среднего арифметического и потенцирования обратно в исходное пространство? Также можем ли мы предположить, что числа положительны?

Dima Chubarov 04.07.2024 11:50

@simon Нет - это был просто способ сделать вопрос более конкретным, но любая разумная обработка краев (включая вычисление только средних географических значений, когда все окно заполнено и возврат меньшего массива) подойдет. Для уменьшающихся окон - да, я это имел в виду - и с другой стороны будет 3,2,1

ufghd34 04.07.2024 11:53

@ ufghd34 Спасибо за разъяснения! Думаю, мой ответ теперь в любом случае охватывает уменьшающиеся окна: D См. ниже.

simon 04.07.2024 11:54

Насколько велик размер окон в ваших реальных сценариях использования? Насколько велики на самом деле массивы на практике? sliding_window_view и convolve неэффективны для больших окон.

Jérôme Richard 04.07.2024 11:56
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
6
96
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий
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.        ]
  • Используйте встроенный gmean() из scipy.
  • Дополните nan, что в сочетании с gmean(…, nan_policy = "omit") приводит к уменьшению размера окна на границах.
  • Используйте скользящий_window_view() для создания текущего результата.
  • Собираем все вместе (см. выше).

Если вам не нужны уменьшающиеся размеры окон на границах (имеется в виду ваш комментарий ), вы можете пропустить шаг заполнения и выбрать 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') ;)

mozway 04.07.2024 11:59

Будет ли gmean более эффективным, чем простой prod?

Yves Daoust 04.07.2024 12:00

@mozway Ох, спасибо! Я не знал np.pad()! @YvesDaoust Думаю, это нужно будет проверить

simon 04.07.2024 12:01

Идеального решения не существует.

Вы можете использовать функцию 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 на скользящем срезе.

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

Jaime 04.07.2024 13:59

@Джейме: это правда. Обходной путь — сообщать о нулевом продукте при достижении нуля, но пропускать операции умножения и деления с нулевым членом.

Yves Daoust 05.07.2024 17:41

Самый быстрый и простой:

>>> 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-й степени из произведения значений)

simon 04.07.2024 13:17

Упс! Спасибо за исправление

Rajat Jain 05.07.2024 15:37

Замена mean() на gmean(), кажется, не сработала: в последних версиях pandas (2.2.2) это выдает AttributeError: объект «Rolling» не имеет атрибута «gmean»

simon 05.07.2024 16:14

Другие вопросы по теме