numpy中的高效bin分配

时间:2018-02-06 15:49:45

标签: python numpy indexing binning set-operations

我有一个非常大的1D python数组x有些重复数字,还有一些相同大小的数据d

x = np.array([48531, 62312, 23345, 62312, 1567, ..., 23345, 23345])
d = np.array([0    , 1    , 2    , 3    , 4   , ..., 99998, 99999])

在我的上下文中“非常大”是指10k ... 100k条目。其中一些是重复的,所以唯一条目的数量大约是5k ... 15k。

我想将它们分组到箱子里。这应该通过创建两个对象来完成。一个是矩阵缓冲区,{d}取自b个数据项。另一个对象是每个缓冲区列引用的唯一x值的向量v。这是一个例子:

v =  [48531, 62312, 23345, 1567, ...]
b = [[0    , 1    , 2    , 4   , ...]
     [X    , 3    , ....., ...., ...]
     [ ...., ....., ....., ...., ...]
     [X    , X    , 99998, X   , ...]
     [X    , X    , 99999, X   , ...] ]

由于x中每个唯一数字的出现次数不同,缓冲区b中的某些值无效(由大写X表示,即“不关心”)。

在numpy中导出v非常容易:

v, n = np.unique(x, return_counts=True)  # yay, just 5ms

我们甚至得到n,这是b中每列中有效条目的数量。此外,(np.max(n), v.shape[0])返回需要分配的矩阵b的形状。

如何有效地生成b ? for循环可以帮助

b = np.zeros((np.max(n), v.shape[0]))
for i in range(v.shape[0]):
    idx = np.flatnonzero(x == v[i])
    b[0:n[i], i] = d[idx]

此循环遍历b的所有列,并通过识别idx的所有位置来提取索引x == v

但是我不喜欢这个解决方案,因为循环速度相当慢(比独特命令长约50倍)。我宁愿让操作矢量化。

因此,一种向量化方法是创建索引矩阵x == v,然后沿着列运行nonzero()命令。但是,这个矩阵需要150k x 15k的内存,所以在32位系统上大约需要8GB。

对我而言,np.unique - 操作甚至可以有效地返回反向索引以便x = v[inv_indices]听起来相当愚蠢但是无法获得每个{-1}}的v-to-x赋值列表bin in v。当函数扫描x时,这几乎是免费的。在实施方面,唯一的挑战是结果索引矩阵的未知大小。

另一种表达此问题的方法,假设np.unique-command是用于分箱的方法:

给定三个数组x, v, inv_indices其中vxx = v[inv_indices]中的唯一元素,有一种生成索引向量v_to_x[i]的有效方法所有箱子all(v[i] == x[v_to_x[i]])都是i

我不应该花费更多时间而不是np.unique-command本身。我很高兴为每个箱子中的物品数量提供上限(例如50)。

1 个答案:

答案 0 :(得分:0)

根据@ user202729的建议,我写了这段代码

x_sorted_args = np.argsort(x)
x_sorted = x[x_sorted_args]

i = 0
v = -np.ones(T)
b = np.zeros((K, T))

for k,g in groupby(enumerate(x_sorted), lambda tup: tup[1]):
    groups = np.array(list(g))[:,0]
    size = groups.shape[0]

    v[i] = k
    b[0:size, i] = d[x_sorted_args[groups]]
    i += 1

在大约100毫秒的运行中导致一些相当大的加速w.r.t.上面张贴的原始代码。

它首先枚举x中添加相应索引信息的值。然后枚举按实际x值分组,实际上是enumerate()生成的元组的第二个值。

for循环遍历所有组,将元组g的迭代器转换为大小为groups的{​​{1}}矩阵,然后抛弃第二列,即{{1}值只保留索引。这导致(size x 2)只是一维数组。

x仅适用于已排序的数组。

干得好。我只是想知道我们能做得更好吗?仍然有很多不合理的数据复制似乎发生了。创建一个元组列表然后将其转换为2D矩阵只是为了丢掉一半它仍然感觉有点不理想。