第一个解决方案提供了一个很好的短语,它使用了numpy中的
sortedsearch
,这需要进行排序和多次搜索。但是numpy在其源代码中有一条快速路径,实际上是用Python完成的,可以在数学上处理相等的bin边缘范围。该解决方案仅使用向量减法和乘法以及一些比较运算。
此解决方案将遵循numpy代码的搜索排序、类型输入和处理权重以及复杂数字。它基本上是第一个解决方案与numpy直方图快速路线、一些额外的类型和迭代细节等的结合。
_range = range
def hist_np_laxis(a, bins=10, range=None, weights=None):
N = a.shape[-1]
data2D = a.reshape(-1,N)
limit = bins*data2D.shape[0]
bin_type = np.result_type(range[0], range[1], a)
if np.issubdtype(bin_type, np.integer):
bin_type = np.result_type(bin_type, float)
bin_edges = np.linspace(range[0],range[1],bins+1, endpoint=True, dtype=bin_type)
if weights is None:
ntype = np.dtype(np.intp)
else:
ntype = weights.dtype
n = np.zeros(limit, ntype)
norm = bins / (range[1] - range[0])
BLOCK = 65536
for i in _range(0, data2D.shape[0], BLOCK):
tmp_a = data2D[i:i+BLOCK]
block_size = tmp_a.shape[0]
if weights is None:
tmp_w = None
else:
tmp_w = weights[i:i + BLOCK]
keep = (tmp_a >= range[0])
keep &= (tmp_a <= range[1])
if not np.logical_and.reduce(np.logical_and.reduce(keep)):
tmp_a = tmp_a[keep]
if tmp_w is not None:
tmp_w = tmp_w[keep]
tmp_a = tmp_a.astype(bin_edges.dtype, copy=False)
f_indices = (tmp_a - range[0]) * norm
indices = f_indices.astype(np.intp)
indices[indices == bins] -= 1
decrement = tmp_a < bin_edges[indices]
indices[decrement] -= 1
increment = ((tmp_a >= bin_edges[indices + 1])
& (indices != bins - 1))
indices[increment] += 1
((bins*np.arange(i, i+block_size)[:,None] * keep)[keep].reshape(indices.shape) + indices).reshape(-1)
if ntype.kind == 'c':
n.real += np.bincount(indices, weights=tmp_w.real,
minlength=limit)
n.imag += np.bincount(indices, weights=tmp_w.imag,
minlength=limit)
else:
n += np.bincount(indices, weights=tmp_w,
minlength=limit).astype(ntype)
n.shape = a.shape[:-1] + (bins,)
return n
data = np.random.randn(4, 5, 6)
out1 = hist_laxis(data, n_bins=200001, range_limits=(- 2.5, 2.5))
out2 = hist_np_laxis(data, bins=200001, range=(- 2.5, 2.5))
print(np.allclose(out1, out2))
True
%timeit hist_np_laxis(data, bins=21, range=(- 2.5, 2.5))
92.1 µs ± 504 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit hist_laxis(data, n_bins=21, range_limits=(- 2.5, 2.5))
55.1 µs ± 3.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
虽然第一个解决方案在小例子和大例子中都更快:
data = np.random.randn(400, 500, 6)
264 ms ± 2.68 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
71.6 ms ± 377 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
这并不总是更快:
data = np.random.randn(400, 6, 500)
%timeit hist_np_laxis(data, bins=101, range=(- 2.5, 2.5))
71.5 ms ± 128 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit hist_laxis(data, n_bins=101, range_limits=(- 2.5, 2.5))
76.9 ms ± 137 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
然而,仅当最后一个轴很大时,numpy的变体才更快。而且增速非常微小。在我尝试的所有其他情况中,无论bin计数和前两个维度的大小如何,第一种解决方案都要快得多。唯一重要的行
((bins*np.arange(i, i+block_size)[:,None] * keep)[keep].reshape(indices.shape) + indices).reshape(-1)
可能更容易优化,尽管我还没有找到更快的方法。
这也意味着O(n)的向量化操作的数量超过了排序和重复增量搜索的O(n log n)。
但是,实际使用情况将具有具有大量数据的最后一个轴和前面的轴很少的情况。因此,在现实中,第一种解决方案中的样本过于人为,无法满足所需的性能。
numpy仓库中已经注意到了直方图的轴添加问题:
https://github.com/numpy/numpy/issues/13166。
一个xhistogram库也试图解决这个问题:
https://xhistogram.readthedocs.io/en/latest/。