Преброяване на биграми (двойка от две думи) във файл с помощта на 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

Попаднах на пример за обекти Counter в Python, който се използва за броене на униграми (единични думи). Той също така използва regex подход.

Примерът е следният:

>>> # 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 object или regex, също ще бъде оценен.


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
Ами сега, забравих да включа преброяването на ngrams в моето сравнение, функцията 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) за генериране на биграми (или по-общо всяка 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}
person Franck Dernoncourt    schedule 15.07.2021