[英]Fastest way to convert a list of indices to 2D numpy array of ones
我有一个索引列表
a = [
[1,2,4],
[0,2,3],
[1,3,4],
[0,2]]
将其转换为numpy数组的最快方法是什么,其中每个索引都将显示发生1的位置?
即我想要的是:
output = array([
[0,1,1,0,1],
[1,0,1,1,0],
[0,1,0,1,1],
[1,0,1,0,0]])
我事先知道数组的最大大小。 我知道我可以遍历每个列表并将1插入每个索引位置,但是有没有一种更快/矢量化的方法来做到这一点?
我的用例可能有成千上万的行/列,我需要做数千次,所以速度越快越好。
这个怎么样:
ncol = 5
nrow = len(a)
out = np.zeros((nrow, ncol), int)
out[np.arange(nrow).repeat([*map(len,a)]), np.concatenate(a)] = 1
out
# array([[0, 1, 1, 0, 1],
# [1, 0, 1, 1, 0],
# [0, 1, 0, 1, 1],
# [1, 0, 1, 0, 0]])
以下是1000x1000二进制数组的计时,请注意,我使用了上面的优化版本,请参见下面的函数pp
:
pp 21.717635259992676 ms
ts 37.10938713003998 ms
u9 37.32933565042913 ms
产生计时的代码:
import itertools as it
import numpy as np
def make_data(n,m):
I,J = np.where(np.random.random((n,m))<np.random.random((n,1)))
return [*map(np.ndarray.tolist, np.split(J, I.searchsorted(np.arange(1,n))))]
def pp():
sz = np.fromiter(map(len,a),int,nrow)
out = np.zeros((nrow,ncol),int)
out[np.arange(nrow).repeat(sz),np.fromiter(it.chain.from_iterable(a),int,sz.sum())] = 1
return out
def ts():
out = np.zeros((nrow,ncol),int)
for i, ix in enumerate(a):
out[i][ix] = 1
return out
def u9():
out = np.zeros((nrow,ncol),int)
for i, (x, y) in enumerate(zip(a, out)):
y[x] = 1
out[i] = y
return out
nrow,ncol = 1000,1000
a = make_data(nrow,ncol)
from timeit import timeit
assert (pp()==ts()).all()
assert (pp()==u9()).all()
print("pp", timeit(pp,number=100)*10, "ms")
print("ts", timeit(ts,number=100)*10, "ms")
print("u9", timeit(u9,number=100)*10, "ms")
这可能不是最快的方法。 您将需要使用大型数组比较这些答案的执行时间,以找出最快的方法。 这是我的解决方案
output = np.zeros((4,5))
for i, ix in enumerate(a):
output[i][ix] = 1
# output ->
# array([[0, 1, 1, 0, 1],
# [1, 0, 1, 1, 0],
# [0, 1, 0, 1, 1],
# [1, 0, 1, 0, 0]])
可能不是最好的方法,但我能想到的唯一方法是:
output = np.zeros((4,5))
for i, (x, y) in enumerate(zip(a, output)):
y[x] = 1
output[i] = y
print(output)
哪个输出:
[[ 0. 1. 1. 0. 1.]
[ 1. 0. 1. 1. 0.]
[ 0. 1. 0. 1. 1.]
[ 1. 0. 1. 0. 0.]]
如果可以并且想要使用Cython ,则可以创建一个可读的(至少在您不介意打字的情况下)快速的解决方案。
在这里,我使用Cython的IPython绑定在Jupyter笔记本中对其进行编译:
%load_ext cython
%%cython
cimport cython
cimport numpy as cnp
import numpy as np
@cython.boundscheck(False) # remove this if you cannot guarantee that nrow/ncol are correct
@cython.wraparound(False)
cpdef cnp.int_t[:, :] mseifert(list a, int nrow, int ncol):
cdef cnp.int_t[:, :] out = np.zeros([nrow, ncol], dtype=int)
cdef list subl
cdef int row_idx
cdef int col_idx
for row_idx, subl in enumerate(a):
for col_idx in subl:
out[row_idx, col_idx] = 1
return out
为了比较此处介绍的解决方案的性能,我使用了我的库simple_benchmark
:
请注意,这使用对数轴来同时显示小型阵列和大型阵列的差异。 根据我的基准,我的功能实际上是解决方案中最快的,但是也值得一提的是,所有解决方案都相差不远。
这是我用于基准测试的完整代码:
import numpy as np
from simple_benchmark import BenchmarkBuilder, MultiArgument
import itertools
b = BenchmarkBuilder()
@b.add_function()
def pp(a, nrow, ncol):
sz = np.fromiter(map(len, a), int, nrow)
out = np.zeros((nrow, ncol), int)
out[np.arange(nrow).repeat(sz), np.fromiter(itertools.chain.from_iterable(a), int, sz.sum())] = 1
return out
@b.add_function()
def ts(a, nrow, ncol):
out = np.zeros((nrow, ncol), int)
for i, ix in enumerate(a):
out[i][ix] = 1
return out
@b.add_function()
def u9(a, nrow, ncol):
out = np.zeros((nrow, ncol), int)
for i, (x, y) in enumerate(zip(a, out)):
y[x] = 1
out[i] = y
return out
b.add_functions([mseifert])
@b.add_arguments("number of rows/columns")
def argument_provider():
for n in range(2, 13):
ncols = 2**n
a = [
sorted(set(np.random.randint(0, ncols, size=np.random.randint(0, ncols))))
for _ in range(ncols)
]
yield ncols, MultiArgument([a, ncols, ncols])
r = b.run()
r.plot()
根据您的用例,您可能会考虑使用稀疏矩阵。 输入矩阵看起来像可压缩的稀疏行(CSR)矩阵。 也许像
import numpy as np
from scipy.sparse import csr_matrix
from itertools import accumulate
def ragged2csr(inds):
offset = len(inds[0])
lens = [len(x) for x in inds]
indptr = list(accumulate(lens))
indptr = np.array([x - offset for x in indptr])
indices = np.array([val for sublist in inds for val in sublist])
n = indices.size
data = np.ones(n)
return csr_matrix((data, indices, indptr))
同样,如果适合您的用例,则稀疏矩阵将允许按元素/遮罩操作按非零数量而不是元素数量(行*列)缩放,这可能带来显着的加速(对于稀疏的矩阵) )。
CSR矩阵的另一个很好的介绍是“ 迭代方法”的第3.4节。 在这种情况下, data
为aa
, indices
为ja
, indptr
为ia
。 这种格式还具有在不同的程序包/库中非常受欢迎的好处。
如何使用数组索引? 如果您对输入了解更多,则可以摆脱必须先转换为线性数组的代价。
import numpy as np
def main():
row_count = 4
col_count = 5
a = [[1,2,4],[0,2,3],[1,3,4],[0,2]]
# iterate through each row, concatenate all indices and convert them to linear
# numpy append performs copy even if you don't want it, list append is faster
b = []
for row_idx, row in enumerate(a):
b.append(np.array(row, dtype=np.int64) + (row_idx * col_count))
linear_idxs = np.hstack(b)
#could skip previous steps if given index inputs well before hand, or in linear index order.
c = np.zeros(row_count * col_count)
c[linear_idxs] = 1
c = c.reshape(row_count, col_count)
print(c)
if __name__ == "__main__":
main()
#output
# [[0. 1. 1. 0. 1.]
# [1. 0. 1. 1. 0.]
# [0. 1. 0. 1. 1.]
# [1. 0. 1. 0. 0.]]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.