python - functions - La manera más eficiente de encontrar el modo en numpy array
numpy python 3.6 windows (4)
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 particular, mientras que cada columna representa valores para varios sitios espaciales para un tiempo dado.
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 configurarse como modo.
Puedo iterar sobre las columnas encontrando el modo uno a la vez, pero esperaba que Numpy pudiera tener alguna función incorporada para hacer eso. O si hay un truco para encontrarlo de manera eficiente sin bucles.
Ampliando este método , se aplica 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 (counts))> 1, también para validar si es realmente representativo de la distribución central de sus datos, puede verificar si cae dentro de su intervalo de desviación estándar.
Creo que una forma muy simple sería usar la clase Counter. A continuación, puede usar la función most_common () de la instancia de contador como se menciona here .
Para arreglos de 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 multidimensionales (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 ser o no una implementación eficiente, pero es conveniente.
Este es un problema difícil, ya que no hay mucho por ahí para calcular el modo a lo largo de un eje. La solución es sencilla para arreglos en 1-D, donde numpy.bincount
es útil, junto con numpy.unique
con return_counts
arg 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 en gran medida:
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 con la memoria
Marque 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]]