Python中使用numpy/scipy进行向量化分箱的方法

5 投票
3 回答
3946 浏览
提问于 2025-04-15 22:16

我在用Python处理一个二维数组(x乘y),想把它按照x值分成几个区间,这些区间在“bins”里定义,使用了np.digitize这个函数:

elements_to_bins = digitize(vals, bins)

这里的“vals”是一个二维数组,比如:

 vals = array([[1, v1], [2, v2], ...]). 

elements_to_bins这个变量就是用来表示每个元素属于哪个区间。接下来,我想得到一个列表,这个列表的长度和“bins”里的区间数量一样,每个元素返回的是“vals”中属于那个区间的y维度的值。我现在是这样做的:

points_by_bins = []
for curr_bin in range(min(elements_to_bins), max(elements_to_bins) + 1):
    curr_indx = where(elements_to_bins == curr_bin)[0]
    curr_bin_vals = vals[:, curr_indx]
    points_by_bins.append(curr_bin_vals)

有没有更优雅或者更简单的方法来实现这个?我只需要一个包含每个区间内y值的列表的列表。

谢谢。

3 个回答

0

你的例子中的箱子键只是整数,没有进行分箱吗?那么你可以这样做,不需要用到numpy:

from collections import defaultdict
bins = defaultdict(list)  # or [ [] ...] as in EOL

vals = [[1, 10], [1, 11], [2, 20], [2, 21], [2, 22]]  # nparray.tolist()
for nbin, val in vals:
    bins[nbin].append(val)

print "bins:", bins
# defaultdict(<type 'list'>, {1: [10, 11], 2: [20, 21, 22]})
1

这段代码会返回一个数据结构,类似于IDL HISTOGRAM中的Reverse_Indices:

ovec = np.argsort(vals)
ivec = np.searchsorted(vals, bin_limits, sorter=ovec)

然后,落入第i个区间的元素列表是:

ovec[ ivec[i] : ivec[i+1] ]

(我快速测试的结果显示,这个方法比EOL的算法快5倍,因为它不需要创建不同大小的列表)

3

如果我理解你的问题没错的话:

vals = array([[1, 10], [1, 11], [2, 20], [2, 21], [2, 22]])  # Example

(x, y) = vals.T  # Shortcut
bin_limits = range(min(x)+1, max(x)+2)  # Other limits could be chosen
points_by_bin = [ [] for _ in bin_limits ]  # Final result
for (bin_num, y_value) in zip(searchsorted(bin_limits, x, "right"), y):  # digitize() finds the correct bin number
    points_by_bin[bin_num].append(y_value)

print points_by_bin  # [[10, 11], [20, 21, 22]]

Numpy的快速数组操作 searchsorted() 是为了达到最高效率。然后,值会一个一个地添加进去(因为最终的结果不是一个规则的矩形数组,所以Numpy在这方面帮不了太多)。这个方法应该比在循环中多次调用 where() 要快,因为后者会让Numpy多次读取同一个数组。

撰写回答