将数组四舍五入为另一个数组中给定的值

Rounding an array to values given in another array

假设我有一个数组:

values = np.array([1.1,2.2,3.3,4.4,2.1,8.4])

我想将这些值四舍五入为任意数组的成员,例如:

rounds = np.array([1.,3.5,5.1,6.7,9.2])

理想情况下返回一个四舍五入的数字数组和一个残数数组:

rounded = np.array([1.,1.,3.5,5.1,1.,9.2])
residues = np.array([-0.1,-1.2,0.2,0.7,-1.1,0.6])

有没有好的 pythonic 方法来做到这一点?

找到最接近的x轮数:

def findClosest(x,rounds):
    return rounds[np.argmin(np.absolute(rounds-x))]

遍历所有值:

rounded = [findClosest(x,rounds) for x in values]
residues = values - rounded

这是一种简单的方法,但使用有序的回合数组可以提高效率。

def findClosest(x,rounds):
    for n in range(len(rounds)):
        if x > rounds[n]:
            if n == 0:
                return rounds[n]
            elif rounds[n]-x > x-rounds[n-1]:
                return rounds[n-1]
            else:
                return rounds[n]  

        return rounds[-1]

这可能比 argmin 方法快,但不一定比 argmin 方法快,因为您在 python for 循环中浪费了时间,但您不必检查整个回合数组。

一个选项是:

>>> x = np.subtract.outer(values, rounds)
>>> y = np.argmin(abs(x), axis=1)

然后roundedresidues分别是:

>>> rounds[y]
array([ 1. ,  1. ,  3.5,  5.1,  1. ,  9.2])

>>> rounds[y] - values
array([-0.1, -1.2,  0.2,  0.7, -1.1,  0.8])

本质上 xvalues 中的每个值减去 rounds 中的每个值的二维数组。 yx每一行绝对值最小的索引的一维数组。这个 y 然后用于索引 rounds.

我应该警告这个答案,注意如果 len(values) * len(rounds) 很大(例如开始超过 10e8),内存使用可能会开始成为问题。在这种情况下,您可以考虑迭代地构建 y,以避免必须将大块内存分配给 x

由于 rounds 数组中的项目已排序(或者如果未对它们排序),我们可以使用 numpy.searchsorted:

O(n logn) 时间
from functools import partial

def closest(rounds, x):
   ind = np.searchsorted(rounds, x, side='right')
   length = len(rounds)
   if ind in (0, length) :
      return rounds[ind]
   else:
      left, right = rounds[ind-1], rounds[ind]
      val = min((left, right), key=lambda y:abs(x-y))
      return val

f = partial(closest, rounds)
rounded = np.apply_along_axis(f, 1, values[:,None])[:,0]
residues = rounded - values
print repr(rounded)
print repr(residues)

输出:

array([ 1. ,  1. ,  3.5,  5.1,  1. ,  9.2])
array([-0.1, -1.2,  0.2,  0.7, -1.1,  0.8])

选择的答案已经很棒了。对于那些不一定习惯于更复杂的列表理解的人来说,这可能看起来很复杂,但如果你熟悉它,它实际上很清楚 (IMO)。

(有趣的是,这恰好 运行 比选择的答案快。为什么 numPy 版本会比这个慢?嗯...)

values = np.array([1.1,2.2,3.3,4.4,2.1,8.4])
rounds = np.array([1.,3.5,5.1,6.7,9.2])

rounded, residues = zip(*[
    [
        (rounds[cIndex]),
        (dists[cIndex])
    ]
    for v in values
    for dists in [[r-v for r in rounds]]
    for absDists in [[abs(d) for d in dists]]
    for cIndex in [absDists.index(min(absDists))]
])

print np.array(rounded)
print np.array(residues)

时间复杂度与 Ashwini Chaudhary 的答案相同,但完全矢量化:

def round_to(rounds, values):
    # The main speed is in this line
    I = np.searchsorted(rounds, values)

    # Pad so that we can index easier
    rounds_p = np.pad(rounds, 1, mode='edge')

    # We have to decide between I and I+1
    rounded = np.vstack([rounds_p[I], rounds_p[I+1]])
    residues = rounded - values
    J = np.argmin(np.abs(residues), axis=0)

    K = np.arange(len(values))
    return rounded[J,K], residues[J,K]