在列表中找到最常见的元素


179

在Python列表中查找最常见元素的有效方法是什么?

我的列表项可能无法散列,因此不能使用字典。同样在绘制时,应返回索引最低的项目。例:

>>> most_common(['duck', 'duck', 'goose'])
'duck'
>>> most_common(['goose', 'duck', 'duck', 'goose'])
'goose'

2
如果列表中的项目不可散列,您如何确定它们何时“相等”?确定不可散列项目是否相等的效率损失可能会抵消您希望通过好的算法获得的任何效率:)
HS。

3
我认为他的意思是,这些项可能是易变的,因此不能视为哈希图中的键...
fortran 2009年

1
是的,这就是我的意思-有时它会包含列表
-hoju


Answers:


97

提出了这么多解决方案,我很惊讶没有人提出我认为是显而易见的解决方案(对于不可哈希但可比较的元素)-[ itertools.groupby] [1]。 itertools提供快速,可重用的功能,并允许您将一些棘手的逻辑委托给经过良好测试的标准库组件。考虑例如:

import itertools
import operator

def most_common(L):
  # get an iterable of (item, iterable) pairs
  SL = sorted((x, i) for i, x in enumerate(L))
  # print 'SL:', SL
  groups = itertools.groupby(SL, key=operator.itemgetter(0))
  # auxiliary function to get "quality" for an item
  def _auxfun(g):
    item, iterable = g
    count = 0
    min_index = len(L)
    for _, where in iterable:
      count += 1
      min_index = min(min_index, where)
    # print 'item %r, count %r, minind %r' % (item, count, min_index)
    return count, -min_index
  # pick the highest-count/earliest item
  return max(groups, key=_auxfun)[0]

当然,这可以写得更简洁一些,但我的目标是最大程度地清晰。print可以不加注释这两个语句,以更好地了解运行中的机制。例如,带有未注释的打印:

print most_common(['goose', 'duck', 'duck', 'goose'])

发出:

SL: [('duck', 1), ('duck', 2), ('goose', 0), ('goose', 3)]
item 'duck', count 2, minind 1
item 'goose', count 2, minind 0
goose

如您所见,SL是一个成对的列表,每对一个项目,后跟原始列表中的项目索引(以实现关键条件,即,如果具有相同最高计数的“最常见”项目> 1,则结果必须是最早出现的一个)。

groupby仅按项目分组(通过operator.itemgetter)。辅助功能在max计算过程中每分组一次调用,它接收并在内部解压缩一个组-具有两个项目的元组,(item, iterable)其中可迭代的项目也是两个项目元组(item, original index)[[ SL]的项目]。

然后,辅助功能使用循环来确定组可迭代项中的条目数最小原始索引。它将返回那些作为组合的“质量关键字”,并且最小索引符号已更改,因此该max操作将考虑“更好”那些在原始列表中较早出现的项目。

此代码可能是更简单的,如果它担心一点点时间和空间,少谈大O问题,如...:

def most_common(L):
  groups = itertools.groupby(sorted(L))
  def _auxfun((item, iterable)):
    return len(list(iterable)), -L.index(item)
  return max(groups, key=_auxfun)[0]

相同的基本思想,只是表达得更简单,紧凑...但是,可惜的是,额外的O(N)辅助空间(将组的可迭代对象体现到列表中)和O(N平方)时间(获取L.index每个项目的总和) 。尽管过早的优化是编程中所有弊端的根源,但在O(N log N)可用时刻意选择O(N平方)方法与可扩展性背道而驰!

最后,对于那些更喜欢“单线”而不是清晰度和性能的人,可以使用名称经过适当修饰的1线附加版本:-)。

from itertools import groupby as g
def most_common_oneliner(L):
  return max(g(sorted(L)), key=lambda(x, v):(len(list(v)),-L.index(x)))[0]

3
如果您的列表具有不同的类型,则这会在Python3上中断。
AlexLordThorsen '16

2
groupby需要首先排序(O(NlogN)); 使用Counter()withmost_common()可以解决这个问题,因为它使用heapq来查找频率最高的项(仅一项,即O(N)时间)。由于Counter()现在已进行了高度优化(计数在C循环中进行),因此即使对于较小的列表,它也可以轻松击败该解决方案。它把它从水里吹出来,列出了很多。
马丁·彼得斯

只有对关系的“最低索引”要求才使它成为针对此问题的有效解决方案。对于更一般的情况,您绝对应该使用Counter方法。
马丁·彼得斯

@MartijnPieters也许您错过了问题的一部分,它说这些项目可能无法散列。
凌晨

@wim对,如果项目不可散列。这使得对定值和最大方法的投票更加不一致。
马丁·彼得斯

455

更简单的单行代码:

def most_common(lst):
    return max(set(lst), key=lst.count)

24
OP指出[..]绘制时应返回索引最低的项目。通常,此代码不满足该要求。
09年

2
另外,OP声明元素必须是可哈希的:集必须包含可哈希的对象。
Eric O Lebigot 09年

2
另外,这种方法在算法上很慢(对于中的每个元素set(lst),都必须再次检查整个列表)…虽然对于大多数用途来说可能足够快…
Eric O Lebigot 09年

9
您可以替换set(lst)lst,它也可以与非哈希元素一起使用;虽然慢。
newacct

24
看起来很有吸引力,但是从算法的角度来看,这是一个糟糕的建议。list.count()必须完整遍历该列表,而您需要对列表每个唯一项进行遍历。这使其成为O(NK)解(在最坏的情况下为O(N ^ 2))。Counter()仅使用O(N)时间!
马丁·彼得斯

188

这里借用,可以与Python 2.7一起使用:

from collections import Counter

def Most_Common(lst):
    data = Counter(lst)
    return data.most_common(1)[0][0]

它的工作速度比Alex的解决方案快4到6倍,比newacct建议的单线快50倍。

要检索在出现联系时首先出现在列表中的元素:

def most_common(lst):
    data = Counter(lst)
    return max(lst, key=data.get)

3
这对某些人可能有用,但是...不幸的是Counter是dict的子类,OP表示他不能使用字典(因为项可能不可散列)。
Danimal 2014年

13
喜欢这个。上面@newacct的单线可能很简单,但是它以O(n ^ 2)运行;也就是说,其中n是列表的长度。这个解是O(n)。
BoltzmannBrain

5
就像简单和速度一样……对于OP来说可能并不理想。但是非常适合我!
2015年

不返回索引最低的项目。most_common返回无序列表,而抓取(1)仅返回所需的内容。
AgentBawls

@AgentBawls:most_common按计数排序,而不是无序。就是说,如果出现联系,它将不会选择第一个元素;我添加了另一种使用计数器的方法,该计数器可以选择第一个元素。
user2357112支持Monica17年

62

您想要的在统计中称为模式,Python当然具有内置功能可以为您完全做到这一点:

>>> from statistics import mode
>>> mode([1, 2, 2, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6])
3

请注意,如果没有“最常见的元素”(例如,前两个元素并列的情况),则会上升StatisticsError,因为从统计学上讲,在这种情况下没有模式


9
这不满足OP对存在多个以上最常见值时返回的内容的要求-statistics.StatisticsError引发
Keith Hall

5
糟糕,阅读时错过了要求。我仍然相信这个答案很有价值,因为在这个问题上没有人建议过,对于那些要求最少的人来说,这是一个很好的解决方案。这是“列表python中最常见的项目”的最佳结果之一
Luiz Berti

1
在这种情况下,请在pandas DataFrames中使用mode函数。
Elmex80s

1
赞成,这个应该更高。而且这并不难满足与OP的要求简单的尝试,除了(见我stackoverflow.com/a/52952300/6646912
KRASSOWSKI

1
@BreakBadSP您的答案由于有额外的内存而使用了更多的内存,这set似乎是合理的O(n^3)
Luiz Berti

13

无需最低索引,您可以使用collections.Counter此方法:

from collections import Counter

a = [1936, 2401, 2916, 4761, 9216, 9216, 9604, 9801] 

c = Counter(a)

print(c.most_common(1)) # the one most common element... 2 would mean the 2 most common
[(9216, 2)] # a set containing the element, and it's count in 'a'

简单快速。你[R我教父😏✌
chainstair

1
此答案需要更多支持,因为它使用标准模块和2行代码解决了列表中元素发生计数的一般任务
pcko1,

9

如果它们不可散列,则可以对它们进行排序,并对计数项目的结果进行单个循环(相同的项目将彼此相邻)。但是使它们可散列并使用dict可能更快。

def most_common(lst):
    cur_length = 0
    max_length = 0
    cur_i = 0
    max_i = 0
    cur_item = None
    max_item = None
    for i, item in sorted(enumerate(lst), key=lambda x: x[1]):
        if cur_item is None or cur_item != item:
            if cur_length > max_length or (cur_length == max_length and cur_i < max_i):
                max_length = cur_length
                max_i = cur_i
                max_item = cur_item
            cur_length = 1
            cur_i = i
            cur_item = item
        else:
            cur_length += 1
    if cur_length > max_length or (cur_length == max_length and cur_i < max_i):
        return cur_item
    return max_item

这是ideone.com/Nq81vf的一种简单方法,与Alex的Counter()解决方案进行比较
Miguel

6

这是一个O(n)解决方案。

mydict   = {}
cnt, itm = 0, ''
for item in reversed(lst):
     mydict[item] = mydict.get(item, 0) + 1
     if mydict[item] >= cnt :
         cnt, itm = mydict[item], item

print itm

(反转用于确保它返回最低的索引项)


5

对列表的副本进行排序,并找到最长的运行时间。您可以在使用每个元素的索引对列表进行排序之前对列表进行修饰,然后在出现平局的情况下选择从最低索引开始的运行。


这些项目可能不具有可比性。
Pawel Furmaniak 2013年

4

单线:

def most_common (lst):
    return max(((item, lst.count(item)) for item in set(lst)), key=lambda a: a[1])[0]

3
# use Decorate, Sort, Undecorate to solve the problem

def most_common(iterable):
    # Make a list with tuples: (item, index)
    # The index will be used later to break ties for most common item.
    lst = [(x, i) for i, x in enumerate(iterable)]
    lst.sort()

    # lst_final will also be a list of tuples: (count, index, item)
    # Sorting on this list will find us the most common item, and the index
    # will break ties so the one listed first wins.  Count is negative so
    # largest count will have lowest value and sort first.
    lst_final = []

    # Get an iterator for our new list...
    itr = iter(lst)

    # ...and pop the first tuple off.  Setup current state vars for loop.
    count = 1
    tup = next(itr)
    x_cur, i_cur = tup

    # Loop over sorted list of tuples, counting occurrences of item.
    for tup in itr:
        # Same item again?
        if x_cur == tup[0]:
            # Yes, same item; increment count
            count += 1
        else:
            # No, new item, so write previous current item to lst_final...
            t = (-count, i_cur, x_cur)
            lst_final.append(t)
            # ...and reset current state vars for loop.
            x_cur, i_cur = tup
            count = 1

    # Write final item after loop ends
    t = (-count, i_cur, x_cur)
    lst_final.append(t)

    lst_final.sort()
    answer = lst_final[0][2]

    return answer

print most_common(['x', 'e', 'a', 'e', 'a', 'e', 'e']) # prints 'e'
print most_common(['goose', 'duck', 'duck', 'goose']) # prints 'goose'


2

您可能不再需要它了,但这就是我为类似问题所做的。(由于注释,它看起来比它更长。)

itemList = ['hi', 'hi', 'hello', 'bye']

counter = {}
maxItemCount = 0
for item in itemList:
    try:
        # Referencing this will cause a KeyError exception
        # if it doesn't already exist
        counter[item]
        # ... meaning if we get this far it didn't happen so
        # we'll increment
        counter[item] += 1
    except KeyError:
        # If we got a KeyError we need to create the
        # dictionary key
        counter[item] = 1

    # Keep overwriting maxItemCount with the latest number,
    # if it's higher than the existing itemCount
    if counter[item] > maxItemCount:
        maxItemCount = counter[item]
        mostPopularItem = item

print mostPopularItem

1
可以使用计数器[项目] = counter.get(项目,0)+ 1,以取代的try /除外部
雪鱼

1

Luiz的答案为基础,但满足“如果绘制具有最低索引的项目应返回”条件:

from statistics import mode, StatisticsError

def most_common(l):
    try:
        return mode(l)
    except StatisticsError as e:
        # will only return the first element if no unique mode found
        if 'no unique mode' in e.args[0]:
            return l[0]
        # this is for "StatisticsError: no mode for empty data"
        # after calling mode([])
        raise

例:

>>> most_common(['a', 'b', 'b'])
'b'
>>> most_common([1, 2])
1
>>> most_common([])
StatisticsError: no mode for empty data

1

我正在使用scipy stat模块和lambda进行此操作:

import scipy.stats
lst = [1,2,3,4,5,6,7,5]
most_freq_val = lambda x: scipy.stats.mode(x)[0][0]
print(most_freq_val(lst))

结果:

 most_freq_val = 5

0

这里:

def most_common(l):
    max = 0
    maxitem = None
    for x in set(l):
        count =  l.count(x)
        if count > max:
            max = count
            maxitem = x
    return maxitem

我有一种模糊的感觉,在标准库中某处有一个方法可以为您提供每个元素的计数,但是我找不到它。


3
“最大”是一种方法。您会更改变量的名称吗?
Pratik Deoghare,2009年

1
请注意,set()也需要可哈希项,因此在这种情况下解决方案将不起作用。
卢卡斯·拉林斯基

等等,我错过了不可散列的部分。但是,如果对象具有相等性,则使它们变得可散列应该很容易。
Lennart Regebro 09年

0

如果排序和哈希都不可行,但是相等比较(==)可用,这是很明显的慢速解决方案(O(n ^ 2)):

def most_common(items):
  if not items:
    raise ValueError
  fitems = [] 
  best_idx = 0
  for item in items:   
    item_missing = True
    i = 0
    for fitem in fitems:  
      if fitem[0] == item:
        fitem[1] += 1
        d = fitem[1] - fitems[best_idx][1]
        if d > 0 or (d == 0 and fitems[best_idx][2] > fitem[2]):
          best_idx = i
        item_missing = False
        break
      i += 1
    if item_missing:
      fitems.append([item, 1, i])
  return items[best_idx]

但是,如果列表(n)的长度很大,则使您的项目可散列或可排序(如其他答案所建议)几乎总是会使查找最常见的元素更快。O(n)平均带有散列,最差的O(n * log(n))用于排序。


对下降投票者:这个答案有什么问题?当排序和哈希都不可行时,是否有其他答案可以提供解决方案?
pt

0
>>> li  = ['goose', 'duck', 'duck']

>>> def foo(li):
         st = set(li)
         mx = -1
         for each in st:
             temp = li.count(each):
             if mx < temp:
                 mx = temp 
                 h = each 
         return h

>>> foo(li)
'duck'

当n大且唯一元素的数量也大时,这具有可怕的性能特征:O(n)用于转换为集合,O(m * n)= O(n ^ 2)用于计数(其中m是唯一身份的数量)。排序和遍历的排序为O(n log n),遍历的值为0(n)。
jmucchiello,2009年

1
是的,没错。现在我知道这是一个糟糕的解决方案,为什么。谢谢评论!!:-)
Pratik Deoghare,2009年

0

我需要在最近的程序中执行此操作。我承认,我听不懂亚历克斯的回答,所以这就是我的最终选择。

def mostPopular(l):
    mpEl=None
    mpIndex=0
    mpCount=0
    curEl=None
    curCount=0
    for i, el in sorted(enumerate(l), key=lambda x: (x[1], x[0]), reverse=True):
        curCount=curCount+1 if el==curEl else 1
        curEl=el
        if curCount>mpCount \
        or (curCount==mpCount and i<mpIndex):
            mpEl=curEl
            mpIndex=i
            mpCount=curCount
    return mpEl, mpCount, mpIndex

我将其与Alex的解决方案进行了计时,对于短列表,它的速度大约提高了10-15%,但是一旦超过100个元素或更多(测试到200000),它的速度就会降低20%。


0
ans  = [1, 1, 0, 0, 1, 1]
all_ans = {ans.count(ans[i]): ans[i] for i in range(len(ans))}
print(all_ans)
all_ans={4: 1, 2: 0}
max_key = max(all_ans.keys())

4

print(all_ans[max_key])

1个


-1

嗨,这是一个非常简单的大O(n)解决方案

L = [1, 4, 7, 5, 5, 4, 5]

def mode_f(L):
# your code here
    counter = 0
    number = L[0]
    for i in L:
        amount_times = L.count(i)
        if amount_times > counter:
            counter = amount_times
            number = i

    return number

列表中大部分时间重复的元素的编号


-2
def mostCommonElement(list):
  count = {} // dict holder
  max = 0 // keep track of the count by key
  result = None // holder when count is greater than max
  for i in list:
    if i not in count:
      count[i] = 1
    else:
      count[i] += 1
    if count[i] > max:
      max = count[i]
      result = i
  return result

mostCommonElement([[“ a”,“ b”,“ a”,“ c”])->“ a”


所有其他答案。您想要我链接它们吗?
12

-3
 def most_common(lst):
    if max([lst.count(i)for i in lst]) == 1:
        return False
    else:
        return max(set(lst), key=lst.count)

6
请提供一些有关您的代码的信息,只是发布代码不是一个完整的答案
jhhoff02

1
有人应该在其他15个答案中使用这个吗?
所有工人都

-5
def popular(L):
C={}
for a in L:
    C[a]=L.count(a)
for b in C.keys():
    if C[b]==max(C.values()):
        return b
L=[2,3,5,3,6,3,6,3,6,3,7,467,4,7,4]
print popular(L)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.