una - Cuenta eficientemente las frecuencias de palabras en python
frecuencia de letras en una palabra python (7)
Aquí hay algunos puntos de referencia. Se verá extraño pero el código más crudo gana.
[código]:
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
[fuera]:
38.306814909
24.8241138458
12.1182529926
Tamaño de datos (154 MB) utilizado en el punto de referencia anterior:
$ wc -c /path/to/file
161680851
$ wc -l /path/to/file
2176141
Algunas cosas a tener en cuenta:
-
Con la versión
sklearn
, hay una sobrecarga de creación de vectorizadores + manipulación y conversión numpy en un objetoCounter
-
Entonces, la versión de actualización nativa de
Counter
, parece queCounter.update()
es una operación costosa
Me gustaría contar las frecuencias de todas las palabras en un archivo de texto.
>>> countInFile(''test.txt'')
debería devolver
{''aaa'':1, ''bbb'': 2, ''ccc'':1}
si el archivo de texto de destino es como:
# test.txt
aaa bbb ccc
bbb
Lo he implementado con Python puro después de algunas publicaciones . Sin embargo, descubrí que las formas de Python puro son insuficientes debido al gran tamaño de archivo (> 1 GB).
Creo que tomar prestado el poder de Sklearn es un candidato.
Si deja que CountVectorizer cuente las frecuencias para cada línea, supongo que obtendrá frecuencias de palabras al resumir cada columna. Pero, suena un poco indirecto.
¿Cuál es la forma más eficiente y directa de contar palabras en un archivo con python?
Actualizar
Mi código (muy lento) está aquí:
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) }
El enfoque más sucinto es utilizar las herramientas que Python le brinda.
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)))
Eso es.
map(str.split, f)
está haciendo un generador que devuelve una
list
de palabras de cada línea.
Al envolver en
chain.from_iterable
eso se convierte en un solo generador que produce una palabra a la vez.
Counter
toma una entrada iterable y cuenta todos los valores únicos en ella.
Al final,
return
un objeto tipo
dict
(un
Counter
) que almacena todas las palabras únicas y sus recuentos, y durante la creación, solo almacena una línea de datos a la vez y los recuentos totales, no todo el archivo a la vez.
En teoría, en Python 2.7 y 3.1, puede hacer un mejor bucle sobre los resultados encadenados usted mismo y usar un
dict
o
collections.defaultdict(int)
para contar (porque
Counter
está implementado en Python, lo que puede hacerlo más lento en algunos casos) , pero dejar que
Counter
haga el trabajo es más simple y más autodocumentado (quiero decir, todo el objetivo es contar, así que use un
Counter
).
Más allá de eso, en CPython (el intérprete de referencia) 3.2 y superior
Counter
tiene un acelerador de nivel C para contar entradas iterables que se ejecutarán más rápido que cualquier cosa que pueda escribir en Python puro.
Actualización: Parece que quiere eliminar la puntuación y la insensibilidad a mayúsculas y minúsculas, así que aquí hay una variante de mi código anterior que hace eso:
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))
Su código se ejecuta mucho más lentamente porque está creando y destruyendo muchos
Counter
pequeños y
set
objetos, en lugar de
.update
,
.update
un solo
Counter
una vez por línea (que, aunque un poco más lento de lo que di en el bloque de código actualizado, sería al menos algorítmicamente similar en factor de escala).
En lugar de decodificar los bytes completos leídos de la URL, proceso los datos binarios.
Debido a que
bytes.translate
espera que su segundo argumento sea una cadena de bytes, utf-8 codifica la
punctuation
.
Después de eliminar los signos de puntuación, utf-8 decodifica la cadena de bytes.
La función
freq_dist
espera un iterable.
Es por eso que pasé
data.splitlines()
.
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))
Salida;
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)]
Parece que
dict
es más eficiente que
Counter
object.
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])
Salida;
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)]
Para ser más eficiente en la memoria al abrir archivos grandes, debe pasar solo la url abierta. Pero el momento también incluirá el tiempo de descarga de archivos.
data = urlopen(url)
word_dist = freq_dist(data)
Esto debería ser suficiente.
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
Omita CountVectorizer y scikit-learn.
El archivo puede ser demasiado grande para cargarlo en la memoria, pero dudo que el diccionario de Python sea demasiado grande. La opción más fácil para usted puede ser dividir el archivo grande en 10-20 archivos más pequeños y extender su código para recorrer los archivos más pequeños.
Una forma eficiente y precisa de memoria es hacer uso de
-
CountVectorizer en
scikit
(para extracción de ngram) -
word_tokenize
paraword_tokenize
-
suma de matriz
numpy
para recoger los recuentos -
collections.Counter
para recopilar los recuentos y el vocabulario
Un ejemplo:
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://.com/questions/3337301/numpy-matrix-to-array
# and http://.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))
[fuera]:
[('','', 32000),
(''.'', 17783),
(''de'', 11225),
(''a'', 7197),
(''que'', 5710),
(''la'', 4732),
(''je'', 4304),
(''se'', 4013),
(''на'', 3978),
(''na'', 3834)]
Esencialmente, también puedes hacer esto:
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)))
Vamos a
timeit
:
import time
start = time.time()
word_distribution = freq_dist(data)
print (time.time() - start)
[fuera]:
5.257147789001465
Tenga en cuenta que
CountVectorizer
también puede tomar un archivo en lugar de una cadena y
no es necesario leer todo el archivo en la memoria
.
En codigo:
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))
puedes probar con sklearn
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)