transpuesta - sumar columnas de una matriz python
probando si una matriz Numpy contiene una fila dada (4)
¿Existe una manera pitónica y eficiente de verificar si una matriz Numpy contiene al menos una instancia de una fila determinada? Por "eficiente" quiero decir que termina al encontrar la primera fila coincidente en lugar de iterar sobre toda la matriz, incluso si ya se ha encontrado un resultado.
Con las matrices de Python, esto se puede lograr de forma muy clara con if row in array:
pero esto no funciona como esperaría para las matrices de Numpy, como se ilustra a continuación.
Con las matrices de Python:
>>> a = [[1,2],[10,20],[100,200]]
>>> [1,2] in a
True
>>> [1,20] in a
False
pero las matrices Numpy dan resultados diferentes y bastante extraños. (El método __contains__
de ndarray
parece estar indocumentado).
>>> a = np.array([[1,2],[10,20],[100,200]])
>>> np.array([1,2]) in a
True
>>> np.array([1,20]) in a
True
>>> np.array([1,42]) in a
True
>>> np.array([42,1]) in a
False
Numpys __contains__
es, en el momento de escribir esto, (a == b).any()
que es posiblemente solo correcto si b
es un escalar (es un poco escabroso, pero creo que funciona así solo en 1.7. más tarde - este sería el método general correcto (a == b).all(np.arange(a.ndim - b.ndim, a.ndim)).any()
, que tiene sentido para todas las combinaciones de b
dimensionalidad) ...
EDITAR: Para ser claros, este no es necesariamente el resultado esperado cuando se trata de la transmisión. También alguien podría argumentar que debería manejar los artículos en a
separada como np.in1d
hace np.in1d
. No estoy seguro de que haya una manera clara de que funcione.
Ahora quiere que Numpy se detenga cuando encuentre la primera ocurrencia. Este AFAIK no existe en este momento. Es difícil porque numpy se basa principalmente en ufuncs, que hacen lo mismo en toda la matriz. Numpy optimiza este tipo de reducciones, pero efectivamente solo funciona cuando la matriz que se está reduciendo ya es una matriz booleana (es decir, np.ones(10, dtype=bool).any()
).
De lo contrario, necesitaría una función especial para __contains__
que no existe. Puede parecer extraño, pero debe recordar que Numpy admite muchos tipos de datos y tiene una maquinaria más grande para seleccionar los correctos y seleccionar la función correcta para trabajar en él. Entonces, en otras palabras, la maquinaria ufunc no puede hacerlo, y la implementación de __contains__
o such especialmente no es tan trivial debido a los tipos de datos.
Por supuesto, puedes escribirlo en python, o como probablemente conozcas tu tipo de datos, escribirlo tú mismo en Cython / C es muy simple.
Eso dijo. A menudo, de todos modos, es mucho mejor utilizar el enfoque basado en clasificación para estas cosas. Eso es un poco tedioso y no existe la searchsorted
de searchsorted
de un lexsort
, pero funciona (también se puede abusar de scipy.spatial.cKDTree
si se quiere). Esto supone que desea comparar únicamente a lo largo del último eje:
# Unfortunatly you need to use structured arrays:
sorted = np.ascontiguousarray(a).view([('''', a.dtype)] * a.shape[-1]).ravel()
# Actually at this point, you can also use np.in1d, if you already have many b
# then that is even better.
sorted.sort()
b_comp = np.ascontiguousarray(b).view(sorted.dtype)
ind = sorted.searchsorted(b_comp)
result = sorted[ind] == b_comp
Esto también funciona para una matriz b
, y si mantiene la matriz ordenada, también es mucho mejor si lo hace por un único valor (fila) en b
a la vez, cuando a
permanece igual (de lo contrario, simplemente lo haría np.in1d
después de verlo como un recarray). Importante: debe hacer el np.ascontiguousarray
por seguridad. Por lo general, no hará nada, pero si lo hace, sería un gran error potencial de lo contrario.
Puedes usar .tolist ()
>>> a = np.array([[1,2],[10,20],[100,200]])
>>> [1,2] in a.tolist()
True
>>> [1,20] in a.tolist()
False
>>> [1,20] in a.tolist()
False
>>> [1,42] in a.tolist()
False
>>> [42,1] in a.tolist()
False
O use una vista:
>>> any((a[:]==[1,2]).all(1))
True
>>> any((a[:]==[1,20]).all(1))
False
O genere sobre la lista numpy (potencialmente MUY LENTA):
any(([1,2] == x).all() for x in a) # stops on first occurrence
O usa las funciones de la lógica numpy:
any(np.equal(a,[1,2]).all(1))
Si cronometra estos:
import numpy as np
import time
n=300000
a=np.arange(n*3).reshape(n,3)
b=a.tolist()
t1,t2,t3=a[n//100][0],a[n//2][0],a[-10][0]
tests=[ (''early hit'',[t1, t1+1, t1+2]),
(''middle hit'',[t2,t2+1,t2+2]),
(''late hit'', [t3,t3+1,t3+2]),
(''miss'',[0,2,0])]
fmt=''/t{:20}{:.5f} seconds and is {}''
for test, tgt in tests:
print(''/n{}: {} in {:,} elements:''.format(test,tgt,n))
name=''view''
t1=time.time()
result=(a[...]==tgt).all(1).any()
t2=time.time()
print(fmt.format(name,t2-t1,result))
name=''python list''
t1=time.time()
result = True if tgt in b else False
t2=time.time()
print(fmt.format(name,t2-t1,result))
name=''gen over numpy''
t1=time.time()
result=any((tgt == x).all() for x in a)
t2=time.time()
print(fmt.format(name,t2-t1,result))
name=''logic equal''
t1=time.time()
np.equal(a,tgt).all(1).any()
t2=time.time()
print(fmt.format(name,t2-t1,result))
Puedes ver que acertar o fallar, las rutinas numpy son la misma velocidad para buscar en la matriz . El operador de Python in
es potencialmente mucho más rápido para un ataque temprano, y el generador es solo una mala noticia si tiene que atravesar todo el conjunto.
Aquí están los resultados para la matriz de elementos de 300,000 x 3:
early hit: [9000, 9001, 9002] in 300,000 elements:
view 0.01002 seconds and is True
python list 0.00305 seconds and is True
gen over numpy 0.06470 seconds and is True
logic equal 0.00909 seconds and is True
middle hit: [450000, 450001, 450002] in 300,000 elements:
view 0.00915 seconds and is True
python list 0.15458 seconds and is True
gen over numpy 3.24386 seconds and is True
logic equal 0.00937 seconds and is True
late hit: [899970, 899971, 899972] in 300,000 elements:
view 0.00936 seconds and is True
python list 0.30604 seconds and is True
gen over numpy 6.47660 seconds and is True
logic equal 0.00965 seconds and is True
miss: [0, 2, 0] in 300,000 elements:
view 0.00936 seconds and is False
python list 0.01287 seconds and is False
gen over numpy 6.49190 seconds and is False
logic equal 0.00965 seconds and is False
Y para una matriz de 3,000,000 x 3:
early hit: [90000, 90001, 90002] in 3,000,000 elements:
view 0.10128 seconds and is True
python list 0.02982 seconds and is True
gen over numpy 0.66057 seconds and is True
logic equal 0.09128 seconds and is True
middle hit: [4500000, 4500001, 4500002] in 3,000,000 elements:
view 0.09331 seconds and is True
python list 1.48180 seconds and is True
gen over numpy 32.69874 seconds and is True
logic equal 0.09438 seconds and is True
late hit: [8999970, 8999971, 8999972] in 3,000,000 elements:
view 0.09868 seconds and is True
python list 3.01236 seconds and is True
gen over numpy 65.15087 seconds and is True
logic equal 0.09591 seconds and is True
miss: [0, 2, 0] in 3,000,000 elements:
view 0.09588 seconds and is False
python list 0.12904 seconds and is False
gen over numpy 64.46789 seconds and is False
logic equal 0.09671 seconds and is False
Lo que parece indicar que np.equal
es la forma numpy pura más rápida para hacer esto ...
Si realmente quieres detenerte en la primera aparición, podrías escribir un ciclo, como:
import numpy as np
needle = np.array([10, 20])
haystack = np.array([[1,2],[10,20],[100,200]])
found = False
for row in haystack:
if np.all(row == needle):
found = True
break
print("Found: ", found)
Sin embargo, sospecho fuertemente, que será mucho más lento que las otras sugerencias que usan rutinas numpy para hacerlo para toda la matriz.
creo
equal([1,2], a).all(axis=1) # also, ([1,2]==a).all(axis=1)
# array([ True, False, False], dtype=bool)
listará las filas que coinciden. Como Jamie señala, para saber si existe al menos una de esas filas, use any
:
equal([1,2], a).all(axis=1).any()
# True
Aparte:
Sospecho que in
(y __contains__
) es exactamente igual a la anterior, pero usa any
lugar de all
.