Определение частоты слов определенных терминов - PullRequest
14 голосов
/ 25 ноября 2008

Я не студент, изучающий информатику, делаю исторический тезис, который включает определение частоты определенных терминов в ряде текстов, а затем нанесение на график этих частот во времени для определения изменений и тенденций. Хотя я выяснил, как определить частоту слов для данного текстового файла, я имею дело с (относительно для меня) большим количеством файлов (> 100) и для согласованности хотел бы ограничить количество слов, включенных в подсчет частоты к определенному набору терминов (вроде как «стоп-лист»)

Это должно быть очень простым. В конце все, что мне нужно, - это частоты для конкретных слов для каждого текстового файла, который я обрабатываю, предпочтительно в формате электронной таблицы (файл с разделителями табуляции), чтобы затем я мог создавать графики и визуализации с использованием этих данных.

Я использую Linux изо дня в день, мне удобно пользоваться командной строкой, и я хотел бы получить решение с открытым исходным кодом (или что-то, что я мог бы запустить с WINE). Однако это не является обязательным требованием:

Я вижу два пути решения этой проблемы:

  1. Найдите способ вырезать все слова в текстовом файле, КРОМЕ для предопределенного списка, а затем выполните отсчет частоты оттуда, или:
  2. Найдите способ подсчета частоты, используя только термины из предварительно определенного списка.

Есть идеи?

Ответы [ 7 ]

7 голосов
/ 25 ноября 2008

Я бы пошел со второй идеей. Вот простая программа на 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;
4 голосов
/ 25 ноября 2008

Я делаю такие вещи с помощью скрипта, подобного следующему (в синтаксисе 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 - это файл, содержащий ваши слова, по одному на строку.

2 голосов
/ 25 ноября 2008

Сначала ознакомьтесь с лексическим анализом и узнайте, как написать спецификацию генератора сканера. Прочтите введение в использование таких инструментов, как YACC, Lex, Bison или мой любимый JFlex. Здесь вы определяете, что представляет собой токен. Здесь вы узнаете, как создать токенизатор.

Далее у вас есть то, что называется начальным списком. Противоположность стоп-списку обычно называют стартовым списком или ограниченной лексикой. Лексикон также был бы полезен для изучения. Часть приложения должна загрузить стартовый список в память, чтобы его можно было быстро запросить. Типичный способ хранения - это файл с одним словом в строке, а затем один раз прочитать его в начале приложения в виде карты. Возможно, вы захотите узнать о концепции хеширования.

Отсюда вы хотите подумать об основном алгоритме и структурах данных, необходимых для хранения результата. Распределение легко представляется в виде двумерного разреженного массива. Изучите основы разреженной матрицы. Вам не нужно 6 месяцев линейной алгебры, чтобы понять, что она делает.

Поскольку вы работаете с большими файлами, я бы выступил за потоковый подход. Не читайте весь файл в память. Прочитайте его как поток в токенизатор, который создает поток токенов.

В следующей части алгоритма подумайте о том, как преобразовать список токенов в список, содержащий только те слова, которые вы хотите. Если подумать, список находится в памяти и может быть очень большим, поэтому лучше отфильтровать не начальные слова в начале. Поэтому в критической точке, когда вы получаете новый токен от токенайзера и, прежде чем добавить его в список токенов, выполните поиск в списке начальных слов в памяти, чтобы увидеть, является ли слово стартовым словом. Если это так, сохраните его в списке выходных токенов. В противном случае игнорируйте его и переходите к следующему токену, пока не будет прочитан весь файл.

Теперь у вас есть список токенов, представляющих интерес. Дело в том, что вы не смотрите на другие показатели индексации, такие как положение, регистр и контекст. Следовательно, вам действительно не нужен список всех токенов. Вы действительно просто хотите разреженную матрицу отдельных токенов с соответствующими счетами.

Итак, сначала создайте пустую разреженную матрицу. Затем подумайте о вставке обнаруженного токена во время синтаксического анализа. Когда это произойдет, увеличьте его счетчик, если он есть в списке, или вставьте новый токен со счетчиком 1. На этот раз в конце синтаксического анализа файла вы получите список различных токенов, каждый из которых имеет частоту, по крайней мере 1.

Этот список теперь в памяти, и вы можете делать все, что захотите. Выгрузка его в файл CSV будет тривиальным процессом итерации по записям и записи каждой записи в каждой строке с ее счетом.

В этом отношении обратите внимание на некоммерческий продукт под названием «GATE» или коммерческий продукт, такой как TextAnalyst, или продукты, перечисленные в http://textanalysis.info

1 голос
/ 11 апреля 2009

К черту большие сценарии. Если вы хотите захватить все слова, попробуйте эту оболочку 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 в конвейер (не проверено).

1 голос
/ 25 ноября 2008

Еще одна попытка 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) (но вы можете изменить разделитель в скрипте). Это должно быть читаемым для любого приложения электронной таблицы. Для построения графиков я бы порекомендовал gnuplot, который полностью подходит для сценариев, так что вы можете настроить выход независимо от входных данных.

1 голос
/ 25 ноября 2008

Я бы сделал "grep" для файлов, чтобы найти все строки, содержащие ваши ключевые слова. (Grep -f может использоваться для указания входного файла слов для поиска (передать вывод grep в файл). Это даст вам список строк, которые содержат экземпляры ваших слов. Затем выполните «sed» для замените разделители слов (скорее всего пробелы) на новые строки, чтобы получить файл отдельных слов (по одному слову в строке). Теперь снова запустите grep с тем же списком слов, за исключением того, что на этот раз укажите -c (чтобы получить счетчик) строк с указанными словами, т.е. количество вхождений слова в исходный файл).

Двухпроходный метод просто облегчает жизнь "седу"; первый grep должен удалить много строк.

Вы можете сделать все это с помощью основных команд командной строки linux. Как только вы освоитесь с этим процессом, вы можете довольно легко поместить все это в сценарий оболочки.

1 голос
/ 25 ноября 2008

Я предполагаю, что со временем появляются новые файлы, и вот как все меняется?

Я считаю, что вам лучше всего пойти на что-то похожее на ваш вариант 2. Предварительная обработка файлов не имеет особого смысла, если все, что вы хотите сделать, - это подсчитывать вхождения ключевых слов. Я бы просто просмотрел каждый файл один раз, считая каждый раз, когда слово в вашем списке появляется. Лично я бы сделал это в Ruby, но такой язык, как perl или python, также сделал бы эту задачу довольно простой. Например, вы можете использовать ассоциативный массив с ключевыми словами в качестве ключей и подсчетом вхождений в качестве значений. (Но это может быть слишком упрощенно, если вам нужно хранить больше информации о происшествиях).

Я не уверен, хотите ли вы хранить информацию для каждого файла или для всего набора данных? Полагаю, это было бы не сложно включить.

Я не уверен, что делать с данными, как только они у вас есть - было бы неплохо экспортировать их в электронную таблицу, если это даст вам то, что вам нужно. Или, в долгосрочной перспективе, вам может быть проще просто написать немного дополнительного кода, который хорошо отображает данные для вас. Зависит от того, что вы хотите сделать с данными (например, если вы хотите составить всего несколько диаграмм в конце упражнения и поместить их в отчет, тогда экспорт в CSV, вероятно, будет наиболее целесообразным, тогда как если вы хотите сгенерировать новый набор данных каждый день в течение года, а затем создание инструмента для его автоматического выполнения почти наверняка является лучшей идеей.

Редактировать: Я только что понял, что, поскольку вы изучаете историю, скорее всего, ваши документы не изменяются с течением времени, а скорее отражают ряд изменений, которые уже произошли. Извините за недопонимание этого. В любом случае, я думаю, что в значительной степени все, что я сказал выше, все еще применимо, но я думаю, что вы склонитесь к экспорту в CSV или что-то еще, а не к автоматическому отображению.

Звучит как забавный проект - удачи!

Ben

...