Подсчет биграмм (пара из двух слов) в файле с использованием Python

Я хочу подсчитать количество вхождений всех биграмм (пар смежных слов) в файле с помощью 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, который используется для подсчета униграмм (отдельных слов). Он также использует подход регулярных выражений.

Пример выглядит следующим образом:

>>> # 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 или регулярного выражения, также будет оценен.


person Swapnil Nawale    schedule 19.09.2012    source источник
comment
вставьте образец текста в вопросе.   -  person Ashwini Chaudhary    schedule 19.09.2012
comment
Вам нужно обрабатывать несколько строк или текст находится в одной строке в файле?   -  person mhawke    schedule 19.09.2012
comment
возможный дубликат Подсчет частот биграмм   -  person David Robinson    schedule 19.09.2012
comment
Да mhawke, текст в файле на одной строке.   -  person Swapnil Nawale    schedule 19.09.2012
comment
Ашвини Чаудхари, я включил образец текста в теги кода выше. Приносим извинения за доставленные неудобства!   -  person Swapnil Nawale    schedule 19.09.2012
comment
см. также stackoverflow.com/questions /11147399/   -  person georg    schedule 19.09.2012


Ответы (6)


Немного 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-грамма:

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 для ленивого использования без чтения всего файла в памяти.

person Abhinav Sarkar    schedule 19.09.2012
comment
Функция itertools ngram великолепна! Однако, если вам нужно выполнить дополнительный текстовый анализ, возможно, стоит проверить TextBlob. Он также имеет функцию TextBlob.ngrams(), которая в основном делает то же самое. Я протестировал как функцию itertools, так и функцию TextBlob, и они, кажется, работают с сопоставимой скоростью и результатами (очень незначительное преимущество для функции itertools). - person Montmons; 24.03.2017
comment
К сожалению, я забыл включить подсчет энграмм в свое сравнение, функция TextBlob не делает этого сама по себе. Я пытался написать для нее функцию с помощью Counter, но в целом это делает ее намного медленнее. Итак... itertools побеждает. - person Montmons; 24.03.2017
comment
Это довольно умно. FWIW делает следующее: L1 — это words, L2 — это islice(words, 1, None), которое разбивает предложение на отдельные слова, начиная со второго слова. izip(words, islice(words, 1, None)) затем застегивает L1 с L2, так что из L1 сопоставляется quick из L2, quick из L1 сопоставляется с человеком из L2 и т. д. Затем счетчик считает пары. А для Python3 вам больше не нужно импортировать izip, просто используйте zip. Ответ ниже от @st0le на самом деле делает то же самое. - person Casey L; 02.03.2019

Как насчет zip()?

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

Вы можете просто использовать 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})
person Kristada673    schedule 16.01.2019
comment
это нормально, но отсутствует импорт - вам нужно добавить from nltk.util import ngrams. FWIW, кажется, работает немного быстрее, чем принятое решение. - person Casey L; 02.03.2019

С предстоящим Python 3.10 release schedule, новая функция 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'), ...]
person Xavier Guihot    schedule 08.12.2020

Прошло много времени с тех пор, как этот вопрос был задан и успешно ответил. Я извлекаю пользу из ответов, чтобы создать собственное решение. Я хотел бы поделиться им:

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

Это предоставит все биграммы, которые не прерываются пунктуацией.

person hurrial    schedule 28.11.2014

Можно использовать CountVectorizer из scikit-learn (pip install sklearn) для создания биграмм (или, в более общем смысле, любых энграмм).

Пример (протестировано с 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}
person Franck Dernoncourt    schedule 15.07.2021