148 votes

Le moyen le plus efficace de trouver le mode dans un tableau numpy

J'ai un tableau 2D contenant des entiers (positifs ou négatifs). Chaque ligne représente les valeurs dans le temps pour un site spatial particulier, tandis que chaque colonne représente les valeurs pour divers sites spatiaux pour un temps donné.

Donc si le tableau est comme :

1 3 4 2 2 7
5 2 2 1 4 1
3 3 2 2 1 1

Le résultat devrait être

1 3 2 2 2 1

Notez que lorsqu'il y a plusieurs valeurs pour le mode, n'importe laquelle (choisie au hasard) peut être définie comme mode.

Je peux itérer sur les colonnes en trouvant le mode un par un mais j'espérais que numpy aurait une fonction intégrée pour le faire. Ou s'il y a une astuce pour trouver cela efficacement sans faire de boucle.

194voto

fgb Points 1494

Vérifiez scipy.stats.mode() (inspiré par le commentaire de @tom10) :

import numpy as np
from scipy import stats

a = np.array([[1, 3, 4, 2, 2, 7],
              [5, 2, 2, 1, 4, 1],
              [3, 3, 2, 2, 1, 1]])

m = stats.mode(a)
print(m)

Sortie :

ModeResult(mode=array([[1, 3, 2, 2, 1, 1]]), count=array([[1, 2, 2, 2, 1, 2]]))

Comme vous pouvez le voir, il renvoie à la fois le mode et les comptes. Vous pouvez sélectionner les modes directement via m[0] :

print(m[0])

Sortie :

[[1 3 2 2 1 1]]

41voto

Devin Cairns Points 355

Mise à jour

Le site scipy.stats.mode a été considérablement optimisée depuis cet article, et serait la méthode recommandée.

Ancienne réponse

C'est un problème délicat, car il n'existe pas grand-chose pour calculer le mode le long d'un axe. La solution est simple pour les réseaux 1-D, où numpy.bincount est pratique, ainsi que numpy.unique avec le return_counts arg comme True . La fonction n-dimensionnelle la plus commune que je vois est scipy.stats.mode, bien qu'elle soit prohibitivement lente - surtout pour les grands tableaux avec beaucoup de valeurs uniques. Comme solution, j'ai développé cette fonction, et je l'utilise beaucoup :

import numpy

def mode(ndarray, axis=0):
    # Check inputs
    ndarray = numpy.asarray(ndarray)
    ndim = ndarray.ndim
    if ndarray.size == 1:
        return (ndarray[0], 1)
    elif ndarray.size == 0:
        raise Exception('Cannot compute mode on empty array')
    try:
        axis = range(ndarray.ndim)[axis]
    except:
        raise Exception('Axis "{}" incompatible with the {}-dimension array'.format(axis, ndim))

    # If array is 1-D and numpy version is > 1.9 numpy.unique will suffice
    if all([ndim == 1,
            int(numpy.__version__.split('.')[0]) >= 1,
            int(numpy.__version__.split('.')[1]) >= 9]):
        modals, counts = numpy.unique(ndarray, return_counts=True)
        index = numpy.argmax(counts)
        return modals[index], counts[index]

    # Sort array
    sort = numpy.sort(ndarray, axis=axis)
    # Create array to transpose along the axis and get padding shape
    transpose = numpy.roll(numpy.arange(ndim)[::-1], axis)
    shape = list(sort.shape)
    shape[axis] = 1
    # Create a boolean array along strides of unique values
    strides = numpy.concatenate([numpy.zeros(shape=shape, dtype='bool'),
                                 numpy.diff(sort, axis=axis) == 0,
                                 numpy.zeros(shape=shape, dtype='bool')],
                                axis=axis).transpose(transpose).ravel()
    # Count the stride lengths
    counts = numpy.cumsum(strides)
    counts[~strides] = numpy.concatenate([[0], numpy.diff(counts[~strides])])
    counts[strides] = 0
    # Get shape of padded counts and slice to return to the original shape
    shape = numpy.array(sort.shape)
    shape[axis] += 1
    shape = shape[transpose]
    slices = [slice(None)] * ndim
    slices[axis] = slice(1, None)
    # Reshape and compute final counts
    counts = counts.reshape(shape).transpose(transpose)[slices] + 1

    # Find maximum counts and return modals/counts
    slices = [slice(None, i) for i in sort.shape]
    del slices[axis]
    index = numpy.ogrid[slices]
    index.insert(axis, numpy.argmax(counts, axis=axis))
    return sort[index], counts[index]

Résultat :

In [2]: a = numpy.array([[1, 3, 4, 2, 2, 7],
                         [5, 2, 2, 1, 4, 1],
                         [3, 3, 2, 2, 1, 1]])

In [3]: mode(a)
Out[3]: (array([1, 3, 2, 2, 1, 1]), array([1, 2, 2, 2, 1, 2]))

Quelques points de repère :

In [4]: import scipy.stats

In [5]: a = numpy.random.randint(1,10,(1000,1000))

In [6]: %timeit scipy.stats.mode(a)
10 loops, best of 3: 41.6 ms per loop

In [7]: %timeit mode(a)
10 loops, best of 3: 46.7 ms per loop

In [8]: a = numpy.random.randint(1,500,(1000,1000))

In [9]: %timeit scipy.stats.mode(a)
1 loops, best of 3: 1.01 s per loop

In [10]: %timeit mode(a)
10 loops, best of 3: 80 ms per loop

In [11]: a = numpy.random.random((200,200))

In [12]: %timeit scipy.stats.mode(a)
1 loops, best of 3: 3.26 s per loop

In [13]: %timeit mode(a)
1000 loops, best of 3: 1.75 ms per loop

EDIT : Fourni plus de contexte et modifié l'approche pour être plus efficace en mémoire.

38voto

poisonedivy Points 359

Si vous voulez utiliser uniquement numpy :

x = [-1, 2, 1, 3, 3]
vals,counts = np.unique(x, return_counts=True)

donne

(array([-1,  1,  2,  3]), array([1, 1, 1, 2]))

Et l'extraire :

index = np.argmax(counts)
return vals[index]

18voto

Def_Os Points 847

Une solution soignée qui seulement utilise numpy (pas scipy ni le Counter classe) :

A = np.array([[1,3,4,2,2,7], [5,2,2,1,4,1], [3,3,2,2,1,1]])

np.apply_along_axis(lambda x: np.bincount(x).argmax(), axis=0, arr=A)

array([1, 3, 2, 2, 1, 1])

14voto

Lean Bravo Points 61

Élargir le champ d'action cette méthode Cette méthode est appliquée à la recherche du mode des données, où l'on peut avoir besoin de l'indice du tableau réel pour savoir à quelle distance la valeur se trouve du centre de la distribution.

(_, idx, counts) = np.unique(a, return_index=True, return_counts=True)
index = idx[np.argmax(counts)]
mode = a[index]

N'oubliez pas de rejeter le mode lorsque len(np.argmax(counts)) > 1, également pour valider s'il est réellement représentatif de la distribution centrale de vos données vous pouvez vérifier s'il tombe à l'intérieur de votre intervalle d'écart type.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X