web-dev-qa-db-fra.com

Trouver des lignes uniques dans numpy.array

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.

180
Akavall

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)

87
aiwabdn

Encore une autre solution possible

np.vstack({Tuple(row) for row in a})
137
Greg von Winckel

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
110
Jaime

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]]
29
Joe Kington

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"

19
Ryan Saxe

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
16
cge

Voici une autre variante pour la réponse @Greg Pythonic

np.vstack(set(map(Tuple, a)))
9
divenex

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])

enter image description here


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
    )
8
Nico Schlömer

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.

7
Ahmed Fasih

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
3

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
3
kalu

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
1
Arash_D_B

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]]
1
B. M.

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'

1
codeape

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]]])
0
Tara

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]])
0
Mahdi Ghelichi

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]
0
mjp

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]
0
Ting On Chan