我有一个2维numpy数组,我希望每个元素都舍入到序列中最接近的数字。数组的形状为(28000, 24)
。
例如,序列为[0, 0.05, 0.2, 0.33, 0.5]
。
E.g。原始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
答案 0 :(得分: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
只需要1D数组,因此我们必须执行.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]
实现,但通常(总是?)更快(请here)使用np.take
。
>>> np.take(seq, idx)
array([[ 0.2 , 0.2 ],
[ 0.33, 0.33]])
答案 1 :(得分:1)
最初的问题表明OP想要舍入到最近的0.1
,这有以下简单的解决方案......
非常简单 - 让numpy
为您完成:
arr = np.array([[0.14, 0.18], [0.20, 0.27]])
numpy.around(arr, decimals=1)
在Python中开发科学软件时,尽可能避免循环是关键。如果numpy
有程序要做某事,请使用它。
答案 2 :(得分: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]])