Je dois trouver des lignes uniques dans un numpy.array
.
Par exemple:
>>> 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]])
Je sais que je peux créer un ensemble et une boucle sur le tableau, mais je recherche une solution efficace pure numpy
. Je pense qu'il existe un moyen de définir le type de données sur null et que je pourrais alors simplement utiliser numpy.unique
, mais je ne pouvais pas comprendre comment le faire fonctionner.
A partir de NumPy 1.13, on peut simplement choisir l’axe pour la sélection de valeurs uniques dans n’importe quel tableau N-dim. Pour obtenir des lignes uniques, on peut faire:
unique_rows = np.unique(original_array, axis=0)
Encore une autre solution possible
np.vstack({Tuple(row) for row in a})
Une autre option pour utiliser des tableaux structurés consiste à utiliser une vue de type void
qui joint la ligne entière à un seul élément:
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]])
EDIT Ajouté np.ascontiguousarray
suivant la recommandation de @ seberg. Cela ralentira la méthode si le tableau n'est pas déjà contigu.
EDIT Ce qui précède peut être légèrement accéléré, peut-être au détriment de la clarté, en faisant:
unique_a = np.unique(b).view(a.dtype).reshape(-1, a.shape[1])
En outre, du moins sur mon système, en termes de performances, il est égal, voire supérieur, à la méthode 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
Si vous souhaitez éviter les dépenses de mémoire liées à la conversion en une série de n-uplets ou en une autre structure de données similaire, vous pouvez exploiter les tableaux structurés de numpy.
L'astuce consiste à afficher votre tableau d'origine sous la forme d'un tableau structuré, chaque élément correspondant à une ligne du tableau d'origine. Cela ne fait pas de copie et est très efficace.
Comme exemple rapide:
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
Pour comprendre ce qui se passe, examinez les résultats intermédiaires.
Une fois que nous voyons les choses comme un tableau structuré, chaque élément du tableau est une ligne de votre tableau d'origine. (Fondamentalement, c'est une structure de données similaire à une liste de n-uplets.)
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')])
Une fois que nous aurons exécuté numpy.unique
, nous obtiendrons un tableau structuré:
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')])
Il nous faut ensuite voir comme un tableau "normal" (_
stocke le résultat du dernier calcul dans ipython
, ce qui explique pourquoi vous voyez _.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])
Et ensuite, remodelez dans un tableau 2D (-1
est un espace réservé qui indique à numpy de calculer le nombre correct de lignes, donnez le nombre de colonnes):
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]])
Évidemment, si vous voulez être plus concis, vous pouvez l'écrire comme suit:
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)
Ce qui résulte en:
[[0 1 1 1 0 0]
[1 1 1 0 0 0]
[1 1 1 1 1 0]]
np.unique
quand je l'exécute sur np.random.random(100).reshape(10,10)
renvoie tous les éléments individuels uniques, mais vous voulez les lignes uniques. Vous devez donc d'abord les mettre en tuples:
array = #your numpy array of lists
new_array = [Tuple(row) for row in array]
uniques = np.unique(new_array)
C’est la seule façon pour moi de vous voir changer les types pour faire ce que vous voulez, et je ne suis pas sûr que l’itération de liste à changer en tuples soit acceptable si vous "ne passez pas en boucle"
np.unique fonctionne en triant un tableau aplati, puis en vérifiant si chaque élément est égal au précédent. Cela peut être fait manuellement sans aplatir:
ind = np.lexsort(a.T)
a[ind[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]]
Cette méthode n'utilise pas de n-uplets et devrait être beaucoup plus rapide et simple que les autres méthodes données ici.
Remarque: une version précédente de ce n'a pas l'index juste après un [ ce qui signifie que les index incorrects ont été utilisés. En outre, Joe Kington souligne le fait que cela fait une variété de copies intermédiaires. La méthode suivante en fait moins, en faisant une copie triée puis en l’affichant en vue:
b = a[np.lexsort(a.T)]
b[np.concatenate(([True], np.any(b[1:] != b[:-1],axis=1)))]
Ceci est plus rapide et utilise moins de mémoire.
De même, si vous souhaitez trouver des lignes uniques dans un ndarray quel que soit le nombre du nombre de dimensions contenues dans le tableau, ce qui suit fonctionnera:
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)))))]
Si vous vouliez trier/unique le long d'un axe arbitraire d'un tableau de dimensions arbitraires, il resterait un problème intéressant, ce qui serait plus difficile.
Modifier:
Pour démontrer les différences de vitesse, j'ai effectué quelques tests sous ipython des trois méthodes décrites dans les réponses. Avec votre exact a, il n'y a pas trop de différence, bien que cette version soit un peu plus rapide:
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
Avec un plus grand, cependant, cette version finit par être beaucoup, beaucoup plus rapide:
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
Voici une autre variante pour la réponse @Greg Pythonic
np.vstack(set(map(Tuple, a)))
J'ai comparé l'alternative suggérée pour la vitesse et constaté que, de manière surprenante, la solution de vue vide unique
est même un peu plus rapide que la méthode native unique
de numpy avec l'argument axis
. Si vous recherchez la vitesse, vous voudrez
numpy.unique(
a.view(numpy.dtype((numpy.void, a.dtype.itemsize*a.shape[1])))
).view(a.dtype).reshape(-1, a.shape[1])
Code pour reproduire l'intrigue:
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
)
Je n’ai aimé aucune de ces réponses car aucune d’entre elles ne gère les tableaux à virgule flottante dans une algèbre linéaire ou un sens d’espace vectoriel, deux lignes étant "égales", signifiant "dans certains ????". La réponse qui a un seuil de tolérance, https://stackoverflow.com/a/26867764/500207 , a pris le seuil pour être à la fois élément-sage et décimal précision, qui fonctionne dans certains cas mais n’est pas aussi mathématiquement général que la distance vectorielle réelle.
Voici ma version:
_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 fonction de domaine public ci-dessus utilise scipy.spatial.distance.pdist
pour rechercher la distance euclidienne (personnalisable) entre chaque paire de lignes . Ensuite, il compare chaque distance à un ancien thresh
pour rechercher les lignes se trouvant dans thresh
les unes des autres et ne renvoie qu'une ligne de chaque groupe thresh
-.
Comme indiqué, la distance metric
ne doit pas nécessairement être euclidienne —pdist
peut calculer diverses distances, y compris cityblock
(norme de Manhattan) et cosine
(angle entre vecteurs).
Si _thresh=0
_ (valeur par défaut), les lignes doivent être exactes en bits pour être considérées comme "uniques". D'autres bonnes valeurs pour thresh
utilisent une précision machine mise à l'échelle, c'est-à-dire thresh=np.spacing(1)*1e3
.
Le package numpy_indexed (disclaimer: je suis son auteur) enveloppe la solution publiée par Jaime dans une interface testée et bien conçue, ainsi que de nombreuses autres fonctionnalités:
import numpy_indexed as npi
new_a = npi.unique(a) # unique elements over axis=0 (rows) by default
Pourquoi ne pas utiliser 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
Sur la base de la réponse fournie dans cette page, j'ai écrit une fonction qui reproduit la capacité de la fonction unique(input,'rows')
de MATLAB, avec la fonctionnalité supplémentaire permettant d'accepter une tolérance pour la vérification de l'unicité. Il renvoie également les index tels que c = data[ia,:]
et data = c[ic,:]
. Veuillez signaler si vous constatez des divergences ou des erreurs.
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
Au-delà de l'excellente réponse de @Jaime, un autre moyen de réduire une ligne consiste à utiliser a.strides[0]
(en supposant que a
est C-contigu), ce qui est égal à a.dtype.itemsize*a.shape[0]
. De plus, void(n)
est un raccourci pour dtype((void,n))
. nous arrivons enfin à cette version la plus courte:
a[unique(a.view(void(a.strides[0])),1)[1]]
For
[[0 1 1 1 0 0]
[1 1 1 0 0 0]
[1 1 1 1 1 0]]
np.unique fonctionne avec une liste de tuples:
>>> np.unique([(1, 1), (2, 2), (3, 3), (4, 4), (2, 2)])
Out[9]:
array([[1, 1],
[2, 2],
[3, 3],
[4, 4]])
Avec une liste de listes, il soulève un TypeError: unhashable type: 'list'
Pour un usage général comme les tableaux imbriqués 3D ou multidimensionnels supérieurs, essayez ceci:
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:])
qui satisfait votre jeu de données 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)
donne:
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Mais aussi des tableaux 3D comme:
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)
donne:
array([[[0, 1, 1], [1, 1, 1]],
[[1, 1, 1], [0, 1, 1]],
[[1, 1, 1], [1, 1, 1]]])
Permet d’obtenir la totalité de la matrice numpy sous forme de liste, puis de supprimer les doublons de cette liste et enfin de renvoyer notre liste unique dans une matrice 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]])
Aucune de ces réponses n'a fonctionné pour moi. Je suppose que mes lignes uniques contiennent des chaînes et non des nombres. Cependant cette réponse d'un autre thread a fonctionné:
Source: https://stackoverflow.com/a/38461043/5402386
Vous pouvez utiliser les méthodes de la liste .count () et .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]
Nous pouvons réellement transformer mxn numeric numpy array en mx 1 numpy string array. Essayez d’utiliser la fonction suivante, elle fournit count , inverse_idx et etc, tout comme 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
Exemple:
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]