在线估计四分位数而不存储观测值

机器算法验证 分位数 中位数 在线算法
2022-01-19 18:29:40

我需要在大量数据上实时计算四分位数(Q1、中位数和 Q3)而不存储观察结果。我首先尝试了 P 平方算法(Jain/Chlamtac),但我对它并不满意(cpu 使用有点过多,并且至少在我的数据集上不相信精度)。

我现在使用 FAME 算法 ( Feldman/Shavitt ) 来动态估计中位数,并尝试推导算法来计算 Q1 和 Q3 :

M = Q1 = Q3 = first data value 
step =step_Q1 = step_Q3 = a small value
for each new data :
        # update median M 
        if M > data:
            M = M - step
        elif M < data:
            M = M + step
        if abs(data-M) < step:
            step = step /2

        # estimate Q1 using M
        if data < M:
            if Q1 > data:
                Q1 = Q1 - step_Q1
            elif Q1 < data:
                Q1 = Q1 + step_Q1
            if abs(data - Q1) < step_Q1:
                step_Q1 = step_Q1/2
        # estimate Q3 using M
        elif data > M:
            if Q3 > data:
                Q3 = Q3 - step_Q3
            elif Q3 < data:
                Q3 = Q3 + step_Q3
            if abs(data-Q3) < step_Q3:
                step_Q3 = step_Q3 /2

要恢复,它只需使用动态获得的中位数 M 将数据集一分为二,然后对 Q1 和 Q3 重复使用相同的算法。

这似乎以某种方式起作用,但我无法证明(我不是数学家)。有缺陷吗?我将不胜感激任何适合该问题的建议或最终的其他技术。

非常感谢您的帮助 !

==== 编辑 =====

对于那些对这些问题感兴趣的人,几周后,我终于通过简单地使用带有 100 个值的 Revervoir 的 Reservoir Sampling 来结束,它给出了非常令人满意的结果(对我来说)。

2个回答

中位数是观察值低于 1/2 和高于 1/2 的点。同样,第 25 个百分位数是最小值和中位数之间的数据的中位数,第 75 个百分位数是中位数和最大值之间的中位数,所以是的,我认为你在应用你首先使用的任何中位数算法时都处于坚实的基础上将整个数据集分区它,然后对两个结果块进行分区。

更新

这个关于 stackoverflow 的问题引出了这篇论文:Raj Jain,Imrich Chlamtac:在不存储观测值的情况下动态计算分位数和直方图的 P² 算法。交流。ACM 28(10): 1076-1085 (1985)其摘要表明您可能对它很感兴趣:

提出了一种启发式算法,用于动态计算中位数和其他分位数。在生成观测值时动态生成估计值。不存储观察结果;因此,无论观察次数如何,该算法的存储需求都非常小且固定。这使其非常适合在可用于工业控制器和记录器的分位数芯片中实现。该算法进一步扩展到直方图绘制。分析算法的准确性。

对您发布的方法进行了非常轻微的更改,您可以计算任意百分位数,而无需计算所有分位数。这是Python代码:

class RunningPercentile:
    def __init__(self, percentile=0.5, step=0.1):
        self.step = step
        self.step_up = 1.0 - percentile
        self.step_down = percentile
        self.x = None

    def push(self, observation):
        if self.x is None:
            self.x = observation
            return

        if self.x > observation:
            self.x -= self.step * self.step_up
        elif self.x < observation:
            self.x += self.step * self.step_down
        if abs(observation - self.x) < self.step:
            self.step /= 2.0

和一个例子:

import numpy as np
import matplotlib.pyplot as plt

distribution = np.random.normal
running_percentile = RunningPercentile(0.841)
observations = []
for _ in range(1000000):
    observation = distribution()
    running_percentile.push(observation)
    observations.append(observation)

plt.figure(figsize=(10, 3))
plt.hist(observations, bins=100)
plt.axvline(running_percentile.x, c='k')
plt.show()

具有 1 个 STD 百分位数的正态分布