脾气暴躁:通过装箱从关联中找到不同值的数量

时间:2018-11-28 06:36:13

标签: python arrays numpy

先决条件

这是此post的扩展。因此,对该问题的一些介绍将类似于该帖子。

问题

假设result是2D数组,values是1D数组。 values拥有与result中的每个元素相关的一些值。 values中的元素到result的映射存储在x_mappingy_mapping中。 result中的位置可以与不同的值相关联。 (x,y)x_mapping中的y_mapping对与results[-y,x]关联。我必须找到按关联分组的值的唯一计数。

一个更好地说明问题的例子。

result数组:

[[ 0.,  0.],
[ 0.,  0.],
[ 0.,  0.],
[ 0.,  0.]]

values数组:

[ 1.,  2.,  1.,  1.,  5.,  6.,  7.,  1.]

注意:这里的result数组和values具有相同数量的元素。但事实并非如此。大小之间根本没有关系。

x_mappingy_mapping具有从1D values到2D result的映射。 x_mappingy_mappingvalues的大小将相同。

x_mapping-[0, 1, 0, 0, 0, 0, 0, 0]

y_mapping-[0, 3, 2, 2, 0, 3, 2, 0]

在这里,第一个值(值[0]),第五个值(值[4])和第八个值(值[7])的x为0,y为0(x_mapping [0]和y_mappping [0])因此与result [0,0]相关联。如果我们计算该组(1,5,1)中不同值的计数,则结果将为2。 @WarrenWeckesser 让我们看看来自[1, 3]x_mapping的{​​{1}}(x,y)对如何贡献y_mapping。由于只有一个与该特定组关联的值,即2,因此results将具有一个值,因为与该单元格关联的不同值的数量为1。

另一个例子。让我们计算results[-3,1]的值。从映射来看,由于没有与单元格关联的值,因此results[-1,1]的值将为零。

类似地,results[-1,1]中的位置[-2, 0]的值为2。

请注意,如果根本没有关联,则results的默认值为零。

计算后的result

result

当前有效的解决方案

使用@Divakar的answer,我找到了可行的解决方案。

[[ 2.,  0.],
[ 1.,  1.],
[ 2.,  0.],
[ 0.,  0.]]

问题

现在,以上解决方案需要15ms才能对19943值进行运算。 我正在寻找一种更快地计算结果的方法。还有其他更有效的方法吗?

旁注

我正在将Numpy版本1.14.3与Python 3.5.2结合使用

编辑

感谢@WarrenWeckesser,指出我尚未说明x_mapping = np.array([0, 1, 0, 0, 0, 0, 0, 0]) y_mapping = np.array([0, 3, 2, 2, 0, 3, 2, 0]) values = np.array([ 1., 2., 1., 1., 5., 6., 7., 1.], dtype=np.float32) result = np.zeros([4, 2], dtype=np.float32) m,n = result.shape out_dtype = result.dtype lidx = ((-y_mapping)%m)*n + x_mapping sidx = lidx.argsort() idx = lidx[sidx] val = values[sidx] m_idx = np.flatnonzero(np.r_[True,idx[:-1] != idx[1:]]) unq_ids = idx[m_idx] r_res = np.zeros(m_idx.size, dtype=np.float32) for i in range(0, m_idx.shape[0]): _next = None arr = None if i == m_idx.shape[0]-1: _next = val.shape[0] else: _next = m_idx[i+1] _start = m_idx[i] if _start >= _next: arr = val[_start] else: arr = val[_start:_next] r_res[i] = np.unique(arr).size result.flat[unq_ids] = r_res 中的元素如何与映射中的results关联。为了清楚起见,我已经更新了帖子并添加了示例。

1 个答案:

答案 0 :(得分:1)

这是一种解决方法

import numpy as np

x_mapping = np.array([0, 1, 0, 0, 0, 0, 0, 0])
y_mapping = np.array([0, 3, 2, 2, 0, 3, 2, 0])
values = np.array([ 1.,  2.,  1.,  1.,  5.,  6.,  7.,  1.], dtype=np.float32)
result = np.zeros([4, 2], dtype=np.float32)

# Get flat indices
idx_mapping = np.ravel_multi_index((-y_mapping, x_mapping), result.shape, mode='wrap')
# Sort flat indices and reorders values accordingly
reorder = np.argsort(idx_mapping)
idx_mapping = idx_mapping[reorder]
values = values[reorder]
# Get unique values
val_uniq = np.unique(values)
# Find where each unique value appears
val_uniq_hit = values[:, np.newaxis] == val_uniq
# Find reduction indices (slices with the same flat index)
reduce_idx = np.concatenate([[0], np.nonzero(np.diff(idx_mapping))[0] + 1])
# Reduce slices
reduced = np.logical_or.reduceat(val_uniq_hit, reduce_idx)
# Count distinct values on each slice
counts = np.count_nonzero(reduced, axis=1)
# Put counts in result
result.flat[idx_mapping[reduce_idx]] = counts

print(result)
# [[2. 0.]
#  [1. 1.]
#  [2. 0.]
#  [0. 0.]]

此方法占用更多内存(O(len(values) * len(np.unique(values)))),但是与原始解决方案相比,较小的基准测试显示出显着的加速效果(尽管这取决于问题的实际大小):

import numpy as np

np.random.seed(100)
result = np.zeros([400, 200], dtype=np.float32)
values = np.random.randint(100, size=(20000,)).astype(np.float32)
x_mapping = np.random.randint(result.shape[1], size=values.shape)
y_mapping = np.random.randint(result.shape[0], size=values.shape)

res1 = solution_orig(x_mapping, y_mapping, values, result)
res2 = solution(x_mapping, y_mapping, values, result)
print(np.allclose(res1, res2))
# True

# Original solution
%timeit solution_orig(x_mapping, y_mapping, values, result)
# 76.2 ms ± 623 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

# This solution
%timeit solution(x_mapping, y_mapping, values, result)
# 13.8 ms ± 51.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

基准功能的完整代码:

import numpy as np

def solution(x_mapping, y_mapping, values, result):
    result = np.array(result)
    idx_mapping = np.ravel_multi_index((-y_mapping, x_mapping), result.shape, mode='wrap')
    reorder = np.argsort(idx_mapping)
    idx_mapping = idx_mapping[reorder]
    values = values[reorder]
    val_uniq = np.unique(values)
    val_uniq_hit = values[:, np.newaxis] == val_uniq
    reduce_idx = np.concatenate([[0], np.nonzero(np.diff(idx_mapping))[0] + 1])
    reduced = np.logical_or.reduceat(val_uniq_hit, reduce_idx)
    counts = np.count_nonzero(reduced, axis=1)
    result.flat[idx_mapping[reduce_idx]] = counts
    return result

def solution_orig(x_mapping, y_mapping, values, result):
    result = np.array(result)
    m,n = result.shape
    out_dtype = result.dtype
    lidx = ((-y_mapping)%m)*n + x_mapping

    sidx = lidx.argsort()
    idx = lidx[sidx]
    val = values[sidx]

    m_idx = np.flatnonzero(np.r_[True,idx[:-1] != idx[1:]])
    unq_ids = idx[m_idx]

    r_res = np.zeros(m_idx.size, dtype=np.float32)
    for i in range(0, m_idx.shape[0]):
        _next = None
        arr = None
        if i == m_idx.shape[0]-1:
            _next = val.shape[0]
        else:
            _next = m_idx[i+1]
        _start = m_idx[i]

        if _start >= _next:
            arr = val[_start]
        else:
            arr = val[_start:_next]
        r_res[i] = np.unique(arr).size
    result.flat[unq_ids] = r_res
    return result