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