如何更快地读取/遍历/切片Scipy稀疏矩阵(LIL,CSR,COO,DOK)?


问题内容

通常,要使用Scipy矩阵,请使用内置方法。但是有时您需要读取矩阵数据以将其分配给非稀疏数据类型。为了演示,我创建了一个随机的LIL稀疏矩阵,并使用不同的方法将其转换为Numpy数组(纯python数据类型会更好!)。

from __future__ import print_function
from scipy.sparse import rand, csr_matrix, lil_matrix
import numpy as np

dim = 1000
lil = rand(dim, dim, density=0.01, format='lil', dtype=np.float32, random_state=0)
print('number of nonzero elements:', lil.nnz)
arr = np.zeros(shape=(dim,dim), dtype=float)

非零元素数:10000

通过索引阅读

%%timeit -n3
for i in xrange(dim):
    for j in xrange(dim):
        arr[i,j] = lil[i,j]

3个循环,每个循环最好3:6.42 s

使用nonzero()方法

%%timeit -n3
nnz = lil.nonzero() # indices of nonzero values
for i, j in zip(nnz[0], nnz[1]):
    arr[i,j] = lil[i,j]

3个循环,最好为3:每个循环75.8 ms

使用内置方法直接转换为数组

不是 读取矩阵数据的通用解决方案,因此不算作解决方案。

%timeit -n3 arr = lil.toarray()

3个循环,每循环最好3:7.85毫秒

用这些方法读取Scipy稀疏矩阵根本没有效率。有没有更快的方法来读取这些矩阵?


问题答案:

尝试读取原始数据。Scipy稀疏矩阵存储在Numpy ndarray中,每个矩阵具有不同的格式。

读取LIL稀疏矩阵的原始数据

%%timeit -n3
for i, (row, data) in enumerate(zip(lil.rows, lil.data)):
    for j, val in zip(row, data):
        arr[i,j] = val

3 loops, best of 3: 4.61 ms per loop

读取CSR稀疏矩阵的原始数据

对于csr矩阵,从原始数据中读取的Python语言要少一些,但是值得这样做。

csr = lil.tocsr()

%%timeit -n3
start = 0
for i, end in enumerate(csr.indptr[1:]):
    for j, val in zip(csr.indices[start:end], csr.data[start:end]):
        arr[i,j] = val
    start = end

3 loops, best of 3: 8.14 ms per loop

此DBSCAN实现中使用了类似的方法。

读取COO稀疏矩阵的原始数据

%%timeit -n3
for i,j,d in zip(coo.row, coo.col, coo.data):
    arr[i,j] = d

3 loops, best of 3: 5.97 ms per loop

根据这些有限的测试:

  • COO矩阵:最干净
  • LIL矩阵:最快
  • CSR矩阵:最慢和最丑。唯一的好处是,与CSR的转换非常快。

编辑:从@hpaulj,我添加了COO矩阵以将所有方法都放在一个位置。