tuplas - metodos de listas en python
Convierta de manera eficiente una lista desigual de listas en una matriz de contenido mÃnima rellenada con nan (4)
considere la lista de listas
l
l = [[1, 2, 3], [1, 2]]
si convierto esto en una
np.array
. obtendré una matriz de objetos unidimensional con
[1, 2, 3]
en la primera posición y
[1, 2]
en la segunda posición.
print(np.array(l))
[[1, 2, 3] [1, 2]]
Quiero esto en su lugar
print(np.array([[1, 2, 3], [1, 2, np.nan]]))
[[ 1. 2. 3.]
[ 1. 2. nan]]
Puedo hacer esto con un bucle, pero todos sabemos lo impopulares que son los bucles.
def box_pir(l):
lengths = [i for i in map(len, l)]
shape = (len(l), max(lengths))
a = np.full(shape, np.nan)
for i, r in enumerate(l):
a[i, :lengths[i]] = r
return a
print(box_pir(l))
[[ 1. 2. 3.]
[ 1. 2. nan]]
¿Cómo hago esto de una manera rápida y vectorizada?
sincronización
funciones de configuración
%%cython
import numpy as np
def box_pir_cython(l):
lengths = [len(item) for item in l]
shape = (len(l), max(lengths))
a = np.full(shape, np.nan)
for i, r in enumerate(l):
a[i, :lengths[i]] = r
return a
def box_divikar(v):
lens = np.array([len(item) for item in v])
mask = lens[:,None] > np.arange(lens.max())
out = np.full(mask.shape, np.nan)
out[mask] = np.concatenate(v)
return out
def box_hpaulj(LoL):
return np.array(list(zip_longest(*LoL, fillvalue=np.nan))).T
def box_simon(LoL):
max_len = len(max(LoL, key=len))
return np.array([x + [np.nan]*(max_len-len(x)) for x in LoL])
def box_dawg(LoL):
cols=len(max(LoL, key=len))
rows=len(LoL)
AoA=np.empty((rows,cols, ))
AoA.fill(np.nan)
for idx in range(rows):
AoA[idx,0:len(LoL[idx])]=LoL[idx]
return AoA
def box_pir(l):
lengths = [len(item) for item in l]
shape = (len(l), max(lengths))
a = np.full(shape, np.nan)
for i, r in enumerate(l):
a[i, :lengths[i]] = r
return a
def box_pandas(l):
return pd.DataFrame(l).values
Esto parece estar cerca de
this question
, donde el relleno estaba con
zeros
lugar de
NaNs
.
Se publicaron enfoques interesantes allí, junto con el
mine
basado en la
broadcasting
y
boolean-indexing
.
Entonces, simplemente modificaría una línea de mi publicación allí para resolver este caso así:
def boolean_indexing(v, fillval=np.nan):
lens = np.array([len(item) for item in v])
mask = lens[:,None] > np.arange(lens.max())
out = np.full(mask.shape,fillval)
out[mask] = np.concatenate(v)
return out
Ejecución de muestra:
In [32]: l
Out[32]: [[1, 2, 3], [1, 2], [3, 8, 9, 7, 3]]
In [33]: boolean_indexing(l)
Out[33]:
array([[ 1., 2., 3., nan, nan],
[ 1., 2., nan, nan, nan],
[ 3., 8., 9., 7., 3.]])
In [34]: boolean_indexing(l,-1)
Out[34]:
array([[ 1, 2, 3, -1, -1],
[ 1, 2, -1, -1, -1],
[ 3, 8, 9, 7, 3]])
He publicado pocos resultados de tiempo de ejecución allí para todos los enfoques publicados en esas preguntas y respuestas, lo que podría ser útil.
Podría escribir esto como una forma de asignación de divisiones en cada una de las sub matrices que se han llenado con un valor predeterminado:
def to_numpy(LoL, default=np.nan):
cols=len(max(LoL, key=len))
rows=len(LoL)
AoA=np.empty((rows,cols, ))
AoA.fill(default)
for idx in range(rows):
AoA[idx,0:len(LoL[idx])]=LoL[idx]
return AoA
Agregué la
indexación booleana
de Divakar como
f4
y agregué a la prueba de sincronización.
Al menos en mis pruebas, (Python 2.7 y Python 3.5; Numpy 1.11) no es el más rápido.
El tiempo muestra que
izip_longest
o
f2
es ligeramente más rápido para la mayoría de las listas, pero la asignación de corte (que es
f1
) es más rápida para listas más grandes:
from __future__ import print_function
import numpy as np
try:
from itertools import izip_longest as zip_longest
except ImportError:
from itertools import zip_longest
def f1(LoL):
cols=len(max(LoL, key=len))
rows=len(LoL)
AoA=np.empty((rows,cols, ))
AoA.fill(np.nan)
for idx in range(rows):
AoA[idx,0:len(LoL[idx])]=LoL[idx]
return AoA
def f2(LoL):
return np.array(list(zip_longest(*LoL,fillvalue=np.nan))).T
def f3(LoL):
max_len = len(max(LoL, key=len))
return np.array([x + [np.nan]*(max_len-len(x)) for x in LoL])
def f4(LoL):
lens = np.array([len(item) for item in LoL])
mask = lens[:,None] > np.arange(lens.max())
out = np.full(mask.shape,np.nan)
out[mask] = np.concatenate(LoL)
return out
if __name__==''__main__'':
import timeit
for case, LoL in ((''small'', [list(range(20)), list(range(30))] * 1000),
(''medium'', [list(range(20)), list(range(30))] * 10000),
(''big'', [list(range(20)), list(range(30))] * 100000),
(''huge'', [list(range(20)), list(range(30))] * 1000000)):
print(case)
for f in (f1, f2, f3, f4):
print(" ",f.__name__, timeit.timeit("f(LoL)", setup="from __main__ import f, LoL", number=100) )
Huellas dactilares:
small
f1 0.245459079742
f2 0.209980010986
f3 0.350691080093
f4 0.332141160965
medium
f1 2.45869493484
f2 2.32307982445
f3 3.65722203255
f4 3.55545687675
big
f1 25.8796288967
f2 26.6177148819
f3 41.6916451454
f4 41.3140149117
huge
f1 262.429639101
f2 295.129109859
f3 427.606887817
f4 441.810388088
Probablemente la versión de lista más rápida usa
itertools.zip_longest
(puede ser
izip_longest
en Py2):
In [747]: np.array(list(itertools.zip_longest(*ll,fillvalue=np.nan))).T
Out[747]:
array([[ 1., 2., 3.],
[ 1., 2., nan]])
La
zip
simple produce:
In [748]: list(itertools.zip_longest(*ll))
Out[748]: [(1, 1), (2, 2), (3, None)]
otro zip ''transpone'':
In [751]: list(zip(*itertools.zip_longest(*ll)))
Out[751]: [(1, 2, 3), (1, 2, None)]
A menudo, al comenzar con listas (o incluso una matriz de listas de objetos), es más rápido seguir con los métodos de lista. Hay una sobrecarga sustancial en la creación de una matriz o marco de datos.
Esta no es la primera vez que se hace esta pregunta.
¿Cómo puedo rellenar y / o truncar un vector a una longitud específica usando numpy?
Mi respuesta allí incluye tanto este
zip_longest
como tu
box_pir
Creo que también hay una versión numpy rápida que usa una matriz aplanada, pero no recuerdo los detalles. Probablemente fue dado por Warren o Divakar.
Creo que la versión ''aplanada'' funciona algo en esta línea:
In [809]: ll
Out[809]: [[1, 2, 3], [1, 2]]
In [810]: sll=np.hstack(ll) # all values in a 1d array
In [816]: res=np.empty((2,3)); res.fill(np.nan) # empty target
obtener índices aplanados donde van los valores.
Este es el paso crucial.
Aquí el uso de
r_
es iterativo;
la versión rápida probablemente usa
cumsum
In [817]: idx=np.r_[0:3, 3:3+2]
In [818]: idx
Out[818]: array([0, 1, 2, 3, 4])
In [819]: res.flat[idx]=sll
In [820]: res
Out[820]:
array([[ 1., 2., 3.],
[ 1., 2., nan]])
================
entonces el enlace que falta es
>np.arange()
broadcasting
In [897]: lens=np.array([len(i) for i in ll])
In [898]: mask=lens[:,None]>np.arange(lens.max())
In [899]: mask
Out[899]:
array([[ True, True, True],
[ True, True, False]], dtype=bool)
In [900]: idx=np.where(mask.ravel())
In [901]: idx
Out[901]: (array([0, 1, 2, 3, 4], dtype=int32),)
Tal vez algo como esto? No sé acerca de su hardware, pero significa a 16 ms para 100 bucles para l2 = [lista (rango (20)), lista (rango (30))] * 10000.
from numpy import nan
def box(l):
max_lenght = len(max(l, key=len))
return [x + [nan]*(max_lenght-len(x)) for x in l]