Ich möchte die Häufigkeit aller Wörter in einer Textdatei zählen.
>>> countInFile('test.txt')
sollte {'aaa':1, 'bbb': 2, 'ccc':1}
zurückgeben, wenn die Zieltextdatei folgendermaßen aussieht:
# test.txt
aaa bbb ccc
bbb
Ich habe es mit reinem Python nach einigen Beiträgen implementiert. Ich habe jedoch herausgefunden, dass reine Python-Methoden aufgrund der großen Dateigröße (> 1 GB) nicht ausreichen.
Ich denke, die Machtübernahme von sklearn ist ein Kandidat.
Wenn Sie CountVectorizer Frequenzen für jede Zeile zählen lassen, werden Sie wahrscheinlich Word-Frequenzen erhalten, indem Sie jede Spalte summieren. Aber es klingt ein bisschen indirekt.
Was ist der effizienteste und einfachste Weg, Wörter in einer Datei mit Python zu zählen?
Mein (sehr langsamer) Code ist hier:
from collections import Counter
def get_term_frequency_in_file(source_file_path):
wordcount = {}
with open(source_file_path) as f:
for line in f:
line = line.lower().translate(None, string.punctuation)
this_wordcount = Counter(line.split())
wordcount = add_merge_two_dict(wordcount, this_wordcount)
return wordcount
def add_merge_two_dict(x, y):
return { k: x.get(k, 0) + y.get(k, 0) for k in set(x) | set(y) }
Der prägnanteste Ansatz ist die Verwendung der Tools, die Python Ihnen zur Verfügung stellt.
from future_builtins import map # Only on Python 2
from collections import Counter
from itertools import chain
def countInFile(filename):
with open(filename) as f:
return Counter(chain.from_iterable(map(str.split, f)))
Das ist es. map(str.split, f)
erstellt einen Generator, der list
s von Wörtern aus jeder Zeile zurückgibt. Durch das Wrapping in chain.from_iterable
wird das in einen einzelnen Generator konvertiert, der jeweils ein Word erzeugt. Counter
nimmt eine Eingabe auf und zählt alle darin enthaltenen eindeutigen Werte. Am Ende return
ein dict
-ähnliches Objekt (eine Counter
), das alle eindeutigen Wörter und deren Anzahl speichert, und während der Erstellung nur eine Datenzeile und die Gesamtzahl und nicht die gesamte Datei gleichzeitig gespeichert werden.
Theoretisch könnten Sie unter Python 2.7 und 3.1 die verketteten Ergebnisse etwas besser durchlaufen und eine dict
oder collections.defaultdict(int)
zum Zählen verwenden (da Counter
in Python implementiert ist, was in manchen Fällen dazu führen kann, dass sie langsamer wird), aber Counter
Die Arbeit ist einfacher und selbstdokumentierender (ich meine, das ganze Ziel zählt, verwenden Sie also eine Counter
). Darüber hinaus verfügt Counter
auf CPython (dem Referenzinterpreter) ab Version 3.2 über einen Beschleuniger auf C-Ebene, um die iterierbaren Eingänge zu zählen, die schneller ausgeführt werden als alles, was Sie in reinem Python schreiben könnten.
Update: Sie scheinen die Interpunktion entfernt zu haben und die Groß- und Kleinschreibung zu berücksichtigen, daher ist hier eine Variante meines früheren Codes, der dies tut:
from string import punctuation
def countInFile(filename):
with open(filename) as f:
linewords = (line.translate(None, punctuation).lower().split() for line in f)
return Counter(chain.from_iterable(linewords))
Ihr Code läuft viel langsamer, da er viele kleine Counter
- und set
-Objekte erstellt und zerstört, anstatt .update
eine einzelne Counter
einmal pro Zeile zu erzeugen (was etwas langsamer ist als das, was ich im aktualisierten Codeblock gegeben habe, zumindest algorithmisch wäre.) ähnlich im Skalierungsfaktor).
Ein speichereffizienter und präziser Weg ist die Nutzung
scikit
(für die Ngram-Extraktion)Word_tokenize
numpy
Matrixsumme zum Erfassen der Zählungencollections.Counter
zum Erfassen der Zählungen und des WortschatzesEin Beispiel:
import urllib.request
from collections import Counter
import numpy as np
from nltk import Word_tokenize
from sklearn.feature_extraction.text import CountVectorizer
# Our sample textfile.
url = 'https://raw.githubusercontent.com/Simdiva/DSL-Task/master/data/DSLCC-v2.0/test/test.txt'
response = urllib.request.urlopen(url)
data = response.read().decode('utf8')
# Note that `ngram_range=(1, 1)` means we want to extract Unigrams, i.e. tokens.
ngram_vectorizer = CountVectorizer(analyzer='Word', tokenizer=Word_tokenize, ngram_range=(1, 1), min_df=1)
# X matrix where the row represents sentences and column is our one-hot vector for each token in our vocabulary
X = ngram_vectorizer.fit_transform(data.split('\n'))
# Vocabulary
vocab = list(ngram_vectorizer.get_feature_names())
# Column-wise sum of the X matrix.
# It's some crazy numpy syntax that looks horribly unpythonic
# For details, see http://stackoverflow.com/questions/3337301/numpy-matrix-to-array
# and http://stackoverflow.com/questions/13567345/how-to-calculate-the-sum-of-all-columns-of-a-2d-numpy-array-efficiently
counts = X.sum(axis=0).A1
freq_distribution = Counter(dict(Zip(vocab, counts)))
print (freq_distribution.most_common(10))
[aus]:
[(',', 32000),
('.', 17783),
('de', 11225),
('a', 7197),
('que', 5710),
('la', 4732),
('je', 4304),
('se', 4013),
('на', 3978),
('na', 3834)]
Im Wesentlichen können Sie dies auch tun:
from collections import Counter
import numpy as np
from nltk import Word_tokenize
from sklearn.feature_extraction.text import CountVectorizer
def freq_dist(data):
"""
:param data: A string with sentences separated by '\n'
:type data: str
"""
ngram_vectorizer = CountVectorizer(analyzer='Word', tokenizer=Word_tokenize, ngram_range=(1, 1), min_df=1)
X = ngram_vectorizer.fit_transform(data.split('\n'))
vocab = list(ngram_vectorizer.get_feature_names())
counts = X.sum(axis=0).A1
return Counter(dict(Zip(vocab, counts)))
Lassen Sie uns timeit
:
import time
start = time.time()
Word_distribution = freq_dist(data)
print (time.time() - start)
[aus]:
5.257147789001465
Beachten Sie, dass CountVectorizer
anstelle eines Strings auch eine Datei verwenden kann und thier nicht die gesamte Datei in den Speicher lesen muss. In Code:
import io
from collections import Counter
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
infile = '/path/to/input.txt'
ngram_vectorizer = CountVectorizer(analyzer='Word', ngram_range=(1, 1), min_df=1)
with io.open(infile, 'r', encoding='utf8') as fin:
X = ngram_vectorizer.fit_transform(fin)
vocab = ngram_vectorizer.get_feature_names()
counts = X.sum(axis=0).A1
freq_distribution = Counter(dict(Zip(vocab, counts)))
print (freq_distribution.most_common(10))
Hier ist ein Benchmark. Es wird seltsam aussehen, aber der gröbste Code gewinnt.
[Code]:
from collections import Counter, defaultdict
import io, time
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
infile = '/path/to/file'
def extract_dictionary_sklearn(file_path):
with io.open(file_path, 'r', encoding='utf8') as fin:
ngram_vectorizer = CountVectorizer(analyzer='Word')
X = ngram_vectorizer.fit_transform(fin)
vocab = ngram_vectorizer.get_feature_names()
counts = X.sum(axis=0).A1
return Counter(dict(Zip(vocab, counts)))
def extract_dictionary_native(file_path):
dictionary = Counter()
with io.open(file_path, 'r', encoding='utf8') as fin:
for line in fin:
dictionary.update(line.split())
return dictionary
def extract_dictionary_Paddle(file_path):
dictionary = defaultdict(int)
with io.open(file_path, 'r', encoding='utf8') as fin:
for line in fin:
for words in line.split():
dictionary[Word] +=1
return dictionary
start = time.time()
extract_dictionary_sklearn(infile)
print time.time() - start
start = time.time()
extract_dictionary_native(infile)
print time.time() - start
start = time.time()
extract_dictionary_Paddle(infile)
print time.time() - start
[aus]:
38.306814909
24.8241138458
12.1182529926
Datengröße (154 MB), die im obigen Benchmark verwendet wird:
$ wc -c /path/to/file
161680851
$ wc -l /path/to/file
2176141
Einige Dinge zu beachten:
sklearn
ist die Erstellung von Vectorizer + die Bearbeitung und die Umwandlung in ein Counter
-Objekt mit einem Mehraufwand verbundenCounter
-Updateversion scheint Counter.update()
eine teure Operation zu seinDas sollte ausreichen.
def countinfile(filename):
d = {}
with open(filename, "r") as fin:
for line in fin:
words = line.strip().split()
for Word in words:
try:
d[Word] += 1
except KeyError:
d[Word] = 1
return d
du kannst es mit sklearn versuchen
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
data=['i am student','the student suffers a lot']
transformed_data =vectorizer.fit_transform(data)
vocab= {a: b for a, b in Zip(vectorizer.get_feature_names(), np.ravel(transformed_data.sum(axis=0)))}
print (vocab)
Anstatt die gesamten aus der URL gelesenen Bytes zu decodieren, verarbeite ich die binären Daten. Da bytes.translate
erwartet, dass sein zweites Argument eine Byte-Zeichenfolge ist, codiert utf-8 punctuation
. Nach dem Entfernen von Satzzeichen decodiere ich den Byte-String.
Die Funktion freq_dist
erwartet eine Iteration. Deshalb habe ich data.splitlines()
bestanden.
from urllib2 import urlopen
from collections import Counter
from string import punctuation
from time import time
import sys
from pprint import pprint
url = 'https://raw.githubusercontent.com/Simdiva/DSL-Task/master/data/DSLCC-v2.0/test/test.txt'
data = urlopen(url).read()
def freq_dist(data):
"""
:param data: file-like object opened in binary mode or
sequence of byte strings separated by '\n'
:type data: an iterable sequence
"""
#For readability
#return Counter(Word for line in data
# for Word in line.translate(
# None,bytes(punctuation.encode('utf-8'))).decode('utf-8').split())
punc = punctuation.encode('utf-8')
words = (Word for line in data for Word in line.translate(None, punc).decode('utf-8').split())
return Counter(words)
start = time()
Word_dist = freq_dist(data.splitlines())
print('elapsed: {}'.format(time() - start))
pprint(Word_dist.most_common(10))
Ausgabe;
elapsed: 0.806480884552
[(u'de', 11106),
(u'a', 6742),
(u'que', 5701),
(u'la', 4319),
(u'je', 4260),
(u'se', 3938),
(u'\u043d\u0430', 3929),
(u'na', 3623),
(u'da', 3534),
(u'i', 3487)]
Es scheint, dass dict
effizienter ist als Counter
-Objekt.
def freq_dist(data):
"""
:param data: A string with sentences separated by '\n'
:type data: str
"""
d = {}
punc = punctuation.encode('utf-8')
words = (Word for line in data for Word in line.translate(None, punc).decode('utf-8').split())
for Word in words:
d[Word] = d.get(Word, 0) + 1
return d
start = time()
Word_dist = freq_dist(data.splitlines())
print('elapsed: {}'.format(time() - start))
pprint(sorted(Word_dist.items(), key=lambda x: (x[1], x[0]), reverse=True)[:10])
Ausgabe;
elapsed: 0.642680168152
[(u'de', 11106),
(u'a', 6742),
(u'que', 5701),
(u'la', 4319),
(u'je', 4260),
(u'se', 3938),
(u'\u043d\u0430', 3929),
(u'na', 3623),
(u'da', 3534),
(u'i', 3487)]
Um beim Öffnen einer großen Datei mehr Speichereffizienz zu erzielen, müssen Sie nur die geöffnete URL übergeben. Das Timing beinhaltet jedoch auch die Downloadzeit für Dateien.
data = urlopen(url)
Word_dist = freq_dist(data)
Überspringen Sie CountVectorizer und scikit-learn.
Die Datei ist möglicherweise zu groß, um sie in den Arbeitsspeicher zu laden, aber ich bezweifle, dass das Python-Wörterbuch zu groß wird. Die einfachste Option für Sie ist, die große Datei in 10-20 kleinere Dateien aufzuteilen und Ihren Code so zu erweitern, dass die kleineren Dateien überlaufen werden.