如何计算累积几何平均值?

How to calculate cumulative geometric mean?

提问人:Loc 提问时间:9/25/2023 最后编辑:Loc 更新时间:9/27/2023 访问量:144

问:

我正在寻找使用 O(1) 空间计算累积几何平均值,其中输入是未知数字流。批次 = 1。我有这个例子,它与我试图解决的问题非常接近:

count = 1
geo_avg = 0
while True:
  number = input("Enter number: ")

  geo_avg = ((1 + float(number)) * (1 + geo_avg)) ** (1 / count) - 1

  print("current geometric average", geo_avg, "\n")

  count += 1

我知道这是错误的。我想我可能不得不删除以前的几何平均值,然后计算当前的几何平均值,但这听起来也不对。任何关于使用什么是正确方程式的评论/提示都将非常有用。

python-3.x 数学 实时 金融

评论

3赞 Him 9/25/2023
跟踪每个值的 $\log$ 的运行总和。几何平均值可以随时从中轻松计算出来。
0赞 Loc 9/25/2023
我想我明白你在说什么。这有意义吗?ln_sum += ln(1 + number); geo_avg = e^((1/count) * ln_sum) - 1
1赞 Onyambu 9/25/2023
累积均值的概念:。因此,您可以跟踪产品,即有cumprod(x)**(1/range(1,len(x) + 1)prod =* float(number); geo_avg = prod**(1/count); count +=1
0赞 chux - Reinstate Monica 9/27/2023
@Loc,代码需要处理在哪里吗?geometric_averager(x)x < 0

答:

1赞 Nick ODell 9/25/2023 #1

扩展评论中提到的想法:

import math


def running_geo_mean(x):
    sum_log = 0
    count = 0
    mean = []
    for elem in x:
        sum_log += math.log(elem)
        count += 1
        mean.append(math.exp(sum_log / count))
    return mean

用法示例:

x = [i + 1 for i in range(10)]
print(running_geo_mean(x))
[1.0, 1.414213562373095, 1.8171205928321397, 2.213363839400643, 2.6051710846973517, 2.993795165523909, 3.3800151591412964, 3.764350599503129, 4.1471662743969135, 4.528728688116766]

评论

0赞 Him 9/25/2023
这只是.“运行”意味着他们正在重复计算这一点。也就是说,它们在一组中有 20M 个值。他们已经计算了几何平均值,然后代码继续用这个平均值做其他事情。他们现在得到一个额外的值(即 20,000,001 个值),并且需要该值的几何平均值。(依此类推)geometric_mean
0赞 Nick ODell 9/25/2023
@Him 事实并非如此,请注意几何平均值返回的值数与此实现返回的值数。
0赞 Him 9/25/2023
当然,但 OP 说他们有一个“流”,所以“运行”版本的目的是不必每次加起来 20M 的东西。20M + 第 1 个平均值是恒定时间,如果您缓存总和(在类变量中,或全局变量中,或将其作为协程,或或或......
0赞 Nick ODell 9/26/2023
@Him我不同意你对这个问题的解读。我邀请你留下一个答案,实现你的解释。
0赞 Him 9/26/2023
查看@NoeDuruz的回答
1赞 Simon Goater 9/25/2023 #2

如果 g_n 是 n 个数字的几何平均值,则要包含另一个数字 x_{n+1},则新的几何平均值为

g_{n+1} = (x_{n+1} * ((g_n) ^ n)) ^ (1/(n+1))

不过,这个公式在实践中可能会引入很多舍入错误。如果保留正在运行的产品

(g_n) ^ n = prod{i = 1 to n} x_i = P_n

它应该返回更好的准确性。如果要包含 m 个数字,则x_{n+1},...,x_{n+m},然后计算

p = prod_{i = n+1 to n+m} x_i

先,然后

P_{n+m} = (p * P_n)

g_{n+m} = P_{n+m} ^ (1/(n+m))

如果有P_n溢出的危险,那么你可以按照他的建议对日志进行求和。我预计这会更慢,并且通常具有更高的舍入误差,但溢出的可能性要小得多。

L_n = sum_{i = 1 to n} ln(x_i) = n * ln(g_n)

g_n = exp(L_n/n)

然后对于另一个 m 个数字,计算

l = sum_{i = n+1 to n+m} ln(x_i)

L_{n+m} = l + L_n

g_{n+m} = exp(L_{n+m}/(n+m))
1赞 TooTone 9/25/2023 #3

要计算尺寸的运行几何平均值,您可以跟踪最后一个数字和当前乘积。当您有一个新号码时,您将要替换的数字除以要添加的数字,然后乘以要添加的数字。nn

这是在 Python 中执行此操作的一种方法。

import functools
import math


def produce_geometric_average(n: int):
    lst = []
    log_prod = 0
    idx = 0

    def geometric_average(x: float):
        nonlocal lst
        nonlocal log_prod
        nonlocal idx
        log_x = math.log(x)
        if len(lst) < n:
            lst.append(log_x)
            log_prod = functools.reduce(lambda x, y: x + y, lst)
        else:
            idx_to_replace = idx % n
            old_log_x = lst[idx_to_replace]
            log_prod = log_prod - old_log_x + log_x
            lst[idx_to_replace] = log_x
        idx += 1
        return math.exp(log_prod / len(lst))
        # c.f., return math.exp(functools.reduce(lambda x, y: x + y, lst) / len(lst))

    return geometric_average


geometric_average = produce_geometric_average(3)

for x in [1.0, 1.0, 8.0, 27.0, 64.0, 125.0, 216.0]:
    print(geometric_average(x))

输出

1.0
1.0
2.0
6.000000000000001 
23.999999999999993
59.999999999999986
119.99999999999997

评论

1赞 Him 9/26/2023
根据单个数字的大小,产品将在短时间内超过一个的大小。例如,如果数字都是 ,则在集合的大小增长到 后,产品将太大而无法存储在 .当然,python 能够表示任意大的整数,但这种表示有其局限性,其中之一是在执行以下操作时: Python 将尝试转换为 ,如果太大,则只会出错。long264long**prodfloatprod
0赞 TooTone 9/26/2023
@Him 你是对的。我本来想将浮点数而不是整数传递给函数,但尽管如此,我还是假设双精度数字的大小 c. 10^300 非常大并且足够了,但对于几个 100 个数字的滚动几何平均值,这是一个不合理的假设。已经修好了,谢谢。
0赞 TooTone 9/26/2023
@Him虽然用闭包做几何平均值的“运行”部分现在想想似乎有点愚蠢,因为客户端可能已经有一个列表/循环缓冲区/等,这个答案创建了它的另一个副本。
1赞 Him 9/26/2023
“有点傻”不。我本来会用类、也许或类似的东西来做到这一点,但这只是命名空间变量 /shrug 的另一种方法。RunningStatistics
4赞 Noé Duruz 9/25/2023 #4

您可以跟踪对数总和数字计数,以便将几何平均值计算为对数刻度中的算术平均值,如维基百科上所述。

下面是使用闭包的 Python 实现。

from math import log, exp


def make_geometric_averager():
    log_sum = 0
    number_count = 0

    def geometric_mean(x: float):
        nonlocal log_sum, number_count
        log_sum += log(x)
        number_count += 1
        return exp(log_sum / number_count)
    
    return geometric_mean


# Example
geometric_averager = make_geometric_averager()

print(geometric_averager(10))  # 10.0
print(geometric_averager(100))  # 31.62
print(geometric_averager(1000))  # 99.99, numerical imprecision
1赞 Severin Pappadeux 9/26/2023 #5

这通常是单行的,给定前一个元组 (gmn, n) 和新值 x,返回 (gm n+1n+1 的新元组

def update_geo_mean(gm_n, x):
    gm, n = gm_n
    return ((gm**n * x)**(1.0/(n+1)), n+1)

可以使用 exp/log 来避免溢出/下溢。

以 (1,0) 元组开头

带 log/exp 的版本

import numpy as np

def update_geo_mean(gm_n, x):
    gm, n = gm_n
    return (np.exp((n * np.log(gm) + np.log(x))/(n+1)), n+1)

更新

可以将元组 (gmn, n) 和 update_geo_mean() 视为 Monoid 的例子,其中恒等元素是 (1,0) 元组。单元二进制操作将是

def geo_mean_binary(gm_n, gm_m):
    g_n, n = gm_n
    g_m, m = gm_m

    return (np.exp((n * np.log(g_n) + m*np.log(g_m))/(n+m)), n+m)

def update_geo_mean(gm_n, x):
    return geo_mean_binary(gm_n, (x,1))