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


Tengo una matriz 2D que contiene 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 para un tiempo dado.

Así que 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 múltiples valores para mode, cualquiera de ellos (seleccionado aleatoriamente) puede ser establecido como mode.

Puedo iterar sobre el modo de búsqueda de columnas una a la vez, pero esperaba que Numpy pudiera tener alguna función incorporada para hacer eso. O si hay un truco para encontrar que eficientemente sin bucle.

Author: Nik, 2013-05-02

4 answers

Comprobar scipy.stats.mode() (inspirado por 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]]
 62
Author: fgb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-11 13:22:32

Este es un problema complicado, ya que no hay mucho para calcular el modo a lo largo de un eje. La solución es sencilla para matrices 1-D, donde numpy.bincount es útil, junto con numpy.unique con el return_counts arg como True. La función n-dimensional más común que veo es scipy.estadísticas.modo, aunque es prohibitivamente lento, 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 de un fondo y modificó el enfoque para ser más eficiente en memoria

 13
Author: Devin Cairns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-17 05:58:33

Expandiendo este método, aplicado para encontrar el modo de los datos donde puede necesitar el índice de la matriz real para ver cuán 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 (conteos)) > 1, también para validar si es realmente representativo de la distribución central de sus datos, puede verificar si está dentro de su intervalo de desviación estándar.

 4
Author: Lean Bravo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:51

Creo que una forma muy sencilla sería usar la clase Counter. Luego puede usar la función most_common () de la instancia del contador 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 dimensionales múltiples (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])

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

 2
Author: Ali_Ayub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-25 01:51:27