python - data - Encontrar el índice de una matriz numpy en una lista
index of element list python (4)
¿Que tal este?
arr = np.array([[1,2,3]])
foo = np.array([1, ''hello'', arr], dtype=np.object)
# if foo array is of heterogeneous elements (str, int, array)
[idx for idx, el in enumerate(foo) if type(el) == type(arr)]
# if foo array has only numpy arrays in it
[idx for idx, el in enumerate(foo) if np.array_equal(el, arr)]
Salida:
[2]
Nota: Esto también funcionará incluso si foo
es una lista. Simplemente lo puse como una matriz numpy
aquí.
import numpy as np
foo = [1, "hello", np.array([[1,2,3]]) ]
Yo esperaría
foo.index( np.array([[1,2,3]]) )
regresar
2
pero en cambio yo obtengo
ValueError: el valor de verdad de una matriz con más de un elemento es ambiguo. Use a.any () o a.all ()
algo mejor que mi solución actual? Parece ineficiente.
def find_index_of_array(list, array):
for i in range(len(list)):
if np.all(list[i]==array):
return i
find_index_of_array(foo, np.array([[1,2,3]]) )
# 2
El motivo del error aquí es, obviamente, porque numpy''s ndarray anula ==
para devolver una matriz en lugar de una booleana.
AFAIK, no hay una solución simple aquí. Lo siguiente funcionará mientras el
np.all(val == array)
funciona.
next((i for i, val in enumerate(lst) if np.all(val == array)), -1)
Si ese bit funciona o no depende críticamente de lo que son los otros elementos en el conjunto y si se pueden comparar con matrices numpy.
El problema aquí (probablemente ya lo sepas pero solo para repetirlo) es que list.index
funciona de la siguiente manera:
for idx, item in enumerate(your_list):
if item == wanted_item:
return idx
La línea if item == wanted_item
es el problema, porque convierte implícitamente item == wanted_item
en boolean. Pero numpy.ndarray
(excepto si es un escalar) plantea este ValueError
entonces:
ValueError: el valor de verdad de una matriz con más de un elemento es ambiguo. Use a.any () o a.all ()
Solución 1: clase de adaptador (envoltura delgada)
Generalmente utilizo un envoltorio delgado (adaptador) alrededor de numpy.ndarray
cada vez que necesito usar funciones de python como list.index
:
class ArrayWrapper(object):
__slots__ = ["_array"] # minimizes the memory footprint of the class.
def __init__(self, array):
self._array = array
def __eq__(self, other_array):
# array_equal also makes sure the shape is identical!
# If you don''t mind broadcasting you can also use
# np.all(self._array == other_array)
return np.array_equal(self._array, other_array)
def __array__(self):
# This makes sure that `np.asarray` works and quite fast.
return self._array
def __repr__(self):
return repr(self._array)
Estas envolturas delgadas son más costosas que manualmente utilizando algún ciclo de enumerate
o comprensión, pero no es necesario volver a implementar las funciones de Python. Suponiendo que la lista solo contenga matrices numpy (de lo contrario, debe hacer algunas comprobaciones if ... else ...
):
list_of_wrapped_arrays = [ArrayWrapper(arr) for arr in list_of_arrays]
Después de este paso, puede usar todas sus funciones de python en esta lista:
>>> list_of_arrays = [np.ones((3, 3)), np.ones((3)), np.ones((3, 3)) * 2, np.ones((3))]
>>> list_of_wrapped_arrays.index(np.ones((3,3)))
0
>>> list_of_wrapped_arrays.index(np.ones((3)))
1
Estas envolturas ya no son numpy-arrays pero tienes envoltorios delgados así que la lista extra es bastante pequeña. De modo que, dependiendo de sus necesidades, puede mantener la lista envuelta y la lista original y elegir las operaciones, por ejemplo, también puede list.count
las matrices idénticas ahora:
>>> list_of_wrapped_arrays.count(np.ones((3)))
2
o list.remove
:
>>> list_of_wrapped_arrays.remove(np.ones((3)))
>>> list_of_wrapped_arrays
[array([[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]]),
array([[ 2., 2., 2.],
[ 2., 2., 2.],
[ 2., 2., 2.]]),
array([ 1., 1., 1.])]
Solución 2: subclase y ndarray.view
Este enfoque usa subclases explícitas de numpy.array
. Tiene la ventaja de que obtienes toda la funcionalidad incorporada de la matriz y solo modificas la operación solicitada (que sería __eq__
):
class ArrayWrapper(np.ndarray):
def __eq__(self, other_array):
return np.array_equal(self, other_array)
>>> your_list = [np.ones(3), np.ones(3)*2, np.ones(3)*3, np.ones(3)*4]
>>> view_list = [arr.view(ArrayWrapper) for arr in your_list]
>>> view_list.index(np.array([2,2,2]))
1
De nuevo obtienes la mayoría de los métodos de lista de esta manera: list.remove
, list.count
además de list.index
.
Sin embargo, este enfoque puede producir un comportamiento sutil si alguna operación usa implícitamente __eq__
. Siempre se puede volver a interpretar como una matriz numpy simple utilizando np.asarray
o .view(np.ndarray)
:
>>> view_list[1]
ArrayWrapper([ 2., 2., 2.])
>>> view_list[1].view(np.ndarray)
array([ 2., 2., 2.])
>>> np.asarray(view_list[1])
array([ 2., 2., 2.])
Alternativa: __bool__
(o __nonzero__
para python 2)
En lugar de solucionar el problema en el método __eq__
, también puede anular __bool__
o __nonzero__
:
class ArrayWrapper(np.ndarray):
# This could also be done in the adapter solution.
def __bool__(self):
return bool(np.all(self))
__nonzero__ = __bool__
De nuevo, esto hace que la list.index
funcione como se list.index
:
>>> your_list = [np.ones(3), np.ones(3)*2, np.ones(3)*3, np.ones(3)*4]
>>> view_list = [arr.view(ArrayWrapper) for arr in your_list]
>>> view_list.index(np.array([2,2,2]))
1
¡Pero esto definitivamente modificará más comportamiento! Por ejemplo:
>>> if ArrayWrapper([1,2,3]):
... print(''that was previously impossible!'')
that was previously impossible!
Para el rendimiento, es posible que desee procesar solo las matrices NumPy en la lista de entrada. Entonces, podríamos verificar el tipo antes de entrar en el ciclo e indexar en los elementos que son matrices.
Por lo tanto, una implementación sería -
def find_index_of_array_v2(list1, array1):
idx = np.nonzero([type(i).__module__ == np.__name__ for i in list1])[0]
for i in idx:
if np.all(list1[i]==array1):
return i