Найефективніший спосіб пошуку режиму в масиві numpy


84

У мене є 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 може мати якусь вбудовану функцію для цього. Або якщо є хитрість, щоб знайти це ефективно без петель.



1
@ tom10: Ви маєте на увазі scipy.stats.mode () , правда? Здається, інший видає маскований масив.
fgb

@fgb: вірно, дякую за виправлення (та +1 за вашу відповідь).
tom10,

Відповіді:


115

Перевірка 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]]

4
Отже, numpy сам по собі не підтримує жодної такої функціональності?
Нік,

1
Мабуть, ні, але реалізація scipy покладається лише на numpy , тому ви можете просто скопіювати цей код у власну функцію.
fgb

11
Тільки примітка для людей, які дивляться на це в майбутньому: вам потрібно import scipy.statsчітко вказати, що це не включається, коли ви просто робите import scipy.
ffledgling

1
Чи можете ви пояснити, як саме він відображає значення режиму та підрахунок? Я не зміг пов'язати вихідні дані із введеним входом.
Рахул

2
@Rahul: ви повинні врахувати другий аргумент за замовчуванням axis=0. Наведений вище код повідомляє про режим для кожного стовпця введення. Підрахунок повідомляє нам, скільки разів він бачив звітний режим у кожному з стовпців. Якщо ви хотіли загальний режим, вам потрібно вказати axis=None. Для отримання додаткової інформації зверніться до docs.scipy.org/doc/scipy/reference/generated/…
fgb

22

Оновлення

scipy.stats.modeФункція була значно оптимізована , так як цей пост, і була б рекомендований метод

Стара відповідь

Це складна проблема, оскільки для обчислення режиму вздовж осі не так багато. Рішення прямо вперед для 1-D масивів, де numpy.bincountце зручно, поряд з numpy.uniqueс return_countsарг як True. Найпоширенішою n-вимірною функцією, яку я бачу, є scipy.stats.mode, хоча вона надзвичайно повільна - особливо для великих масивів з багатьма унікальними значеннями. Як рішення я розробив цю функцію та активно використовую її:

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

РЕДАГУВАТИ: надано більше фонового зображення та модифіковано підхід для підвищення ефективності пам'яті


1
Будь ласка, внесіть його до модуля статистики scipy, щоб інші також могли скористатися ним.
ARF

Що стосується великих розмірних проблем із великими int ndarrays, ваше рішення, здається, все ще набагато швидше, ніж scipy.stats.mode. Мені довелося обчислити режим уздовж першої осі ndarray 4x250x250x500, і ваша функція зайняла 10 секунд, тоді як scipy.stats.mode - майже 600 секунд.
CheshireCat

11

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

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

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


Коли np.argmax коли-небудь повертає щось довжиною більше 1, якщо ви не вказали вісь?
loganjones16

9

Акуратне рішення, яке використовує лишеnumpyscipyне 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])


1
Приємно і лаконічно, але слід використовувати з обережністю, якщо оригінальні масиви містять дуже велику кількість, оскільки bincount створить масиви бен зі значенням len (max (A [i])) для кожного оригінального масиву A [i].
Скоттлтл,

Це чудове рішення. У нас насправді є недолік scipy.stats.mode. Коли є кілька значень, що мають найбільшу кількість випадків (кілька режимів), це видасть очікування. Але цей метод автоматично прийме «перший режим».
Крістофер

5

Якщо ви хочете використовувати лише numpy:

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

дає

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

І витягніть його:

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

Подобається цей метод, оскільки він підтримує не тільки цілі числа, але також плаваючі та навіть рядки!
Крістофер

3

Думаю, дуже простим способом було б використання класу Counter. Потім ви можете використовувати функцію most_common () екземпляра Counter, як згадано тут .

Для 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])    

Для багатовимірних масивів (невелика різниця):

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

Це може бути ефективним впровадженням, а може і не бути, але це зручно.


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)

Counter(data)Підраховує частоту і повертає defaultdict. sorted(Counter(data).items())сортує за допомогою клавіш, а не за частотою. Нарешті, потрібно відсортувати частоту, використовуючи іншу, відсортовану за key = lambda x: x[1]. Реверс говорить Python сортувати частоту від найбільшої до найменшої.


Оскільки питання було задано 6 років тому, нормально, що він не отримав великої репутації.
Зеліха Бекташ

1

найпростіший спосіб у Python отримати режим списку або масиву a

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

Це воно

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.