python - ndarray - numpy where
Encuentre filas Ășnicas en numpy.array (20)
¿Por qué no usar drop_duplicates
de pandas?
>>> timeit pd.DataFrame(image.reshape(-1,3)).drop_duplicates().values
1 loops, best of 3: 3.08 s per loop
>>> timeit np.vstack({tuple(r) for r in image.reshape(-1,3)})
1 loops, best of 3: 51 s per loop
Necesito encontrar filas únicas en numpy.array
.
Por ejemplo:
>>> a # I have
array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
>>> new_a # I want to get to
array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 0]])
Sé que puedo crear un conjunto y recorrer el conjunto, pero estoy buscando una solución numpy
pura eficiente. Creo que hay una forma de establecer el tipo de datos en vacío y luego solo pude usar numpy.unique
, pero no pude encontrar la manera de hacerlo funcionar.
A partir de NumPy 1.13, uno puede simplemente elegir el eje para la selección de valores únicos en cualquier matriz N-dim. Para obtener filas únicas, uno puede hacer:
unique_rows = np.unique(original_array, axis=0)
Aquí hay otra variación para @Greg pythonic answer
np.vstack(set(map(tuple, a)))
Basado en la respuesta de esta página, he escrito una función que replica la capacidad de la función unique(input,''rows'')
de MATLAB unique(input,''rows'')
, con la característica adicional de aceptar tolerancia para verificar la singularidad. También devuelve los índices tales que c = data[ia,:]
y data = c[ic,:]
. Informe si nota discrepancias o errores.
def unique_rows(data, prec=5):
import numpy as np
d_r = np.fix(data * 10 ** prec) / 10 ** prec + 0.0
b = np.ascontiguousarray(d_r).view(np.dtype((np.void, d_r.dtype.itemsize * d_r.shape[1])))
_, ia = np.unique(b, return_index=True)
_, ic = np.unique(b, return_inverse=True)
return np.unique(b).view(d_r.dtype).reshape(-1, d_r.shape[1]), ia, ic
El paquete numpy_indexed (descargo de responsabilidad: soy su autor) envuelve la solución publicada por Jaime en una interfaz agradable y probada, además de muchas más características:
import numpy_indexed as npi
new_a = npi.unique(a) # unique elements over axis=0 (rows) by default
He comparado la alternativa sugerida para la velocidad y descubrí que, sorprendentemente, la solución unique
vista vacía es incluso un poco más rápida que la original nativa de numpy con el argumento del axis
. Si buscas velocidad, querrás
numpy.unique(
a.view(numpy.dtype((numpy.void, a.dtype.itemsize*a.shape[1])))
).view(a.dtype).reshape(-1, a.shape[1])
Código para reproducir la trama:
import numpy
import perfplot
def unique_void_view(a):
return numpy.unique(
a.view(numpy.dtype((numpy.void, a.dtype.itemsize*a.shape[1])))
).view(a.dtype).reshape(-1, a.shape[1])
def lexsort(a):
ind = numpy.lexsort(a.T)
return a[ind[
numpy.concatenate((
[True], numpy.any(a[ind[1:]] != a[ind[:-1]], axis=1)
))
]]
def vstack(a):
return numpy.vstack({tuple(row) for row in a})
def unique_axis(a):
return numpy.unique(a, axis=0)
perfplot.show(
setup=lambda n: numpy.random.randint(2, size=(n, 20)),
kernels=[unique_void_view, lexsort, vstack, unique_axis],
n_range=[2**k for k in range(15)],
logx=True,
logy=True,
xlabel=''len(a)'',
equality_check=None
)
La solución más directa es hacer que las filas sean un solo elemento haciendo que sean cadenas. Cada fila se puede comparar como un todo por su singularidad usando numpy. Esta solución es generalizable, solo necesita remodelar y transponer su matriz para otras combinaciones. Aquí está la solución para el problema provisto.
import numpy as np
original = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
uniques, index = np.unique([str(i) for i in original], return_index=True)
cleaned = original[index]
print(cleaned)
Daré:
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Envía mi premio nobel por correo
Más allá de la respuesta excelente de @Jaime, otra forma de colapsar una fila es usar a.strides[0]
(suponiendo que a
es C-contigua) que es igual a a.dtype.itemsize*a.shape[0]
. Además, void(n)
es un atajo para dtype((void,n))
. finalmente llegamos a esta versión más corta:
a[unique(a.view(void(a.strides[0])),1)[1]]
por
[[0 1 1 1 0 0]
[1 1 1 0 0 0]
[1 1 1 1 1 0]]
Ninguna de estas respuestas funcionó para mí. Asumo que mis filas únicas contenían cadenas y no números. Sin embargo, esta respuesta de otro hilo funcionó:
Fuente: https://.com/a/38461043/5402386
Puede usar los métodos de la lista .count () y .index ()
coor = np.array([[10, 10], [12, 9], [10, 5], [12, 9]])
coor_tuple = [tuple(x) for x in coor]
unique_coor = sorted(set(coor_tuple), key=lambda x: coor_tuple.index(x))
unique_count = [coor_tuple.count(x) for x in unique_coor]
unique_index = [coor_tuple.index(x) for x in unique_coor]
No me gustó ninguna de estas respuestas porque ninguna maneja arreglos de punto flotante en un álgebra lineal o sentido de espacio vectorial, donde dos filas son "iguales" y significan "dentro de algunos ε". La única respuesta que tiene un umbral de tolerancia, https://.com/a/26867764/500207 , tomó el umbral para ser precisión decimal y de elementos, que funciona en algunos casos, pero no es tan matemáticamente general como distancia de vector verdadero.
Aquí está mi versión:
from scipy.spatial.distance import squareform, pdist
def uniqueRows(arr, thresh=0.0, metric=''euclidean''):
"Returns subset of rows that are unique, in terms of Euclidean distance"
distances = squareform(pdist(arr, metric=metric))
idxset = {tuple(np.nonzero(v)[0]) for v in distances <= thresh}
return arr[[x[0] for x in idxset]]
# With this, unique columns are super-easy:
def uniqueColumns(arr, *args, **kwargs):
return uniqueRows(arr.T, *args, **kwargs)
La función de dominio público anterior usa scipy.spatial.distance.pdist
para encontrar la distancia euclidiana (personalizable) entre cada par de filas. Luego compara cada una de las distancias hasta un thresh
para encontrar las filas que están dentro del thresh
del otro, y devuelve una sola fila de cada umbral de clúster.
Como se insinuó, la metric
distancia no tiene que ser euclidiana. El pdist
puede calcular diversas distancias, incluyendo cityblock
(Manhattan-norm) y cosine
(el ángulo entre vectores).
Si thresh=0
(valor predeterminado), las filas tienen que ser bit-exactas para ser consideradas "únicas". Otros buenos valores para el uso del umbral son la precisión de la máquina a escala, es decir, el thresh=np.spacing(1)*1e3
.
Otra opción para el uso de matrices estructuradas es usar una vista de un tipo de void
que une toda la fila en un solo elemento:
a = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
b = np.ascontiguousarray(a).view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
_, idx = np.unique(b, return_index=True)
unique_a = a[idx]
>>> unique_a
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
EDITAR Agregó np.ascontiguousarray
siguiendo la recomendación de @seberg. Esto ralentizará el método si la matriz no está contigua.
EDITAR Lo anterior se puede acelerar un poco, quizás a costa de la claridad, al hacer:
unique_a = np.unique(b).view(a.dtype).reshape(-1, a.shape[1])
Además, al menos en mi sistema, en cuanto al rendimiento, está a la par, o incluso mejor, que el método lexsort:
a = np.random.randint(2, size=(10000, 6))
%timeit np.unique(a.view(np.dtype((np.void, a.dtype.itemsize*a.shape[1])))).view(a.dtype).reshape(-1, a.shape[1])
100 loops, best of 3: 3.17 ms per loop
%timeit ind = np.lexsort(a.T); a[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]
100 loops, best of 3: 5.93 ms per loop
a = np.random.randint(2, size=(10000, 100))
%timeit np.unique(a.view(np.dtype((np.void, a.dtype.itemsize*a.shape[1])))).view(a.dtype).reshape(-1, a.shape[1])
10 loops, best of 3: 29.9 ms per loop
%timeit ind = np.lexsort(a.T); a[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]
10 loops, best of 3: 116 ms per loop
Otra posible solución
np.vstack({tuple(row) for row in a})
Para propósitos generales como matrices anidadas multidimensionales en 3D o superiores, intente esto:
import numpy as np
def unique_nested_arrays(ar):
origin_shape = ar.shape
origin_dtype = ar.dtype
ar = ar.reshape(origin_shape[0], np.prod(origin_shape[1:]))
ar = np.ascontiguousarray(ar)
unique_ar = np.unique(ar.view([('''', origin_dtype)]*np.prod(origin_shape[1:])))
return unique_ar.view(origin_dtype).reshape((unique_ar.shape[0], ) + origin_shape[1:])
que satisface tu conjunto de datos 2D:
a = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
unique_nested_arrays(a)
da:
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Pero también matrices 3D como:
b = np.array([[[1, 1, 1], [0, 1, 1]],
[[0, 1, 1], [1, 1, 1]],
[[1, 1, 1], [0, 1, 1]],
[[1, 1, 1], [1, 1, 1]]])
unique_nested_arrays(b)
da:
array([[[0, 1, 1], [1, 1, 1]],
[[1, 1, 1], [0, 1, 1]],
[[1, 1, 1], [1, 1, 1]]])
Podemos convertir mxn nuam array en mx 1 numpy string array, intente utilizar la siguiente función, proporciona count , inverse_idx y etc., al igual que numpy.unique:
import numpy as np
def uniqueRow(a):
#This function turn m x n numpy array into m x 1 numpy array storing
#string, and so the np.unique can be used
#Input: an m x n numpy array (a)
#Output unique m'' x n numpy array (unique), inverse_indx, and counts
s = np.chararray((a.shape[0],1))
s[:] = ''-''
b = (a).astype(np.str)
s2 = np.expand_dims(b[:,0],axis=1) + s + np.expand_dims(b[:,1],axis=1)
n = a.shape[1] - 2
for i in range(0,n):
s2 = s2 + s + np.expand_dims(b[:,i+2],axis=1)
s3, idx, inv_, c = np.unique(s2,return_index = True, return_inverse = True, return_counts = True)
return a[idx], inv_, c
Ejemplo:
A = np.array([[ 3.17 9.502 3.291],
[ 9.984 2.773 6.852],
[ 1.172 8.885 4.258],
[ 9.73 7.518 3.227],
[ 8.113 9.563 9.117],
[ 9.984 2.773 6.852],
[ 9.73 7.518 3.227]])
B, inv_, c = uniqueRow(A)
Results:
B:
[[ 1.172 8.885 4.258]
[ 3.17 9.502 3.291]
[ 8.113 9.563 9.117]
[ 9.73 7.518 3.227]
[ 9.984 2.773 6.852]]
inv_:
[3 4 1 0 2 4 0]
c:
[2 1 1 1 2]
Si desea evitar el gasto de memoria de convertir una serie de tuplas u otra estructura de datos similar, puede explotar las matrices estructuradas de numpy.
El truco es ver tu matriz original como una matriz estructurada donde cada elemento corresponde a una fila de la matriz original. Esto no hace una copia, y es bastante eficiente.
Como un ejemplo rápido:
import numpy as np
data = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
ncols = data.shape[1]
dtype = data.dtype.descr * ncols
struct = data.view(dtype)
uniq = np.unique(struct)
uniq = uniq.view(data.dtype).reshape(-1, ncols)
print uniq
Para entender lo que está pasando, eche un vistazo a los resultados intermedios.
Una vez que vemos las cosas como una matriz estructurada, cada elemento de la matriz es una fila en su matriz original. (Básicamente, es una estructura de datos similar a una lista de tuplas).
In [71]: struct
Out[71]:
array([[(1, 1, 1, 0, 0, 0)],
[(0, 1, 1, 1, 0, 0)],
[(0, 1, 1, 1, 0, 0)],
[(1, 1, 1, 0, 0, 0)],
[(1, 1, 1, 1, 1, 0)]],
dtype=[(''f0'', ''<i8''), (''f1'', ''<i8''), (''f2'', ''<i8''), (''f3'', ''<i8''), (''f4'', ''<i8''), (''f5'', ''<i8'')])
In [72]: struct[0]
Out[72]:
array([(1, 1, 1, 0, 0, 0)],
dtype=[(''f0'', ''<i8''), (''f1'', ''<i8''), (''f2'', ''<i8''), (''f3'', ''<i8''), (''f4'', ''<i8''), (''f5'', ''<i8'')])
Una vez que numpy.unique
, obtendremos una matriz estructurada de nuevo:
In [73]: np.unique(struct)
Out[73]:
array([(0, 1, 1, 1, 0, 0), (1, 1, 1, 0, 0, 0), (1, 1, 1, 1, 1, 0)],
dtype=[(''f0'', ''<i8''), (''f1'', ''<i8''), (''f2'', ''<i8''), (''f3'', ''<i8''), (''f4'', ''<i8''), (''f5'', ''<i8'')])
Que luego necesitamos ver como una matriz "normal" ( _
almacena el resultado del último cálculo en ipython
, que es la razón por la que está viendo _.view...
):
In [74]: _.view(data.dtype)
Out[74]: array([0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0])
Y luego vuelva a formar una matriz 2D ( -1
es un marcador de posición que le dice a numpy que calcule el número correcto de filas, indique el número de columnas):
In [75]: _.reshape(-1, ncols)
Out[75]:
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Obviamente, si quisieras ser más conciso, podrías escribirlo como:
import numpy as np
def unique_rows(data):
uniq = np.unique(data.view(data.dtype.descr * data.shape[1]))
return uniq.view(data.dtype).reshape(-1, data.shape[1])
data = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
print unique_rows(data)
Lo que resulta en:
[[0 1 1 1 0 0]
[1 1 1 0 0 0]
[1 1 1 1 1 0]]
Vamos a obtener toda la matriz numpy como una lista, luego soltamos los duplicados de esta lista, y finalmente devolvemos nuestra lista única a una matriz numpy:
matrix_as_list=data.tolist()
matrix_as_list:
[[1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 0, 0], [0, 1, 1, 1, 0, 0], [1, 1, 1, 0, 0, 0], [1, 1, 1, 1, 1, 0]]
uniq_list=list()
uniq_list.append(matrix_as_list[0])
[uniq_list.append(item) for item in matrix_as_list if item not in uniq_list]
unique_matrix=np.array(uniq_list)
unique_matrix:
array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 0]])
np.unique funciona con una lista de tuplas:
>>> np.unique([(1, 1), (2, 2), (3, 3), (4, 4), (2, 2)])
Out[9]:
array([[1, 1],
[2, 2],
[3, 3],
[4, 4]])
Con una lista de listas, genera un TypeError: unhashable type: ''list''
np.unique funciona ordenando una matriz aplanada, luego mirando si cada elemento es igual al anterior. Esto puede hacerse manualmente sin aplanamiento:
ind = np.lexsort(a.T)
a[ind[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]]
Este método no usa tuplas, y debería ser mucho más rápido y simple que otros métodos dados aquí.
NOTA: Una versión anterior de esto no tenía el ind justo después de un [, lo que significa que se usaron los índices incorrectos. Además, Joe Kington señala que esto hace una variedad de copias intermedias. El siguiente método hace menos, al hacer una copia ordenada y luego usar vistas de la misma:
b = a[np.lexsort(a.T)]
b[np.concatenate(([True], np.any(b[1:] != b[:-1],axis=1)))]
Esto es más rápido y usa menos memoria.
Además, si desea encontrar filas únicas en un ndarray, independientemente de cuántas dimensiones haya en el conjunto, lo siguiente funcionará:
b = a[lexsort(a.reshape((a.shape[0],-1)).T)];
b[np.concatenate(([True], np.any(b[1:]!=b[:-1],axis=tuple(range(1,a.ndim)))))]
Un problema restante interesante sería si desea ordenar / único a lo largo de un eje arbitrario de una matriz de dimensión arbitraria, algo que sería más difícil.
Editar:
Para demostrar las diferencias de velocidad, realicé algunas pruebas en ipython de los tres métodos diferentes que se describen en las respuestas. Con su exacto a, no hay demasiada diferencia, aunque esta versión es un poco más rápida:
In [87]: %timeit unique(a.view(dtype)).view(''<i8'')
10000 loops, best of 3: 48.4 us per loop
In [88]: %timeit ind = np.lexsort(a.T); a[np.concatenate(([True], np.any(a[ind[1:]]!= a[ind[:-1]], axis=1)))]
10000 loops, best of 3: 37.6 us per loop
In [89]: %timeit b = [tuple(row) for row in a]; np.unique(b)
10000 loops, best of 3: 41.6 us per loop
Sin embargo, con una a más grande, esta versión termina siendo mucho, mucho más rápida:
In [96]: a = np.random.randint(0,2,size=(10000,6))
In [97]: %timeit unique(a.view(dtype)).view(''<i8'')
10 loops, best of 3: 24.4 ms per loop
In [98]: %timeit b = [tuple(row) for row in a]; np.unique(b)
10 loops, best of 3: 28.2 ms per loop
In [99]: %timeit ind = np.lexsort(a.T); a[np.concatenate(([True],np.any(a[ind[1:]]!= a[ind[:-1]],axis=1)))]
100 loops, best of 3: 3.25 ms per loop
np.unique
cuando lo ejecuto en np.random.random(100).reshape(10,10)
devuelve todos los elementos individuales únicos, pero quieres las filas únicas, así que primero debes ponerlos en tuplas:
array = #your numpy array of lists
new_array = [tuple(row) for row in array]
uniques = np.unique(new_array)
Esa es la única forma en que veo que cambias los tipos para hacer lo que quieres, y no estoy seguro de si la iteración de la lista para cambiar a tuplas está bien con tu "no bucle".
import numpy as np
original = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
# create a view that the subarray as tuple and return unique indeies.
_, unique_index = np.unique(original.view(original.dtype.descr * original.shape[1]),
return_index=True)
# get unique set
print(original[unique_index])