使用Python在文件中计算bigrams(由两个单词组成的对)

30
我想使用python统计文件中所有相邻单词对的出现次数(即bigrams),由于我处理的是非常大的文件,所以我正在寻找一种高效的方法。我尝试在文件内容上使用带有正则表达式"\w+\s\w+" 的count方法,但它并不高效。
例如,假设我想从文件a.txt中计算bigrams的数量,该文件具有以下内容:
"the quick person did not realize his speed and the quick person bumped "

对于上面的文件,其二元组集合及其计数如下:

(the,quick) = 2
(quick,person) = 2
(person,did) = 1
(did, not) = 1
(not, realize) = 1
(realize,his) = 1
(his,speed) = 1
(speed,and) = 1
(and,the) = 1
(person, bumped) = 1
我在 Python 中遇到了一个 Counter 对象的示例,用于计算单词数量。它还使用了正则表达式方法。
这个示例是这样的:
>>> # Find the ten most common words in Hamlet
>>> import re
>>> from collections import Counter
>>> words = re.findall('\w+', open('a.txt').read())
>>> print Counter(words)
以上代码的输出为:
[('the', 2), ('quick', 2), ('person', 2), ('did', 1), ('not', 1),
 ('realize', 1),  ('his', 1), ('speed', 1), ('bumped', 1)]

我想知道是否可以使用计数器对象来获取二元组的计数。欢迎使用计数器对象或正则表达式以外的任何方法。


粘贴问题中的示例文本。 - Ashwini Chaudhary
你需要处理多行还是每个文件的文本都在一行上? - mhawke
1
可能是计算二元频率的重复问题。 - David Robinson
是的,mhawke,文件中的文本只有一行。 - swap310
Ashwini Chaudhary,我已经在上面的代码标签中包含了示例文本。对不起给您带来的不便! - swap310
请参见:https://dev59.com/EGXWa4cB1Zd3GeqPLESS。 - georg
6个回答

52

一些 itertools 魔法:

>>> import re
>>> from itertools import islice, izip
>>> words = re.findall("\w+", 
   "the quick person did not realize his speed and the quick person bumped")
>>> print Counter(izip(words, islice(words, 1, None)))

输出:

Counter({('the', 'quick'): 2, ('quick', 'person'): 2, ('person', 'did'): 1, 
  ('did', 'not'): 1, ('not', 'realize'): 1, ('and', 'the'): 1, 
  ('speed', 'and'): 1, ('person', 'bumped'): 1, ('his', 'speed'): 1, 
  ('realize', 'his'): 1})

奖励问题

获取任意n-gram的频率:

from itertools import tee, islice

def ngrams(lst, n):
  tlst = lst
  while True:
    a, b = tee(tlst)
    l = tuple(islice(a, n))
    if len(l) == n:
      yield l
      next(b)
      tlst = b
    else:
      break

>>> Counter(ngrams(words, 3))

输出:

Counter({('the', 'quick', 'person'): 2, ('and', 'the', 'quick'): 1, 
  ('realize', 'his', 'speed'): 1, ('his', 'speed', 'and'): 1, 
  ('person', 'did', 'not'): 1, ('quick', 'person', 'did'): 1, 
  ('quick', 'person', 'bumped'): 1, ('did', 'not', 'realize'): 1, 
  ('speed', 'and', 'the'): 1, ('not', 'realize', 'his'): 1})

这也适用于惰性可迭代对象和生成器。因此,您可以编写一个生成器逐行读取文件,生成单词,并将其传递给ngarms以便在不读取整个文件到内存中的情况下进行惰性消耗。


itertools的ngram函数非常好用!但是,如果您需要执行其他文本分析,那么值得查看一下TextBlob。它也有一个TextBlob.ngrams()函数,基本上做同样的事情。我已经测试了itertools和TextBlob函数,它们似乎具有相当的速度和结果(itertools函数略微优势)。 - Montmons
哎呀,我忘记在比较中包括ngrams的计数了,TextBlob函数本身不会这样做。我尝试使用Counter编写一个函数来实现它,但总体而言,这使得它成为一个更慢的选项。所以...itertools胜出。 - Montmons
这很聪明。FWIW它的作用如下:L1是“words”,L2是“islice(words, 1, None)”,它将句子切成以第二个单词开头的单个单词。“izip(words, islice(words, 1, None))”然后将L1与L2一起压缩,以便来自L1的“the”与来自L2的“quick”匹配,“quick”来自L1与来自L2的“person”匹配,等等。计数器然后计算这些对。对于Python3,您不再需要导入“izip”,只需使用“zip”。@st0le下面的答案实际上也是做同样的事情。 - Casey L

14

那么zip()怎么样?

import re
from collections import Counter
words = re.findall('\w+', open('a.txt').read())
print(Counter(zip(words,words[1:])))

5
您可以简单地使用Counter来处理任意n_gram,如下所示:
from collections import Counter
from nltk.util import ngrams 

text = "the quick person did not realize his speed and the quick person bumped "
n_gram = 2
Counter(ngrams(text.split(), n_gram))
>>>
Counter({('and', 'the'): 1,
         ('did', 'not'): 1,
         ('his', 'speed'): 1,
         ('not', 'realize'): 1,
         ('person', 'bumped'): 1,
         ('person', 'did'): 1,
         ('quick', 'person'): 2,
         ('realize', 'his'): 1,
         ('speed', 'and'): 1,
         ('the', 'quick'): 2})

对于3元组,只需将n_gram更改为3:

n_gram = 3
Counter(ngrams(text.split(), n_gram))
>>>
Counter({('and', 'the', 'quick'): 1,
         ('did', 'not', 'realize'): 1,
         ('his', 'speed', 'and'): 1,
         ('not', 'realize', 'his'): 1,
         ('person', 'did', 'not'): 1,
         ('quick', 'person', 'bumped'): 1,
         ('quick', 'person', 'did'): 1,
         ('realize', 'his', 'speed'): 1,
         ('speed', 'and', 'the'): 1,
         ('the', 'quick', 'person'): 2})

1
这个没问题,但是缺少一个导入 - 你需要添加 from nltk.util import ngrams。顺便说一句,它似乎比被接受的解决方案运行得更快。 - Casey L

5

Python 3.10开始,新的pairwise函数提供了一种滑动连续元素对的方式,使得您的用例变得非常简单:

from itertools import pairwise
import re
from collections import Counter

# text = "the quick person did not realize his speed and the quick person bumped "
Counter(pairwise(re.findall('\w+', text)))
# Counter({('the', 'quick'): 2, ('quick', 'person'): 2, ('person', 'did'): 1, ('did', 'not'): 1, ('not', 'realize'): 1, ('realize', 'his'): 1, ('his', 'speed'): 1, ('speed', 'and'): 1, ('and', 'the'): 1, ('person', 'bumped'): 1})

中间结果的详细信息:

re.findall('\w+', text)
# ['the', 'quick', 'person', 'did', 'not', 'realize', 'his', ...]
pairwise(re.findall('\w+', text))
# [('the', 'quick'), ('quick', 'person'), ('person', 'did'), ...]

1

这个问题被提出并得到成功回答已经有很长一段时间了。我从回答中受益,创建了自己的解决方案。我想分享它:

    import regex
    bigrams_tst = regex.findall(r"\b\w+\s\w+", open(myfile).read(), overlapped=True)

这将提供所有没有被标点符号打断的二元组。

0
可以使用 CountVectorizer 来自 scikit-learnpip install sklearn)生成bigrams(或者更一般地,任何ngram)。
示例(在Python 3.6.7和scikit-learn 0.24.2下测试)。
import sklearn.feature_extraction.text

ngram_size = 2
train_set = ['the quick person did not realize his speed and the quick person bumped']

vectorizer = sklearn.feature_extraction.text.CountVectorizer(ngram_range=(ngram_size,ngram_size))
vectorizer.fit(train_set) # build ngram dictionary
ngram = vectorizer.transform(train_set) # get ngram
print('ngram: {0}\n'.format(ngram))
print('ngram.shape: {0}'.format(ngram.shape))
print('vectorizer.vocabulary_: {0}'.format(vectorizer.vocabulary_))

输出:

>>> print('ngram: {0}\n'.format(ngram)) # Shows the bi-gram count
ngram:   (0, 0) 1
  (0, 1)        1
  (0, 2)        1
  (0, 3)        1
  (0, 4)        1
  (0, 5)        1
  (0, 6)        2
  (0, 7)        1
  (0, 8)        1
  (0, 9)        2

>>> print('ngram.shape: {0}'.format(ngram.shape))
ngram.shape: (1, 10)
>>> print('vectorizer.vocabulary_: {0}'.format(vectorizer.vocabulary_))
vectorizer.vocabulary_: {'the quick': 9, 'quick person': 6, 'person did': 5, 'did not': 1, 
'not realize': 3, 'realize his': 7, 'his speed': 2, 'speed and': 8, 'and the': 0, 
'person bumped': 4}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接