Python:使用Counter進行計數統計及collections模塊

    計數統計就是統計某一項出現的次數。實際應用中很多需求需要用到這個模型。比如測試樣本中某一指出現的次數、日誌分析中某一消息出現的頻率等等‘這種類似的需求有很多實現方法。下面就列舉幾條。

(1)使用dict

看下面代碼

#coding=utf-8
data = ['a','2',2,4,5,'2','b',4,7,'a',5,'d','a','z']
count_frq = dict()
for one in data:
     if one in count_frq:
          count_frq[one] += 1
     else:
          count_frq[one] = 1

print count_frq

輸出結果如下:

{'a': 3, 2: 1, 'b': 1, 4: 2, 5: 2, 7: 1, '2': 2, 'z': 1, 'd': 1}

這種方法最簡單,也是最容易想到的,鄙人這寫這篇博文之前用的最多,不過以後應該不會用來,我們應該使代碼更加Pythonic

(2)使用set和list

代碼如下:

#coding=utf-8
data = ['a','2',2,4,5,'2','b',4,7,'a',5,'d','a','z']
data_set = set(data)
count_list = []
for one in data_set:
     count_list.append((one,data.count(one)))

print count_list

輸出結果如下:

[('a', 3), (2, 1), ('b', 1), (4, 2), (5, 2), (7, 1), ('2', 2), ('z', 1), ('d', 1)]

    這裏面利用了list的通用方法和集合(set)的特性,集合是一個無序不重複的元素集,而工廠函數set()可以將列表轉換爲一個無序不重複的元素集合。

以上方法都很簡單,但不夠Pythonic。下面來介紹collections中的Counter類。

(一)Counter類

    Counter類的目的是用來跟蹤值出現的次數。它是一個無序的容器類型,以字典的鍵值對形式存儲,其中元素作爲key,其計數作爲value。計數值可以是任意的Interger(包括0和負數)支持集合操作+、-、&、|,其中&、|操作分別返回兩個Counter對象各元素的最大值和最小值。

(1)Counter的初始化

跟平時自定義類的初始化方法差不多,如下:

c = Counter("hello world")#可迭代對象創建

c = Counter(h=1,l=3,o=2)#關鍵字創建

c = Counter({'h':1,'l':3,'o':2})#字典創建

c = Counter()#空Counter類

(2)Counter類常見方法

elements():返回一個迭代器。元素被重複了多少次,在該迭代器中就包含多少個該元素。所有元素按照字母序排序,個數小於1的元素不被包含。

update():用於統計對象元素的更新,原有的Counter計數器對象與新增元素的統計計數值相加而不是直接替換。

subtract():該方法用於計數器對象中元素統計值減少,輸入輸出的統計值書可以爲0或者負數的。

most_common([n]):可以查找出前n個出現頻率最高的元素以及它們對於的次數,也就是說頻率搞的排在最前面。

copy():淺拷貝。關於淺拷貝,深拷貝可以參考上篇博文。http://11026142.blog.51cto.com/11016142/1851472

所以上面的例子用Counter類的話,也很簡單,代碼如下:

#coding=utf-8
from collections import Counter
data = ['a','2',2,4,5,'2','b',4,7,'a',5,'d','a','z']
c = Counter(data)
print c

輸出結果如下:

Counter({'a': 3, 4: 2, 5: 2, '2': 2, 2: 1, 'b': 1, 7: 1, 'z': 1, 'd': 1})

咱們接着看代碼

print c.elements()
print list(c.elements())

輸出結果如下:

<itertools.chain object at 0x7f94b81683d0>

['a', 'a', 'a', 2, 'b', 4, 4, 5, 5, 7, '2', '2', 'z', 'd']

c['z'] -= 1
print c
print c.elements()
print list(c.elements())

輸出結果如下:

Counter({'a': 3, 4: 2, 5: 2, '2': 2, 2: 1, 'b': 1, 7: 1, 'd': 1, 'z': 0})

<itertools.chain object at 0x7f0e928723d0>

['a', 'a', 'a', 2, 'b', 4, 4, 5, 5, 7, '2', '2', 'd']

元素’z'的統計值變爲了0,然後進行elements()運算後,‘z'就被排除掉了。

c.update("aaaa")
print c

輸出結果:

Counter({'a': 7, 4: 2, 5: 2, '2': 2, 2: 1, 'b': 1, 7: 1, 'd': 1, 'z': 0})

update()在原基礎上增加了計數值

c.subtract("aaaaa")
print c

輸出結果如下:

Counter({'a': 2, 4: 2, 5: 2, '2': 2, 2: 1, 'b': 1, 7: 1, 'd': 1, 'z': 0})

subtract()在原基礎上減少計數值

print c.most_common()

輸出結果如下:

[('a', 2), (4, 2), (5, 2), ('2', 2), (2, 1), ('b', 1), (7, 1), ('d', 1), ('z', 0)]

以上代碼都是連接在一起的。

(3)算術和集合操作

#coding=utf-8
from collections import Counter
data = ['a','2','2','b','a','d','a',]
c = Counter(data)
b = Counter(a=1,b=2)
print c
print b
print b+c  # c[x] + d[x]
print c-b   # subtract(只保留正數計數的元素)
print c&b   # 交集:  min(c[x], d[x])
print c|b    # 並集:  max(c[x], d[x])

輸出結果如下:


Counter({'a': 3, '2': 2, 'b': 1, 'd': 1})

Counter({'b': 2, 'a': 1})

Counter({'a': 4, 'b': 3, '2': 2, 'd': 1})

Counter({'a': 2, '2': 2, 'd': 1})

Counter({'a': 1, 'b': 1})

Counter({'a': 3, '2': 2, 'b': 2, 'd': 1})


(4)其它

Counter類返回值跟字典很類似,所以字典類的方法對Counter對象也適用。如下:

#coding=utf-8
from collections import Counter
data = ['a','2',2,4,5,'2','b',4,7,'a',5,'d','a','z']
c = Counter(data)
print c.keys()
print c.has_key('a')
print c.get('a')
print c.items()
print c.values()
print c.viewitems()
print c.viewkeys()

輸出如下:

['a', 2, 'b', 4, 5, 7, '2', 'z', 'd']

True

3

[('a', 3), (2, 1), ('b', 1), (4, 2), (5, 2), (7, 1), ('2', 2), ('z', 1), ('d', 1)]

[3, 1, 1, 2, 2, 1, 2, 1, 1]

dict_items([('a', 3), (2, 1), ('b', 1), (4, 2), (5, 2), (7, 1), ('2', 2), ('z', 1), ('d', 1)])

dict_keys(['a', 2, 'b', 4, 5, 7, '2', 'z', 'd'])

這只是其中一部分,其它的方法可以參考字典類的方法。

另外,Counter對象還支持工廠函數操作set()、list()、dict().


(二)collections模塊中其它類/方法

常見的內置數據類型有列表、字典、集合、元組等等,collections模塊,在此基礎上定義了一些其它的數據類型,如果用的好的話,對提升代碼運行效率還是有很大的幫助的,下面一一介紹。

1.deque

 deque其實是 double-ended queue 的縮寫,翻譯過來就是雙端隊列。與list相比, 使用list存儲數據時,按索引訪問元素很快,但是插入和刪除元素就很慢了,因爲list是線性存儲,數據量大的時候,插入和刪除效率很低;deque是爲了高效實現插入和刪除操作的雙向列表,適合用於隊列和棧,它最大的好處就是實現了從隊列 頭部快速增加和取出對象。

雙端隊列的創建很簡單,如下:

from collections import deque
q = deque(['a', 'b', 'c'])

雙端隊列的主要方法如下:

append():在右邊加入一個元素

appendleft():在左邊加入一個元素

clear():情況雙端隊列,使其長度爲0

count():統計某個元素出現的次數

extend():擴展隊列,接受一個可迭代對象參數

extendleft():也是擴展隊列,也是接受一個可迭代對象參數,與extend()不同的是,先把可迭代對象翻轉後在添加到列表前端

pop():從deque的右端刪除一個元素

popleft():從deque的左端刪除一個元素。

remove():刪除一個元素

reverse():對deque對象反序

rotate():將左端元素右移n個位置,如果是負數表示向左移。

前面幾個方法都比較簡單,也比較好理解,主要是最後一個方法可能有點難理解,通過幾個例子來說明。

#coding=utf-8
from collections import deque
q = deque(['a', 'b', 'c'])
print q
q.rotate(2)
print q

結果如下:

deque(['a', 'b', 'c'])

deque(['b', 'c', 'a'])

它就相當於這三個元素組成了一個“閉環”,在“閉環”裏移動。另外,通信和電子信息等專業,如果學過單片機,應該知道×××燈,其實利用rotate()函數,我們也可以寫個類似的“×××燈"。代碼如下:

#coding=utf-8
import sys
import time
from collections import deque
fancy_loading = deque('>--------------------')
while True:
    print '\r%s' % ''.join(fancy_loading),
    fancy_loading.rotate(1)
    sys.stdout.flush()
    time.sleep(0.1)

有興趣的可以運行一下該代碼看看效果,對理解這個函數會有一定的幫助。


2. namedtuple

 namedtuple正如其名字,給元組命名,術語就是命名元組。namedtuple主要用來產生可以使用名稱來訪問元素的數據對象,通常用來增強代碼的可讀性, 在訪問一些tuple類型的數據時尤其好用。看下面例子

#coding=utf-8
Bob=('bob',30,'male')  
print 'Representation:',Bob  
Jane=('Jane',29,'female')  
print 'Field by index:',Jane[0]  
for people in [Bob,Jane]:  
    print "%s is %d years old %s" % people

Bob與Jane是元組,如果想獲取就用索引,比如上面的Jane[0],如果元素很多的時候操作起來就很麻煩。

#coding=utf-8
import collections  
Person = collections.namedtuple('Person','name age gender')  
print 'Type of Person:', type(Person)  
Bob = Person(name='Bob', age=30, gender='male')  
print 'Representation:', Bob  
Jane = Person(name='Jane', age=29, gender='female')  
print 'Field by Name:', Jane.name  
for people in [Bob,Jane]:  
     print "%s is %d years old %s" % people

 解釋一下nametuple的幾個參數:

     以Person = collections.namedtuple(‘Person’, 'name age gender’)爲例,其中 ’Person’是這個namedtuple的名稱,後面的’name age gender’這個字符串中三個用空格隔開的字符告訴我們,我們的這個namedtuple有三個元素,分別名爲name, age和gender。也可以這樣表示,用中括號或者小括號,Person = collections.namedtuple(‘Person’, ['name','age','gender’])或者Person =collections.namedtuple(‘Person’, ('name','age','gender’)),也就是說這個表達式是在定義一個nametuple型的Person類,它有三個屬性,然後在創建它的時候可以通過Bob = Person(name=’Bob’, age=30, gender=’male’)這種方式,這類似於Python中類對象的使用。而且,我們也可以像訪問類對象的屬性那樣使用Jane.name這種方式訪問namedtuple的元素。

 其輸出結果如下:

 Type of Person: <type 'type'>  

Representation: Person(name='Bob', age=30, gender='male')  

Field by Name: Jane  

Bob is 30 years old male  

Jane is 29 years old female  


  但是在使用namedtyuple的時候要注意其中的名稱不能使用Python的關鍵字,如:class def等;而且也不能有重複的元素名稱,比如:不能有兩個’age age’。如果出現這些情況,程序會報錯。但是,在實際使用的時候可能無法避免這種情況,比如:可能我們的元素名稱是從數據庫裏讀出來的記錄,這樣很難保 證一定不會出現Python關鍵字。這種情況下的解決辦法是將namedtuple的重命名模式打開,這樣如果遇到Python關鍵字或者有重複元素名時,自動進行重命名。

如下代碼:

#coding=utf-8
import collections
with_class=collections.namedtuple('Person','name age class gender',rename=True)
print with_class._fields
two_ages=collections.namedtuple('Person','name age gender age',rename=True)
print two_ages._fields

輸出結果如下:

('name', 'age', '_2', 'gender')

('name', 'age', 'gender', '_3')

使用rename=True的方式打開重命名選項。可以看到第一個集合中的class被重命名爲 ‘_2′ ; 第二個集合中重複的age被重命名爲 ‘_3′,這是因爲namedtuple在重命名的時候使用了下劃線 _ 加元素所在索引數的方式進行重命名。

3.OrderedDict

    直譯的話就是有序字典。dict這個數據結構由於hash的特性,是無序的,這在有的時候會帶來一些麻煩,還好collections模塊爲我們提供了OrderedDict,當你要獲得一個有序的字典對象時,可以用OrderedDict,它是dict的子類,它記住了內容添加的順序。看下面代碼:

#coding=utf-8
from collections import OrderedDict
items = (
    ('A', 1),
    ('B', 2),
    ('C', 3)
)
regular_dict = dict(items)
ordered_dict = OrderedDict(items)
print 'Regular Dict:'
for k, v in regular_dict.items():
    print k, v
print 'Ordered Dict:'
for k, v in ordered_dict.items():
    print k, v

輸出結果如下:

Regular Dict:

A 1

C 3

B 2

Ordered Dict:

A 1

B 2

C 3

注意,OrderedDict的Key會按照插入的順序排列,不是Key本身排序。

OrderedDict可以實現一個FIFO(先進先出)的dict,當容量超出限制時,先刪除最早添加的Key:

from collections import OrderedDict

class LastUpdatedOrderedDict(OrderedDict):

    def __init__(self, capacity):
        super(LastUpdatedOrderedDict, self).__init__()
        self._capacity = capacity

    def __setitem__(self, key, value):
        containsKey = 1 if key in self else 0
        if len(self) - containsKey >= self._capacity:
            last = self.popitem(last=False)
            print 'remove:', last
        if containsKey:
            del self[key]
            print 'set:', (key, value)
        else:
            print 'add:', (key, value)
        OrderedDict.__setitem__(self, key, value)

上面的代碼不難理解,可以仔細理解下。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章