37

テキスト ファイル内のすべての単語の頻度をカウントしたいと思います。

>>> countInFile('test.txt')

{'aaa':1, 'bbb': 2, 'ccc':1}ターゲット テキスト ファイルが次のような場合に返されます。

# test.txt
aaa bbb ccc
bbb

いくつかの投稿に従って、純粋なpythonで実装しました。ただし、ファイルサイズが非常に大きいため(> 1GB)、純粋なpythonの方法では不十分であることがわかりました。

sklearnの力を借りるのも候補だと思います。

CountVectorizer に各行の頻度をカウントさせると、各列を合計することで単語の頻度が得られると思います。しかし、それは少し間接的な方法に聞こえます。

Pythonでファイル内の単語を数える最も効率的で簡単な方法は何ですか?

アップデート

私の(非常に遅い)コードは次のとおりです。

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) }
4

8 に答える 8

49

最も簡潔なアプローチは、Python が提供するツールを使用することです。

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

それでおしまい。各行から s 個の単語map(str.split, f)を返すジェネレータを作成しています。listラップするchain.from_iterableと、一度に単語を生成する単一のジェネレーターに変換されます。Counter入力イテラブルを取り、その中のすべての一意の値をカウントします。最後に、すべての一意の単語とそのカウントを格納returnする の dictようなオブジェクト (a Counter) を作成します。作成中は、一度にファイル全体ではなく、一度に 1 行のデータと合計カウントのみを格納します。

dict理論的には、Python 2.7 および 3.1 では、チェーンされた結果を自分でループし、 orcollections.defaultdict(int)を使用してカウントする方がわずかに良いかもしれません( CounterPython で実装されているため、場合によっては遅くなる可能性があるため)、Counter作業を任せる方が簡単です。より自己文書化します(つまり、全体の目標はカウントであるため、 a を使用しますCounter)。さらに、CPython (リファレンス インタープリター) 3.2 以降Counterには、反復可能な入力をカウントするための C レベルのアクセラレータがあり、純粋な Python で記述できるものよりも高速に実行されます。

更新:句読点を取り除き、大文字と小文字を区別しないようにしたいようです。それを行う以前のコードの変形を次に示します。

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

あなたのコードは、行ごとに 1つのオブジェクトを作成および破棄するのではなく、多くの小さなオブジェクトCounterを作成および破棄するため、はるかにゆっくりと実行されます (これは、更新されたコード ブロックで指定したものよりもわずかに遅くなりますが、少なくともアルゴリズム的にはスケーリング係数が似ています)。 )。set.updateCounter

于 2016-03-08T02:30:21.447 に答える
15

メモリ効率が高く正確な方法は、

  • CountVectorizer in scikit(ngram 抽出用)
  • NLTKword_tokenize
  • numpyカウントを収集する行列合計
  • collections.Counterカウントと語彙を収集するため

例:

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

[アウト]:

[(',', 32000),
 ('.', 17783),
 ('de', 11225),
 ('a', 7197),
 ('que', 5710),
 ('la', 4732),
 ('je', 4304),
 ('se', 4013),
 ('на', 3978),
 ('na', 3834)]

基本的に、これを行うこともできます:

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

しましょうtimeit

import time

start = time.time()
word_distribution = freq_dist(data)
print (time.time() - start)

[アウト]:

5.257147789001465

CountVectorizer文字列の代わりにファイルを受け取ることもでき、ファイル全体をメモリに読み込む必要がないことに注意してください。コード内:

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))
于 2016-03-10T12:53:51.623 に答える
3

これで十分です。

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
于 2016-03-08T02:14:34.043 に答える
2

URL から読み取ったバイト全体をデコードする代わりに、バイナリ データを処理します。bytes.translate2 番目の引数がバイト文字列であることを想定しているため、utf-8 でエンコードしpunctuationます。句読点を削除した後、バイト文字列を utf-8 デコードします。

関数freq_distは iterable を期待しています。それが私が合格した理由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))

出力;

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

オブジェクト dictよりも効率的です。Counter

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

出力;

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

巨大なファイルを開くときのメモリ効率を高めるには、開いた URL だけを渡す必要があります。ただし、タイミングにはファイルのダウンロード時間も含まれます。

data = urlopen(url)
word_dist = freq_dist(data)
于 2016-03-11T15:45:00.993 に答える
0

CountVectorizer と scikit-learn をスキップします。

ファイルが大きすぎてメモリに読み込めない可能性がありますが、Python 辞書が大きくなりすぎるとは思えません。最も簡単なオプションは、大きなファイルを 10 ~ 20 個の小さなファイルに分割し、小さなファイルをループするようにコードを拡張することです。

于 2016-03-08T02:10:43.670 に答える