python - una - transform np array in list




Il modo più veloce per convertire un elenco di indici in serie numpy 2D di quelli (4)

Ho una lista di indici

a = [
  [1,2,4],
  [0,2,3],
  [1,3,4],
  [0,2]]

Qual è il modo più veloce per convertire questo in una serie numerica di quelli, in cui ogni indice mostra la posizione in cui 1 si verificherebbe?

Cioè quello che voglio è:

output = array([
  [0,1,1,0,1],
  [1,0,1,1,0],
  [0,1,0,1,1],
  [1,0,1,0,0]])

Conosco in anticipo la dimensione massima dell'array. So che potrei scorrere ogni lista e inserire un 1 in ogni posizione dell'indice, ma c'è un modo più veloce / vettoriale per farlo?

Il mio caso d'uso potrebbe avere migliaia di righe / colonne e ho bisogno di farlo migliaia di volte, quindi più veloce è, meglio è.


Che ne dici di utilizzare l'indicizzazione degli array? Se sapessi di più sul tuo input, potresti eliminare la penalità per prima cosa devi convertire in un array lineare.

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

Cosa ne pensi di questo:

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

Qui ci sono i tempi per un array binario 1000x1000, nota che uso una versione ottimizzata di quanto sopra, vedi la funzione pp seguito:

pp 21.717635259992676 ms
ts 37.10938713003998 ms
u9 37.32933565042913 ms

Codice per produrre tempi:

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

Potrebbe non essere il modo migliore ma l'unico modo in cui posso pensare:

output = np.zeros((4,5))
for i, (x, y) in enumerate(zip(a, output)):
    y[x] = 1
    output[i] = y
print(output)

Quali uscite:

[[ 0.  1.  1.  0.  1.]
 [ 1.  0.  1.  1.  0.]
 [ 0.  1.  0.  1.  1.]
 [ 1.  0.  1.  0.  0.]]

Questo potrebbe non essere il modo più veloce. Dovrai confrontare i tempi di esecuzione di queste risposte utilizzando array di grandi dimensioni per scoprire il modo più veloce. Ecco la mia soluzione

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




numpy