如何保持动态直方图?

时间:2011-07-29 16:45:23

标签: algorithm data-structures statistics histogram

是否有已知的算法+数据结构来维持动态直方图?

想象一下,我有一个数据流(x_1,w_1),(x_2,w_2),...其中x_t是双精度数,代表一些测量变量,w_t是相关权重。

我可以做一下显而易见的(伪python代码):

x0,xN = 0, 10
numbins = 100
hist = [(x0 + i * delta , 0) for i in xrange(numbins)]
def updateHistogram(x, w):
    k = lookup(x,  hist)    #find the adequated bin where to put x
    hist[k][1] += 1

但是当我有连续的数据流时,我遇到了一些问题。我手头没有完整的数据集,我必须在数据收集之间检查直方图。我没有期望:

  • 理想的垃圾箱尺寸,不会留下很多空箱,
  • 数据范围

所以我想动态定义分档。我可以做愚蠢的事情:

 for x in data_stream:
      data.append(x)
      hist = make_histogram(data)

但我想这会很快变慢......

如果所有权重等于我认为将数据存储在排序数组中并以保持数组排序的方式插入新数据之一。这样我就可以:

data = sortedarray();
for x in data_stream:
     data.insert(x)
     bins = [ data[int(i * data.size()/numbins)] for i in xrange(numbins)]

并且每个bin中的计数将等于所有bin的data.size()/ numbins。

我想不出在这方面包含权重的方法......有没有人有建议? (关于这样做的c ++库的知识也会受到欢迎)。

编辑:(对于要求的澄清)

x_t是浮点数。要计算直方图,我必须将x所属的连续范围除以多个区间。所以我将有一个数字序列bin [0],bin [1]等...所以我必须确定我做什么bin [i]< x<仓[I + 1]。

当您事先获得所有数据时,通常会使用直方图。然后,您将知道极限(x)和min(x)的极限,并且很容易确定足够的箱。例如,你可以让它们在min(x)和max(x)之间等间隔。

如果您事先不知道范围,则无法确定容器。你可以收到一个不属于任何垃圾箱的x。或者你可以使用许多空箱,因为你选择了太大的范围来制作垃圾箱。

3 个答案:

答案 0 :(得分:13)

如何确定垃圾箱数量

在直方图中有许多用于确定number of bins的规则。对于你的问题,我会选择Scott的选择:

bin_width = 3.5*sd*n^{-1/3}

其中 sd 是标准偏差, n 是数据点的数量。至关重要的是,您可以使用online算法来计算标准偏差。箱柜的数量 k 由下式给出:

k = ceil((max(x) - min(x))/bin_width)

存储数据

假设我们已观察到N个数据点。然后是标准差的置信区间,

Lower limit: sd*sqrt((N-1)/CHIINV((alpha/2), N-1))
Upper limit: sd*sqrt((N-1)/CHIINV(1-(alpha/2), N-1))

其中CHIINV是卡方分布的值。当N = 1000时,sd的CI为:

(0.96*sd, 1.05*sd)

所以bin宽度为95%CI:

(3.5*0.96*sd*1000^{-1/3}, 3.5*1.05*sd*1000^{-1/3})
(0.336*sd, 0.3675*sd)

您可以获得类似于垃圾箱的数量。

<强>算法

  1. 存储所有数据,直到您对最佳bin宽度有估计值,比如当bin的数量的下限和上限相等时。
  2. 创建垃圾箱数量并将数据放入垃圾箱。
  3. 所有新数据点都放入垃圾箱,然后丢弃。
  4. <强>评论

    1. Freedman-Diaconis的规则更适合选择分档数量,但它涉及分位数间范围,这在网上计算起来有点棘手。
    2. 从技术上讲,当数据是连续的时,CI间隔不正确。但是如果你设置一个合理的最小数据点来观察,比如说~100或1000,你应该没问题。
    3. 这假设数据都遵循相同的分布。
    4. 箱数取决于n ^ { - 1/3}。如果你大致知道预期的点数,即10 ^ 5,10 ^ 6或10 ^ 7,那么你可以创建更小的箱子,期望将来改变箱子宽度。

答案 1 :(得分:4)

听起来好像你想要实现以下抽象数据类型。

insert(x, w): add item x to the collection with weight x
select(p): return the item greater than a p weighted fraction of the items

例如,select(0)返回最小值,select(0.5)返回加权中位数,select(1)返回最大值。

我会以两种方式之一实现此ADT。如果选择不频繁,我会将数据放入数组并使用线性时间选择算法,用于O(1)时间插入和O(n)时间选择。如果选择频繁,我会使用二叉搜索树,其中每个节点在其子树中存储总权重。例如,在

之后
insert(2, 10)
insert(1, 5)
insert(3, 100)
insert(4, 20)

树可能看起来像

   2 (135)
  / \
 /   \
1 (5) 4 (120)
     /
    /
   3 (100)

现在,要查找加权中位数,请将135乘以0.5,并将67.5作为所需的“索引”。从根2开始,我们发现5小于67.5,因此该项不在左子树中,我们减去5以获取62.5 },索引到树的其余部分。由于135 - 120 = 15小于62.5,因此中位数不是2。我们从15中减去62.5以获得47.5并下降到4。在4,我们发现100大于47.5,因此3是中位数。

假设平衡树,insertselect的运行时间为O(log n)。如果我从头开始实施,我可能会选择一个展开树。

答案 2 :(得分:2)

ROOT是粒子物理学家用于此类工作的工具......它带有python绑定。请注意,它不是一个轻量级的软件。

在c ++中你会做类似

的事情
TH1D hist("hist","longer title for hist",numbins,lowlimit,highimit);

...

for (int i=0; i<num; ++i){
   hist.Fill(x[i],w[i]);
}

...

hist.Draw();

ROOT没有为分箱问题提供内置解决方案,低于/高于分箱范围的输入被添加到欠流量/过流量箱。

您最初可以在很宽的范围内设置分档,并在以后转换为较短的范围。我认为该方法是Rebin。所有明显的限制都适用。