2010-11-30 25 views
31

Me pregunto cuál es la mejor manera de iterar entradas distintas de cero de matrices dispersas con scipy.sparse. Por ejemplo, si hago lo siguiente:Iteración a través de un vector scipy.sparse (o matriz)

from scipy.sparse import lil_matrix 

x = lil_matrix((20,1)) 
x[13,0] = 1 
x[15,0] = 2 

c = 0 
for i in x: 
    print c, i 
    c = c+1 

la salida es

0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 (0, 0) 1.0 
14 
15 (0, 0) 2.0 
16 
17 
18 
19 

por lo que parece el iterador está en contacto con todos los elementos, no sólo los elementos no nulos. He echado un vistazo a la API

http://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.lil_matrix.html

y busqué un poco, pero me parece que no puede encontrar una solución que funcione.

Respuesta

43

Editar: bbtrb's method (usando coo_matrix) es mucho más rápido que mi sugerencia original, usando nonzero. La sugerencia de Sven Marnach de usar itertools.izip también mejora la velocidad. Actual más rápido es using_tocoo_izip:

import scipy.sparse 
import random 
import itertools 

def using_nonzero(x): 
    rows,cols = x.nonzero() 
    for row,col in zip(rows,cols): 
     ((row,col), x[row,col]) 

def using_coo(x): 
    cx = scipy.sparse.coo_matrix(x)  
    for i,j,v in zip(cx.row, cx.col, cx.data): 
     (i,j,v) 

def using_tocoo(x): 
    cx = x.tocoo()  
    for i,j,v in zip(cx.row, cx.col, cx.data): 
     (i,j,v) 

def using_tocoo_izip(x): 
    cx = x.tocoo()  
    for i,j,v in itertools.izip(cx.row, cx.col, cx.data): 
     (i,j,v) 

N=200 
x = scipy.sparse.lil_matrix((N,N)) 
for _ in xrange(N): 
    x[random.randint(0,N-1),random.randint(0,N-1)]=random.randint(1,100) 

produce estos timeit resultados:

% python -mtimeit -s'import test' 'test.using_tocoo_izip(test.x)' 
1000 loops, best of 3: 670 usec per loop 
% python -mtimeit -s'import test' 'test.using_tocoo(test.x)' 
1000 loops, best of 3: 706 usec per loop 
% python -mtimeit -s'import test' 'test.using_coo(test.x)' 
1000 loops, best of 3: 802 usec per loop 
% python -mtimeit -s'import test' 'test.using_nonzero(test.x)' 
100 loops, best of 3: 5.25 msec per loop 
+0

Obviamente es mejor. – Kabie

+0

¿Qué le parece usar 'izip()' en lugar de 'zip()'? Debería ser más rápido para matrices grandes. –

+0

@Sven Marnach: Gracias; de hecho, eso es más rápido. – unutbu

0

Pruebe filter(lambda x:x, x) en lugar de x.

27

La forma más rápida debe ser mediante la conversión a un coo_matrix:

cx = scipy.sparse.coo_matrix(x) 

for i,j,v in zip(cx.row, cx.col, cx.data): 
    print "(%d, %d), %s" % (i,j,v) 
+0

+1. Tienes razón; para matrices más grandes, esto es mucho más rápido. – unutbu

+0

¿Es más rápido convertir y luego iterar, o supongo que puedo cambiar para trabajar con coo_matrix? – RandomGuy

+0

@scandido: esto depende de lo que va a lograr. 'coo_matrix' es un formato muy simple y muy rápido de construir y acceder, pero puede ser inadecuado para otras tareas. Aquí hay una descripción general sobre los diferentes formatos de matriz http://www.scipy.org/SciPyPackages/Sparse y especialmente la sección "construir desde cero más rápido, con coo_matrix" – bbtrb

1

que tenían el mismo problema y, de hecho, si su preocupación es solo la velocidad, la manera más rápida (más de 1 orden de magnitud más rápido) es convertir la matriz dispersa en una densa (x.todense()) e iterando sobre los elementos distintos de cero en la matriz densa. (Aunque, por supuesto, este enfoque requiere mucha más memoria)

+0

¿Está proponiendo usar matrices densas todo el tiempo, o convertir a densas y luego iterando? No me puedo imaginar que este último sea más rápido. Pero, por supuesto, usar una matriz densa será mucho, mucho más rápido si tienes suficiente memoria. – RandomGuy

+0

Supongo que depende del escenario y del tipo de datos. He estado haciendo un poco de profilaxis en una secuencia de comandos que itera en matrices que contienen al menos 50-100M elementos booleanos. Al iterar, convertir a denso y luego iterar requiere mucho menos tiempo que iterar usando la 'mejor solución' de la respuesta de unutbu. Pero, por supuesto, el uso de la memoria aumenta MUCHO. –

1

tocoo() materializa toda la matriz en una estructura diferente, que no es el MO preferido para python 3. También puede considerar este iterador, que es especialmente útil para matrices grandes.

from itertools import chain, repeat 
def iter_csr(matrix): 
    for (row, col, val) in zip(
    chain(*(
      repeat(i, r) 
      for (i,r) in enumerate(comparisons.indptr[1:] - comparisons.indptr[:-1]) 
    )), 
    matrix.indices, 
    matrix.data 
): 
    yield (row, col, val) 

Tengo que admitir que estoy usando un montón de python-construcciones que posiblemente debería sustituirse por numpy constructos (especialmente enumerar).

NB:

In [43]: t=time.time(); sum(1 for x in rather_dense_sparse_matrix.data); print(time.time()-t) 
52.48686504364014 
In [44]: t=time.time(); sum(1 for x in enumerate(rather_dense_sparse_matrix.data)); print(time.time()-t) 
70.19013023376465 
In [45]: rather_dense_sparse_matrix 
<99829x99829 sparse matrix of type '<class 'numpy.float16'>' 
with 757622819 stored elements in Compressed Sparse Row format> 

Así que sí, enumerar es algo lento (más o menos)

Para el iterador:

In [47]: it = iter_csr(rather_dense_sparse_matrix) 
In [48]: t=time.time(); sum(1 for x in it); print(time.time()-t) 
113.something something 

por lo que decide si esta sobrecarga es aceptable, en mi caso el tocoo causó MemoryOverflows.

mi humilde opinión: un iterador tal debe ser parte de la interfaz de csr_matrix, similar a los elementos() en un dict() :)

+0

Se supone que es 'matrix.indptr' en lugar de' comparisons.indptr', ¿no? – zwol

+0

No, 'comparisons.indptr [1:] - comparisons.indptr [: - 1]' es un vector con las longitudes de las filas :) La parte de cadena/repetición es solo para iterar a través de los índices de fila, que son 0 0 0 ... 0 1 1 ... 1 2 2 ... 2 .... nn .... n (así es como funciona csr), así que simplemente repito la fila tantas veces como la cantidad de elementos en la fila, y encadena estos todos juntos. – Herbert

+0

¡Uh, no hay 'comparaciones' variables en el alcance! – zwol

2

Para bucle de una variedad de matrices dispersas de la sección scipy.sparse código me gustaría utilizar esta pequeña función de contenedor (tenga en cuenta que para Python-2 se le anima a utilizar xrange y izip para un mejor rendimiento en grandes matrices):

from scipy.sparse import * 
def iter_spmatrix(matrix): 
    """ Iterator for iterating the elements in a ``scipy.sparse.*_matrix`` 

    This will always return: 
    >>> (row, column, matrix-element) 

    Currently this can iterate `coo`, `csc`, `lil` and `csr`, others may easily be added. 

    Parameters 
    ---------- 
    matrix : ``scipy.sparse.sp_matrix`` 
     the sparse matrix to iterate non-zero elements 
    """ 
    if isspmatrix_coo(matrix): 
     for r, c, m in zip(matrix.row, matrix.col, matrix.data): 
      yield r, c, m 

    elif isspmatrix_csc(matrix): 
     for c in range(matrix.shape[1]): 
      for ind in range(matrix.indptr[c], matrix.indptr[c+1]): 
       yield matrix.indices[ind], c, matrix.data[ind] 

    elif isspmatrix_csr(matrix): 
     for r in range(matrix.shape[0]): 
      for ind in range(matrix.indptr[r], matrix.indptr[r+1]): 
       yield r, matrix.indices[ind], matrix.data[ind] 

    elif isspmatrix_lil(matrix): 
     for r in range(matrix.shape[0]): 
      for c, d in zip(matrix.rows[r], matrix.data[r]): 
       yield r, c, d 

    else: 
     raise NotImplementedError("The iterator for this sparse matrix has not been implemented") 
Cuestiones relacionadas