将索引列表转换为2D numpy数组的最快方法

时间:2019-06-19 05:07:50

标签: python arrays performance numpy

我有一个索引列表

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,但是有没有一种更快/矢量化的方法来做到这一点?

我的用例可能有成千上万的行/列,而我需要这样做数千次,所以速度越快越好。

6 个答案:

答案 0 :(得分:10)

如何?

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")

答案 1 :(得分:6)

这可能不是最快的方法。您将需要使用大型数组比较这些答案的执行时间,以找出最快的方法。这是我的解决方法

CREATE TABLEinventry.users(
    id INT NOT NULL AUTO_INCREMENT,
    username VARCHAR(225) NOT NULL,
    email VARCHAR(225) NOT NULL,
    password VARCHAR(300) NOT NULL,
    usertype ENUM(**'0'**) NOT NULL,
    register_dateDATETIME NOT NULL,
    last_loginDATETIME NOT NULL,
    notesVARCHAR(225) NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;

答案 2 :(得分:4)

可能不是最好的方法,但我能想到的唯一方法是

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.]]

答案 3 :(得分:3)

如果可以并且想使用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

enter image description here

请注意,这使用对数轴来同时显示小型阵列和大型阵列的差异。根据我的基准,我的功能实际上是解决方案中最快的,但是也值得指出的是,所有解决方案之间的距离都不太远。

这是我用于基准测试的完整代码:

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()

答案 4 :(得分:1)

根据您的用例,您可能会考虑使用稀疏矩阵。输入矩阵看起来像一个Compressed Sparse Row (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矩阵的另一个很好的介绍是Iterative Methods的3.4节。在这种情况下,dataaaindicesja,而indptria。这种格式还具有在不同的程序包/库中非常受欢迎的好处。

答案 5 :(得分:0)

如何使用数组索引?如果您对输入了解更多,则可以摆脱必须先转换为线性数组的代价。

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.]]