Подтвердить что ты не робот

Самый эффективный способ поиска режима в массиве numpy

У меня есть 2D-массив, содержащий целые числа (как положительные, так и отрицательные). Каждая строка представляет значения со временем для определенного пространственного сайта, тогда как каждый столбец представляет значения для различных пространственных сайтов в течение заданного времени.

Итак, если массив похож:

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

Результат должен быть

1 3 2 2 2 1

Обратите внимание, что при наличии нескольких значений для режима, любой (выбранный случайным образом) может быть установлен как режим.

Я могу выполнять итерацию по столбцам, набирающим один режим за один раз, но я надеялся, что numpy может иметь встроенную функцию для этого. Или если есть хитрость, чтобы найти это эффективно без цикла.

4b9b3361

Ответ 1

Отметьте scipy.stats.mode() (вдохновленный комментарием @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)

Вывод:

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

Как вы можете видеть, он возвращает как режим, так и счетчик. Вы можете напрямую выбирать режимы через m[0]:

print(m[0])

Вывод:

[[1 3 2 2 1 1]]

Ответ 2

Обновить

Функция scipy.stats.mode была значительно оптимизирована с этого поста и будет рекомендованным методом

Старый ответ

Это сложная проблема, так как не так уж много времени для расчета моды вдоль оси. Решение прямо вперед для 1-D массивов, где numpy.bincount удобно, наряду с numpy.unique причем return_counts Arg, как True. Наиболее распространенная n-мерная функция, которую я вижу, это scipy.stats.mode, хотя она запрещена slow-, особенно для больших массивов со многими уникальными значениями. В качестве решения я разработал эту функцию и активно ее использую:

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]

Результат:

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

Некоторые тесты:

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

РЕДАКТИРОВАТЬ: Обеспечил больше фона и изменил подход, чтобы быть более эффективным с точки зрения памяти

Ответ 3

Расширение на этот метод, применяемый для поиска режима данных, где вам может понадобиться индекс фактического массива, чтобы увидеть, как далеко это значение от центр распределения.

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

Не забывайте отменить режим, когда len (np.argmax(counts)) > 1, а также для проверки того, действительно ли он отражает центральное распределение ваших данных, вы можете проверить, попадает ли он в ваш стандартный интервал отклонения.

Ответ 4

Я думаю, что очень простой способ - использовать класс Counter. Затем вы можете использовать функцию most_common() экземпляра Counter, как упомянуто здесь.

Для 1-го массива:

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

Для многомерных массивов (небольшая разница):

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

Это может или не может быть эффективной реализацией, но это удобно.

Ответ 5

scipy решение, которое использует только numpy (не scipy не класс 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)

массив ([1, 3, 2, 2, 1, 1])

Ответ 6

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)

Counter(data) считает частоту и возвращает по умолчанию. sorted(Counter(data).items()) сортирует по ключам, а не по частоте. Наконец, нужно отсортировать частоту, используя другую, отсортированную с помощью key = lambda x: x[1]. Обратное указывает Python сортировать частоту от самой большой до самой маленькой.