2

我有一个二维 numpy 数组,我希望将每个元素四舍五入到序列中最接近的数字。数组有 shape (28000, 24)

例如,序列将是[0, 0.05, 0.2, 0.33, 0.5]

例如,原件0.27将四舍五入为0.33,并将0.42四舍五入为0.5

这是我到目前为止使用的,但是双循环当然非常慢。

MWE:

arr = np.array([[0.14, 0.18], [0.20, 0.27]])
new = []
sequence = np.array([0, 0.05, 0.2, 0.33, 0.5])
for i in range(len(arr)):
    row = []
    for j in range(len(arr[0])):
        temp = (arr[i][j] - sequence)**2
        row.append(list(sequence[np.where(temp == min(temp))])[0])
    new.append(row)

结果:

[[0.2000001, 0.2000001], [0.2000001, 0.33000001]]  

动机:

在机器学习中,我在做预测。由于结果反映了专家的信心,因此可能是 2/3 给出了 1(因此为 0.66)。因此,在该数据中,会出现相对较多的 0、0.1、0.2、0.33、0.66、0.75 等。然而,我的预测类似于 0.1724。在这种情况下,我会通过四舍五入到 0.2 来消除很多预测误差。

如何优化舍入所有元素?

更新:我现在预先分配了内存,所以不必不断地追加。

 # new = [[0]*len(arr[0])] * len(arr), then unloading into new[i][j],
 # instead of appending 

时间:

Original problem: 36.62 seconds
Pre-allocated array: 15.52 seconds  
shx2 SOLUTION 1 (extra dimension): 0.47 seconds
shx2 SOLUTION 2 (better for big arrays): 4.39 seconds
Jaime's np.digitize: 0.02 seconds
4

3 回答 3

4

可以围绕np.digitize.

>>> def round_to_sequence(arr, seq):
...     rnd_thresholds = np.add(seq[:-1], seq[1:]) / 2
...     arr = np.asarray(arr)
...     idx = np.digitize(arr.ravel(), rnd_thresholds).reshape(arr.shape)
...     return np.take(seq, idx)
... 
>>> round_to_sequence([[0.14, 0.18], [0.20, 0.27]],
...                   [0, 0.05, 0.2, 0.33, 0.5])
array([[ 0.2 ,  0.2 ],
       [ 0.2 ,  0.33]])

更新 所以发生了什么......函数中的第一行计算出序列中项目之间的中点是什么。这个值是四舍五入的阈值:低于它,你必须向下舍入,高于它,你必须向上舍入。我使用np.add, 而不是更清晰的seq[:-1] + seq[1:],以便它接受列表或元组,而无需将其显式转换为 numpy 数组。

>>> seq = [0, 0.05, 0.2, 0.33, 0.5]
>>> rnd_threshold = np.add(seq[:-1], seq[1:]) / 2
>>> rnd_threshold
array([ 0.025,  0.125,  0.265,  0.415])

接下来,我们用np.digitize这些阈值来找出数组中的每个项目在哪个 bin 中。np.digitize只需要一维数组,所以我们必须做.ravel加号.reshape来保持数组的原始形状。照原样,它使用限制项向上舍入的标准约定,您可以通过使用right关键字参数来反转此行为。

>>> arr = np.array([[0.14, 0.18], [0.20, 0.27]])
>>> idx = np.digitize(arr.ravel(), seq).reshape(arr.shape)
>>> idx
array([[2, 2],
       [3, 3]], dtype=int64)

现在我们需要做的就是创建一个形状为 的数组idx,使用它的条目来索引要四舍五入的值序列。这可以通过 来实现seq[idx]但通常(总是?)使用.np.take

>>> np.take(seq, idx)
array([[ 0.2 ,  0.2 ],
       [ 0.33,  0.33]])
于 2013-11-01T12:45:33.127 回答
1

原始问题

原始问题表明OP想要四舍五入到最近的0.1,它具有以下简单的解决方案......

真的很简单——让numpy我们为你做吧:

arr = np.array([[0.14, 0.18], [0.20, 0.27]])
numpy.around(arr, decimals=1)

在使用 Python 开发科学软件时,尽可能避免循环是关键。如果numpy有程序可以做某事,请使用它。

于 2013-11-01T08:09:40.527 回答
1

我想为您的问题提出两种解决方案。第一个是纯 numpy 解决方案,但如果你的原始数组是 NxM,序列大小是 K,它使用大小为 NxMxK 的数组。所以这个解决方案只有在你的情况下这个尺寸不是很大的时候才是好的。尽管使用了大数组,但它仍然可以非常快,用于在 numpy 空间中完成所有工作。

第二种是混合方法(结果证明代码也更简单),使用@np.vectorize. 它确实在 numpy 空间中循环,但为每个元素回调 python。好处是它避免了创建巨大的数组。

两者都是有效的解决方案。您选择最适合您的阵列大小的一个。

此外,两者都适用于具有任意维数的数组。

解决方案 1

import numpy as np

a = np.random.random((2,4))
a
=> 
array([[ 0.5501662 ,  0.13055979,  0.579619  ,  0.3161156 ],
       [ 0.07327783,  0.45156743,  0.38334009,  0.48772392]])

seq = np.array([ 0.1, 0.3, 0.6, 0.63 ])

# create 3-dim array of all the distances
all_dists = np.abs(a[..., np.newaxis] - seq)
all_dists.shape
=> (2, 4, 4)
all_dists
=>
array([[[ 0.4501662 ,  0.2501662 ,  0.0498338 ,  0.0798338 ],
        [ 0.03055979,  0.16944021,  0.46944021,  0.49944021],
        [ 0.479619  ,  0.279619  ,  0.020381  ,  0.050381  ],
        [ 0.2161156 ,  0.0161156 ,  0.2838844 ,  0.3138844 ]],

       [[ 0.02672217,  0.22672217,  0.52672217,  0.55672217],
        [ 0.35156743,  0.15156743,  0.14843257,  0.17843257],
        [ 0.28334009,  0.08334009,  0.21665991,  0.24665991],
        [ 0.38772392,  0.18772392,  0.11227608,  0.14227608]]])

# find where each element gets its closest, i.e. min dist
closest_idxs = all_dists.argmin(axis = -1)
closest_idxs
=> 
array([[2, 0, 2, 1],
       [0, 2, 1, 2]])

# choose
seq[closest_idxs]
=>
array([[ 0.6,  0.1,  0.6,  0.3],
       [ 0.1,  0.6,  0.3,  0.6]])

解决方案 2

@np.vectorize
def find_closest(x):
    dists = np.abs(x-seq)
    return seq[dists.argmin()]

find_closest(a)
=> 
array([[ 0.6,  0.1,  0.6,  0.3],
       [ 0.1,  0.6,  0.3,  0.6]])
于 2013-11-01T08:28:25.887 回答