web-dev-qa-db-fra.com

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 de 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 existe plusieurs valeurs pour le mode, l'une quelconque (sélectionnée de manière aléatoire) peut être définie comme mode.

Je peux parcourir le mode de recherche des colonnes une par une, 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 bouclage.

54
Nik

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 constater, il renvoie à la fois le mode et le nombre. Vous pouvez sélectionner les modes directement via m[0]:

print(m[0])

Sortie:

[[1 3 2 2 1 1]]
77
fgb

Mise à jour

Le scipy.stats.mode La fonction a été considérablement optimisée depuis ce poste et serait la méthode recommandée

Ancienne réponse

C'est un problème délicat, car il n'y a pas beaucoup de possibilités de calculer le mode le long d'un axe. La solution est simple pour les tableaux 1-D, où numpy.bincount est pratique, avec numpy.unique avec le return_counts _ arg comme True. La fonction n-dimensionnelle la plus courante que je voie est scipy.stats.mode, bien qu’elle soit extrêmement lente, en particulier pour les tableaux de grande taille comportant de nombreuses valeurs uniques. En guise de 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 repères:

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 davantage d’arrière-plan et modifié l’approche pour optimiser la mémoire.

19
Devin Cairns

Développer sur cette méthode , appliqué à la recherche du mode des données pour lequel vous pourriez avoir besoin de l'index du tableau réel pour voir à quelle distance se trouve la valeur par rapport au 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 supprimer le mode lorsque len (np.argmax (count))> 1, afin de valider s'il est réellement représentatif de la distribution centrale de vos données, vous pouvez vérifier si elle se situe dans votre intervalle d'écart-type.

7
Lean Bravo

Je pense qu'un moyen très simple serait d'utiliser la classe Counter. Vous pouvez ensuite utiliser la fonction most_common () de l'instance Counter comme indiqué here .

Pour les tableaux 1-D:

import numpy as np
from collections import Counter

nparr = np.arange(10) 
nparr[2] = 6 
nparr[3] = 6 #6 is now the mode
mode = Counter(nparr).most_common(1)
# mode will be [(6,3)] to give the count of the most occurring value, so ->
print(mode[0][0])    

Pour les tableaux multidimensionnels (petite différence):

import numpy as np
from collections import Counter

nparr = np.arange(10) 
nparr[2] = 6 
nparr[3] = 6 
nparr = nparr.reshape((10,2,5))     #same thing but we add this to reshape into ndarray
mode = Counter(nparr.flatten()).most_common(1)  # just use .flatten() method

# mode will be [(6,3)] to give the count of the most occurring value, so ->
print(mode[0][0])

Cela peut ou peut ne pas être une implémentation efficace, mais c'est pratique.

3
Ali_Ayub
from collections import Counter

n = int(input())
data = sorted([int(i) for i in input().split()])

sorted(sorted(Counter(data).items()), key = lambda x: x[1], reverse = True)[0][0]

print(Mean)

La Counter(data) compte la fréquence et retourne un defaultdict. sorted(Counter(data).items()) trie en utilisant les touches, pas la fréquence. Enfin, vous devez trier la fréquence en utilisant une autre triée avec key = lambda x: x[1]. L'inverse dit à Python de trier la fréquence du plus grand au plus petit.

0
zeliha_bektas

Une solution intéressante qui seulement utilise numpy (pas scipy ni la classe Counter.):

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)

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

0
Def_Os