Найдите наиболее частое число в векторе numpy

124

Предположим, у меня есть следующий список на Python:

a = [1,2,3,1,2,1,1,1,3,2,2,1]

Как аккуратно найти самый частый номер в этом списке?

Как раз вовремя
источник

Ответы:

193

Если ваш список содержит все неотрицательные целые числа, вам следует взглянуть на numpy.bincounts:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html

а затем, вероятно, используйте np.argmax:

a = np.array([1,2,3,1,2,1,1,1,3,2,2,1])
counts = np.bincount(a)
print(np.argmax(counts))

Для более сложного списка (который, возможно, содержит отрицательные числа или нецелые значения) вы можете использовать np.histogramаналогичный способ. В качестве альтернативы, если вы просто хотите работать в python без использования numpy, collections.Counterэто хороший способ обработки данных такого рода.

from collections import Counter
a = [1,2,3,1,2,1,1,1,3,2,2,1]
b = Counter(a)
print(b.most_common(1))
JoshAdel
источник
58
+1. Может быть простоnp.bincount([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1]).argmax()
Николай Фетисов 06
1
+1. Это как минимум на порядок быстрее scipy.stats.mode, хотя и менее общее.
Fred Foo
Хороший ответ! Однако, если кто-то использует python 2.6, collections.Counter недоступен. В таком случае см. Мой ответ ниже.
JJC
19
Тем из нас, кто посетит после 2016 года: мне не нравится этот ответ, поскольку bincount (arr) возвращает массив размером с самый большой элемент в arr, поэтому небольшой массив с большим диапазоном создаст чрезмерно большой массив. Ответ Apoengtus ниже намного лучше, хотя я не думаю, что numpy.unique () существовал в 2011 году, когда был создан этот ответ.
Wehrdo
2
Python 3 :Counter(array).most_common(1)[0][0]
diralik
81

Вы можете использовать

(values,counts) = np.unique(a,return_counts=True)
ind=np.argmax(counts)
print values[ind]  # prints the most frequent element

Если один элемент встречается так же часто, как другой, этот код вернет только первый элемент.

Apogentus
источник
4
Я считаю это наиболее полезным, поскольку он общий, короткий и позволяет извлекать элементы из значений или счетчиков по некоторому производному индексу.
ryanjdillon
2
Если у нас есть несколько наиболее частых значений, values[counts.argmax()]вернется первое значение. Чтобы получить их все, мы можем использовать values[counts == counts.max()].
W. Zhu
44

Если вы хотите использовать SciPy :

>>> from scipy.stats import mode
>>> mode([1,2,3,1,2,1,1,1,3,2,2,1])
(array([ 1.]), array([ 6.]))
>>> most_frequent = mode([1,2,3,1,2,1,1,1,3,2,2,1])[0][0]
>>> most_frequent
1.0
Фред Фу
источник
30

Показатели (с использованием iPython) для некоторых решений можно найти здесь:

>>> # small array
>>> a = [12,3,65,33,12,3,123,888000]
>>> 
>>> import collections
>>> collections.Counter(a).most_common()[0][0]
3
>>> %timeit collections.Counter(a).most_common()[0][0]
100000 loops, best of 3: 11.3 µs per loop
>>> 
>>> import numpy
>>> numpy.bincount(a).argmax()
3
>>> %timeit numpy.bincount(a).argmax()
100 loops, best of 3: 2.84 ms per loop
>>> 
>>> import scipy.stats
>>> scipy.stats.mode(a)[0][0]
3.0
>>> %timeit scipy.stats.mode(a)[0][0]
10000 loops, best of 3: 172 µs per loop
>>> 
>>> from collections import defaultdict
>>> def jjc(l):
...     d = defaultdict(int)
...     for i in a:
...         d[i] += 1
...     return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)[0]
... 
>>> jjc(a)[0]
3
>>> %timeit jjc(a)[0]
100000 loops, best of 3: 5.58 µs per loop
>>> 
>>> max(map(lambda val: (a.count(val), val), set(a)))[1]
12
>>> %timeit max(map(lambda val: (a.count(val), val), set(a)))[1]
100000 loops, best of 3: 4.11 µs per loop
>>> 

Лучше всего «max» с «set» для небольших массивов, таких как проблема.

По словам @David Sanders, если вы увеличите размер массива примерно до 100 000 элементов, алгоритм «max w / set» окажется наихудшим, тогда как метод «numpy bincount» будет лучшим.

iuridiniz
источник
1
@IuliusCurt, чтобы указать лучший подход, нам нужно протестировать его в нескольких случаях: маленькие массивы, большие массивы, случайные массивы, массивы реального мира (например, timsort для сортировки), ... Но я согласен с вами
iuridiniz
3
Использование только небольшого массива, как в вашем подходе, не поможет разграничить разные алгоритмы.
Дэвид Сандерс
10
Если вы увеличите размер списка тестов до 100000 ( a = (np.random.rand(100000) * 1000).round().astype('int'); a_list = list(a)), ваш алгоритм «max w / set» окажется наихудшим, тогда как метод «numpy bincount» будет лучшим. Я провел этот тест, используя a_listсобственный код Python и код anumpy, чтобы избежать потери результатов из-за затрат на сборку.
Дэвид Сандерс
4

Также, если вы хотите получить наиболее частое значение (положительное или отрицательное) без загрузки каких-либо модулей, вы можете использовать следующий код:

lVals = [1,2,3,1,2,1,1,1,3,2,2,1]
print max(map(lambda val: (lVals.count(val), val), set(lVals)))
Артем Рудзенко
источник
1
Это было давно, но для потомков: это эквивалентно более легкому для чтения max(set(lVals), key=lVals.count), который подсчитывает O (n) для каждого уникального элемента lValsпримерно на O (n ^ 2) (при условии O (n) уникальных элементы). Использование collections.Counter(lVals).most_common(1)[0][0]стандартной библиотеки, как предлагает JoshAdel , составляет всего O (n).
Дугал
3

Хотя большинство приведенных выше ответов полезны, если вам: 1) он нужен для поддержки неположительных целочисленных значений (например, с плавающей запятой или отрицательных целых чисел ;-)), и 2) не на Python 2.7 (который collections.Counter требует), и 3) предпочитают не добавлять зависимость scipy (или даже numpy) к вашему коду, тогда решение чисто на Python 2.6, которое является O (nlogn) (то есть эффективным), просто так:

from collections import defaultdict

a = [1,2,3,1,2,1,1,1,3,2,2,1]

d = defaultdict(int)
for i in a:
  d[i] += 1
most_frequent = sorted(d.iteritems(), key=lambda x: x[1], reverse=True)[0]
JJC
источник
2

Мне нравится решение Джоша Аделя.

Но есть только одна загвоздка.

np.bincount()Решение работает только на номера.

Если у вас есть строки, collections.Counterрешение подойдет вам.

Викас
источник
1

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

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

Не забудьте отказаться от режима, когда len (np.argmax (counts))> 1

Lean Bravo
источник
1

В Python 3 должно работать следующее:

max(set(a), key=lambda x: a.count(x))
Юрий Клячко
источник
1

Начиная с Python 3.4, стандартная библиотека включает statistics.modeфункцию для возврата единственной наиболее распространенной точки данных.

from statistics import mode

mode([1, 2, 3, 1, 2, 1, 1, 1, 3, 2, 2, 1])
# 1

Если имеется несколько режимов с одинаковой частотой, statistics.modeвозвращает первый встреченный.


Начиная с Python 3.8, statistics.multimodeфункция возвращает список наиболее часто встречающихся значений в том порядке, в котором они были впервые обнаружены:

from statistics import multimode

multimode([1, 2, 3, 1, 2])
# [1, 2]
Ксавье Гихот
источник
0

Вот общее решение, которое можно применить вдоль оси, независимо от значений, используя чисто numpy. Я также обнаружил, что это намного быстрее, чем 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]
Девин Кэрнс
источник
-1

Я недавно делаю проект и использую collections.Counter. (Что меня мучило).

На мой взгляд, счетчик в коллекциях работает очень плохо. Это просто класс-оболочка dict ().

Что еще хуже, если вы используете cProfile для профилирования его метода, вы должны увидеть много вещей «__missing__» и «__instancecheck__», которые тратят все время впустую.

Будьте осторожны при использовании most_common (), потому что каждый раз он будет вызывать сортировку, что делает его очень медленным. и если вы используете most_common (x), он вызовет сортировку кучи, что также будет медленным.

Кстати, у numpy bincount тоже есть проблема: если вы используете np.bincount ([1,2,4000000]), вы получите массив с 4000000 элементами.

Вэйчу Лю
источник
3
Dict - это наиболее точно настроенная структура данных в Python, которая идеально подходит для подсчета произвольных объектов. В отличие от этого, биннинг работает только с числовыми значениями и не позволяет предотвратить сглаживание между близко расположенными дискретными значениями. В случае Counter метод __missing__ вызывается только тогда, когда элемент впервые виден; в противном случае его присутствие является бесплатным. Обратите внимание, что метод most_common () в большинстве случаев невероятно быстр, потому что куча очень мала по сравнению с общим набором данных. В большинстве случаев метод most_common () выполняет лишь немного больше сравнений, чем min () .
Raymond Hettinger