La forma más eficiente de encontrar el modo en la matriz numpy


84

Tengo una matriz 2D que contiene números enteros (tanto positivos como negativos). Cada fila representa los valores a lo largo del tiempo para un sitio espacial en particular, mientras que cada columna representa valores para varios sitios espaciales durante un tiempo determinado.

Entonces, si la matriz es como:

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

El resultado debe ser

1 3 2 2 2 1

Tenga en cuenta que cuando hay varios valores para el modo, cualquiera (seleccionado al azar) puede establecerse como modo.

Puedo iterar sobre el modo de búsqueda de columnas una a la vez, pero esperaba que numpy tuviera alguna función incorporada para hacer eso. O si hay un truco para encontrarlo de manera eficiente sin hacer bucles.



1
@ tom10: ¿Te refieres a scipy.stats.mode () , verdad? El otro parece generar una matriz enmascarada.
fgb

@fgb: correcto, gracias por la corrección (y +1 por tu respuesta).
tom10

Respuestas:


115

Verificar scipy.stats.mode()(inspirado en el comentario 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)

Salida:

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

Como puede ver, devuelve tanto el modo como los recuentos. Puede seleccionar los modos directamente a través de m[0]:

print(m[0])

Salida:

[[1 3 2 2 1 1]]

4
Entonces, ¿numpy por sí solo no admite ninguna de estas funciones?
Nik

1
Aparentemente no, pero la implementación de scipy se basa solo en numpy , por lo que podría copiar ese código en su propia función.
fgb

11
Solo una nota, para las personas que vean esto en el futuro: debe hacerlo import scipy.statsexplícitamente, no se incluye cuando simplemente hace un import scipy.
incipiente

1
¿Puede explicar cómo se muestran exactamente los valores de modo y el recuento? No pude relacionar la salida con la entrada proporcionada.
Rahul

2
@Rahul: debe considerar el segundo argumento predeterminado de axis=0. El código anterior informa el modo por columna de la entrada. El recuento nos dice cuántas veces ha visto el modo informado en cada una de las columnas. Si desea el modo general, debe especificar axis=None. Para obtener más información, consulte docs.scipy.org/doc/scipy/reference/generated/…
fgb

22

Actualizar

La scipy.stats.modefunción se ha optimizado significativamente desde esta publicación y sería el método recomendado

Respuesta antigua

Este es un problema complicado, ya que no hay mucho para calcular el modo a lo largo de un eje. La solución es sencillo para las matrices 1-D, donde numpy.bincountes práctico, junto con numpy.uniquecon el return_countsarg como True. La función n-dimensional más común que veo es scipy.stats.mode, aunque es prohibitivamente lenta, especialmente para matrices grandes con muchos valores únicos. Como solución, he desarrollado esta función y la uso mucho:

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]

Resultado:

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

Algunos puntos de referencia:

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

EDITAR: proporcionó más antecedentes y modificó el enfoque para que sea más eficiente en la memoria


1
Contribuya al módulo de estadísticas de scipy para que otros también puedan beneficiarse de él.
ARF

Para problemas de dimensiones superiores con grandes ndarrays int, su solución parece ser mucho más rápida que scipy.stats.mode. Tuve que calcular el modo a lo largo del primer eje de un ndarray 4x250x250x500, y su función tomó 10 segundos, mientras que scipy.stats.mode tomó casi 600 segundos.
CheshireCat

11

Ampliando este método , aplicado para encontrar el modo de los datos donde puede necesitar el índice de la matriz real para ver qué tan lejos está el valor del centro de la distribución.

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

Recuerde descartar el modo cuando len (np.argmax (count))> 1, también para validar si es realmente representativo de la distribución central de sus datos, puede verificar si se encuentra dentro de su intervalo de desviación estándar.


¿Cuándo np.argmax devuelve algo con una longitud mayor que 1 si no especifica un eje?
loganjones16

9

Una solución ordenada que solo usa numpy(no scipyni la Counterclase):

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)

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


1
Agradable y conciso, pero debe usarse con precaución si las matrices originales contienen un número muy grande porque bincount creará matrices bin con len (max (A [i])) para cada matriz original A [i].
Scottlittle

Esta es una solución asombrosa. En realidad, hay un inconveniente scipy.stats.mode. Cuando hay varios valores que tienen la mayor frecuencia (modos múltiples), arrojará una expectativa. Pero este método tomará automáticamente el "primer modo".
Christopher

5

Si solo desea usar numpy:

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

da

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

Y extraerlo:

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

Me gusta este método porque admite no solo números enteros, sino también flotantes e incluso cadenas.
Christopher

3

Creo que una forma muy sencilla sería utilizar la clase Counter. Luego puede usar la función most_common () de la instancia de Counter como se menciona aquí .

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

Para matrices de varias dimensiones (poca diferencia):

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

Esta puede ser una implementación eficiente o no, pero es conveniente.


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

El Counter(data)cuenta la frecuencia y devuelve un diccionario predeterminado. sorted(Counter(data).items())ordena usando las teclas, no la frecuencia. Finalmente, es necesario ordenar la frecuencia usando otra ordenada con key = lambda x: x[1]. Lo contrario le dice a Python que ordene la frecuencia de mayor a menor.


Dado que la pregunta se hizo hace 6 años, es normal que no haya recibido mucha reputación.
Zeliha Bektas

1

forma más sencilla en Python de obtener el modo de una lista o matriz a

   import statistics
   print("mode = "+str(statistics.(mode(a)))

Eso es

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.