Определение частоты слов конкретных терминов
Я - не-компьютерный научный студент, занимающийся диссертацией по истории, который включает определение частоты конкретных терминов в ряде текстов, а затем построение этих частот с течением времени для определения изменений и тенденций. Хотя я выяснил, как определить частоты слов для данного текстового файла, я имею дело с (относительно, для меня) большим количеством файлов ( > 100), и для согласований просто хотел бы ограничить слова, включенные в частоту к определенному набору терминов (вроде как противоположность "стоп-листа" )
Это должно быть очень простым. В конце все, что мне нужно - это частоты для конкретных слов для каждого обрабатываемого текстового файла, желательно в формате электронных таблиц (с разделителем табуляции), чтобы затем я мог создавать графики и визуализации с использованием этих данных.
Я использую Linux изо дня в день, мне удобнее использовать командную строку, и мне бы понравилось решение с открытым исходным кодом (или что-то, что я мог бы запустить с WINE). Однако это не требование:
Я вижу два способа решить эту проблему:
- Найдите способ вычеркнуть все слова в текстовом файле EXCEPT для предварительно определенного списка, а затем выполните подсчет частоты оттуда или:
- Найдите способ делать счетчик частоты, используя только термины из предварительно определенного списка.
Любые идеи?
Ответы
Ответ 1
Я бы пошел со второй мыслью. Вот простая программа Perl, которая будет читать список слов из первого предоставленного файла и печатать количество каждого слова в списке из второго файла, представленного в формате, разделенном в виде вкладок. Список слов в первом файле должен быть указан по одному в строке.
#!/usr/bin/perl
use strict;
use warnings;
my $word_list_file = shift;
my $process_file = shift;
my %word_counts;
# Open the word list file, read a line at a time, remove the newline,
# add it to the hash of words to track, initialize the count to zero
open(WORDS, $word_list_file) or die "Failed to open list file: $!\n";
while (<WORDS>) {
chomp;
# Store words in lowercase for case-insensitive match
$word_counts{lc($_)} = 0;
}
close(WORDS);
# Read the text file one line at a time, break the text up into words
# based on word boundaries (\b), iterate through each word incrementing
# the word count in the word hash if the word is in the hash
open(FILE, $process_file) or die "Failed to open process file: $!\n";
while (<FILE>) {
chomp;
while ( /-$/ ) {
# If the line ends in a hyphen, remove the hyphen and
# continue reading lines until we find one that doesn't
chop;
my $next_line = <FILE>;
defined($next_line) ? $_ .= $next_line : last;
}
my @words = split /\b/, lc; # Split the lower-cased version of the string
foreach my $word (@words) {
$word_counts{$word}++ if exists $word_counts{$word};
}
}
close(FILE);
# Print each word in the hash in alphabetical order along with the
# number of time encountered, delimited by tabs (\t)
foreach my $word (sort keys %word_counts)
{
print "$word\t$word_counts{$word}\n"
}
Если файл words.txt содержит:
linux
frequencies
science
words
И файл text.txt содержит текст вашего сообщения, следующую команду:
perl analyze.pl words.txt text.txt
напечатает:
frequencies 3
linux 1
science 1
words 3
Обратите внимание, что нарушение границ слов с помощью \b может не работать так, как вы хотите во всех случаях, например, если ваши текстовые файлы содержат слова, которые переносятся через строки, вам нужно сделать что-то более интеллектуальное, чтобы соответствовать этим, В этом случае вы можете проверить, является ли последний символ в строке дефисом, и если это так, просто удалите дефис и прочитайте еще одну строку, прежде чем разделить строку на слова.
Изменить: обновленная версия, которая обрабатывает слова без учета регистра и обрабатывает переносимые слова через строки.
Обратите внимание: если есть слова с переносом, некоторые из которых разбиты по строкам, а некоторые - нет, это не будет их искать, потому что в конце строки удаляются только дефисы. В этом случае вы можете просто удалить все дефисы и слова соответствия после удаления дефис. Вы можете сделать это, просто добавив следующую строку прямо перед функцией split:
s/-//g;
Ответ 2
Я делаю такие вещи с script следующим образом (в синтаксисе bash):
for file in *.txt
do
sed -r 's/([^ ]+) +/\1\n/g' "$file" \
| grep -F -f 'go-words' \
| sort | uniq -c > "${file}.frq"
done
Вы можете настроить регулярное выражение, которое вы используете, чтобы разграничить отдельные слова; в примере я просто рассматриваю пробелы как разделитель. Аргумент -f для grep - это файл, содержащий ваши интересные слова, по одному на строку.
Ответ 3
Сначала ознакомьтесь с лексическим анализом и как написать спецификацию генератора сканера. Прочтите введение в использование таких инструментов, как YACC, Lex, Bison или мой личный фаворит JFlex. Здесь вы определяете, что представляет собой токен. Здесь вы узнаете о том, как создать токенизатор.
Затем у вас есть так называемый список семян. Противоположность списку остановок обычно называется стартовым или ограниченным лексиконом. Лексикон тоже будет хорошей идеей. Часть приложения должна загружать стартовый список в память, чтобы его можно было быстро запросить. Типичным способом хранения является файл с одним словом на строку, а затем прочитайте его в начале приложения, когда-то, в виде карты. Возможно, вам захочется узнать о концепции хеширования.
Здесь вы хотите подумать об основном алгоритме и структурах данных, необходимых для хранения результата. Распределение легко представляется в виде двумерного разреженного массива. Изучите основы разреженной матрицы. Вам не нужно 6 месяцев линейной алгебры, чтобы понять, что она делает.
Поскольку вы работаете с большими файлами, я бы защищал поточный подход. Не читайте весь файл в памяти. Прочитайте его как поток в токенизатор, который создает поток токенов.
В следующей части алгоритма подумайте о том, как преобразовать список токенов в список, содержащий только слова, которые вы хотите. Если вы думаете об этом, список будет в памяти и может быть очень большим, поэтому лучше отфильтровывать без начала слова в начале. Итак, в критический момент, когда вы получаете новый токен из токенизатора и перед добавлением его в список токенов, выполните поиск в списке начальных слов в памяти, чтобы узнать, является ли слово стартовым. Если да, сохраните его в списке маркеров вывода. В противном случае игнорируйте его и перейдите к следующему токену, пока не будет прочитан весь файл.
Теперь у вас есть список жетонов, представляющих интерес. Дело в том, что вы не смотрите на другие показатели индексирования, такие как позиция и случай и контекст. Поэтому вам действительно не нужен список всех токенов. Вы действительно просто хотите разреженную матрицу из разных токенов с соответствующими подсчетами.
Итак, сначала создайте пустую разреженную матрицу. Затем подумайте о вставке вновь найденного токена во время разбора. Когда это произойдет, увеличьте его количество, если оно в списке, или иначе вставьте новый токен со счетом 1. На этот раз, в конце разбора файла, у вас есть список различных токенов, каждый с частотой не менее 1.
Этот список теперь находится в-mem, и вы можете делать все, что хотите. Сбрасывание его в CSV файл было бы тривиальным процессом повторения записей и записи каждой записи в строке с ее счетчиком.
В этом случае взгляните на некоммерческий продукт под названием "GATE" или коммерческий продукт, такой как TextAnalyst или продукты, перечисленные в http://textanalysis.info
Ответ 4
Я предполагаю, что со временем появятся новые файлы и что изменится ситуация?
Я считаю, что лучше всего будет идти с чем-то вроде вашего варианта 2. Там не так много предварительных обработок файлов, если все, что вы хотите сделать, это подсчет вхождения ключевых слов. Я просто просматриваю каждый файл один раз, каждый раз подсчитывая слово в вашем списке. Лично я делаю это в Ruby, но такой язык, как perl или python, также сделает эту задачу довольно простой. Например, вы можете использовать ассоциативный массив с ключевыми словами в качестве ключей и количество вхождений в качестве значений. (Но это может быть слишком упрощенным, если вам нужно хранить больше информации о событиях).
Я не уверен, хотите ли вы хранить информацию в файле или всего набора данных? Думаю, это не было бы слишком сложно включить.
Я не уверен, что делать с данными, как только вы их получите, - экспортировать его в электронную таблицу будет хорошо, если это даст вам то, что вам нужно. Или вам может быть проще в долгосрочной перспективе просто написать немного дополнительного кода, который хорошо отображает данные для вас. Зависит от того, что вы хотите делать с данными (например, если вы хотите создать только несколько диаграмм в конце упражнения и поместить их в отчет, тогда экспорт в CSV, вероятно, будет иметь наибольший смысл, тогда как если вы хотите сгенерировать новый набор данных каждый день в течение года, тогда создание инструмента для этого автоматически - это, безусловно, лучшая идея.
Редактирование: я просто понял, что, поскольку вы изучаете историю, шансы на то, что ваши документы не меняются со временем, а скорее отражают набор изменений, которые уже произошли. Извините за недоразумение. Во всяком случае, я думаю, что все, что я сказал выше, все еще применяется, но, я думаю, вы будете склоняться к тому, чтобы экспортировать в CSV или что у вас есть, а не на автоматическое отображение.
Звучит как забавный проект - удачи!
Бен
Ответ 5
Я бы сделал "grep" в файлах, чтобы найти все строки, содержащие ваши ключевые слова. (Grep -f может использоваться для указания входного файла слов для поиска (вывести вывод grep в файл). Это даст вам список строк, содержащих экземпляры ваших слов. Затем выполните команду sed замените разделители слов (скорее всего, пробелы) на новые строки, чтобы дать вам файл с отдельными словами (по одному слову на строку). Теперь снова запустите grep с тем же списком слов, кроме этого времени укажите -c (чтобы получить счет строк с указанными словами, т.е. подсчет вхождений слова в исходный файл).
Двухпроходный метод просто упрощает жизнь для "sed"; первый grep должен устранить много строк.
Вы можете сделать все это в основных командах командной строки linux. После того, как вы будете довольны процессом, вы можете легко разместить все это в оболочке script.
Ответ 6
Другая попытка Perl:
#!/usr/bin/perl -w
use strict;
use File::Slurp;
use Tie::File;
# Usage:
#
# $ perl WordCount.pl <Files>
#
# Example:
#
# $ perl WordCount.pl *.text
#
# Counts words in all files given as arguments.
# The words are taken from the file "WordList".
# The output is appended to the file "WordCount.out" in the format implied in the
# following example:
#
# File,Word1,Word2,Word3,...
# File1,0,5,3,...
# File2,6,3,4,...
# .
# .
# .
#
### Configuration
my $CaseSensitive = 1; # 0 or 1
my $OutputSeparator = ","; # another option might be "\t" (TAB)
my $RemoveHyphenation = 0; # 0 or 1. Careful, may be too greedy.
###
my @WordList = read_file("WordList");
chomp @WordList;
tie (my @Output, 'Tie::File', "WordCount.out");
push (@Output, join ($OutputSeparator, "File", @WordList));
for my $InFile (@ARGV)
{ my $Text = read_file($InFile);
if ($RemoveHyphenation) { $Text =~ s/-\n//g; };
my %Count;
for my $Word (@WordList)
{ if ($CaseSensitive)
{ $Count{$Word} = ($Text =~ s/(\b$Word\b)/$1/g); }
else
{ $Count{$Word} = ($Text =~ s/(\b$Word\b)/$1/gi); }; };
my $OutputLine = "$InFile";
for my $Word (@WordList)
{ if ($Count{$Word})
{ $OutputLine .= $OutputSeparator . $Count{$Word}; }
else
{ $OutputLine .= $OutputSeparator . "0"; }; };
push (@Output, $OutputLine); };
untie @Output;
Когда я помещаю ваш вопрос в файл wc-test
, а Роберт Гэмбл отвечает на wc-ans-test
, выходной файл выглядит следующим образом:
File,linux,frequencies,science,words
wc-ans-test,2,2,2,12
wc-test,1,3,1,3
Это файл с разделителями-запятыми (csv) (но вы можете изменить разделитель в script). Он должен читаться для любого приложения электронной таблицы. Для построения графиков я бы рекомендовал gnuplot
, который полностью доступен для сценариев, поэтому вы можете настроить свой выход независимо от входных данных.
Ответ 7
К черту большие скрипты. Если вы хотите захватить все слова, попробуйте эту оболочку fu:
cat *.txt | tr A-Z a-z | tr -cs a-z '\n' | sort | uniq -c | sort -rn |
sed '/[0-9] /&, /'
Это (проверено) даст вам список всех слов, отсортированных по частоте в формате CSV, легко импортируемых вашей любимой электронной таблицей. Если у вас должны быть слова остановки, попробуйте вставить grep -w -F -f stopwords.txt
в конвейер (не протестировано).