Найефективніший спосіб пошуку режиму в масиві 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

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


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.