【问题标题】:Counting bigrams (pair of two words) in a file using Python使用 Python 计算文件中的二元组(两个单词对)
【发布时间】:2012-09-11 09:48:10
【问题描述】:

我想使用 python 计算文件中所有二元组(相邻单词对)的出现次数。在这里,我正在处理非常大的文件,因此我正在寻找一种有效的方法。我尝试在文件内容上使用带有正则表达式 "\w+\s\w+" 的计数方法,但它并没有被证明是有效的。

例如假设我想计算文件 a.txt 中的二元组数,该文件具有以下内容:

"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 对象的示例,它用于计算 unigrams(单个单词)。它还使用正则表达式方法。

示例如下:

>>> # 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)]

我想知道是否可以使用 Counter 对象来获取二元数。 除了 Counter 对象或正则表达式之外的任何方法也将不胜感激。

【问题讨论】:

  • 粘贴有问题的示例文本。
  • 你必须处理多行还是每个文件的文本都在一行?
  • Counting bi-gram frequencies 的可能重复项
  • 是的,mhawke,文件中的文本是单行的。
  • Ashwini Chaudhary,我在上面的代码标签中包含了示例文本。给您带来的不便深表歉意!

标签: python regex n-gram


【解决方案1】:

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'), ...]

【讨论】:

    【解决方案2】:

    可以使用来自scikit-learn (pip install sklearn) 的CountVectorizer 来生成二元组(或更一般地说,任何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}
    

    【讨论】:

      【解决方案3】:

      您可以简单地将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-gram,只需将 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})
      

      【讨论】:

      • 这很好,但缺少导入 - 您需要添加 from nltk.util import ngrams。 FWIW 它的运行速度似乎比公认的解决方案快一点。
      【解决方案4】:

      这个问题被问到并成功回答已经很久了。我受益于创建自己的解决方案的响应。我想分享它:

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

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

      【讨论】:

        【解决方案5】:

        一些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 函数的优势很小)。
        • 糟糕,我忘记在比较中包括计算 ngram,TextBlob 函数本身不会这样做。我尝试为它编写一个带有 Counter 的函数,但总的来说,这使它成为一个慢得多的选择。所以.. itertools 赢了。
        • 这很聪明。 FWIW 它的作用如下:L1 是words,L2 是islice(words, 1, None),它将句子分成从第二个单词开始的单个单词。 izip(words, islice(words, 1, None)) 然后将 L1 与 L2 拉上拉链,以便 L1 中的“the”与 L2 中的“quick”匹配,L1 中的“quick”与 L2 中的“person”匹配,等等。然后计数器计算这些对。而对于 Python3,您不再需要导入 izip,只需使用 zip。 @st0le 下面的答案实际上做了同样的事情。
        【解决方案6】:

        zip()怎么样?

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多