14

我是一名非计算机科学专业的学生,​​正在撰写一篇历史论文,该论文涉及确定许多文本中特定术语的频率,然后随着时间的推移绘制这些频率以确定变化和趋势。虽然我已经弄清楚如何确定给定文本文件的词频,但我正在处理(相对而言,对我而言)大量文件(> 100),并且为了保持一致性,我想限制频率计数中包含的词到一组特定的术语(有点像“停止列表”的反面)

这应该保持非常简单。最后,我需要的是我处理的每个文本文件的特定单词的频率,最好是电子表格格式(制表符描述的文件),这样我就可以使用该数据创建图表和可视化。

我每天都在使用 Linux,对使用命令行很舒服,并且喜欢开源解决方案(或者我可以用 WINE 运行的东西)。然而,这不是一个要求:

我看到了两种解决这个问题的方法:

  1. 找到一种方法去除文本文件中除预定义列表之外的所有单词,然后从那里进行频率计数,或者:
  2. 找到一种仅使用预定义列表中的术语进行频率计数的方法。

有任何想法吗?

4

7 回答 7

7

我会选择第二个想法。这是一个简单的 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;
于 2008-11-24T22:17:26.860 回答
4

我使用如下脚本(在 bash 语法中)执行此类操作:

for file in *.txt
do 
  sed -r 's/([^ ]+) +/\1\n/g' "$file" \
  | grep -F -f 'go-words' \
  | sort | uniq -c > "${file}.frq"
done

您可以调整用于分隔单个单词的正则表达式;在示例中,我只是将空格视为分隔符。grep 的 -f 参数是一个包含您感兴趣的单词的文件,每行一个。

于 2008-11-24T22:51:11.923 回答
2

首先熟悉词法分析以及如何编写扫描器生成器规范。阅读有关使用 YACC、Lex、Bison 或我个人最喜欢的 JFlex 等工具的介绍。在这里,您定义什么构成令牌。这是您了解如何创建标记器的地方。

接下来你有所谓的种子列表。停止列表的反面通常被称为开始列表或有限词典。Lexicon 也是一个值得学习的好东西。部分应用需要将开始列表加载到内存中,以便快速查询。典型的存储方式是每行一个单词的文件,然后在应用程序开始时将其读入一次,读入类似于地图的内容。您可能想了解散列的概念。

从这里您想考虑存储结果所需的基本算法和数据结构。分布很容易表示为二维稀疏数组。学习稀疏矩阵的基础知识。你不需要 6 个月的线性代数来理解它的作用。

因为您正在处理更大的文件,所以我会提倡基于流的方法。不要将整个文件读入内存。将其作为流读入生成标记流的标记器。

在算法的下一部分考虑如何将标记列表转换为仅包含您想要的单词的列表。如果您考虑一下,列表在内存中并且可能非常大,因此最好在开始时过滤掉非起始词。因此,在您从分词器中获取新令牌的关键点,在将其添加到令牌列表之前,请在内存中的起始词列表中进行查找以查看该词是否为起始词。如果是这样,请将其保留在输出令牌列表中。否则忽略它并移至下一个标记,直到读取整个文件。

现在您有一个仅感兴趣的令牌列表。问题是,您没有查看其他索引指标,例如位置、案例和上下文。因此,您真的不需要所有令牌的列表。你真的只想要一个具有相关计数的不同标记的稀疏矩阵。

所以,首先创建一个空的稀疏矩阵。然后想想解析时新发现的token的插入。当它发生时,如果它在列表中,则增加它的计数,或者插入一个计数为 1 的新标记。这次,在解析文件结束时,您有一个不同标记的列表,每个标记的频率至少为1.

该列表现在在内存中,您可以做任何您想做的事情。将其转储到 CSV 文件将是一个简单的过程,即迭代条目并在每行写入每个条目及其计数。

为此,请查看名为“GATE”的非商业产品或 TextAnalyst 等商业产品或http://textanalysis.info中列出的产品

于 2008-11-24T22:26:59.977 回答
1

我猜随着时间的推移会引入新文件,这就是事情的变化?

我认为您最好的选择是使用类似于您的选项 2 的方法。如果您只想计算关键字的出现次数,那么预处理文件没有多大意义。我只会浏览每个文件一次,计算列表中出现的每个单词。就我个人而言,我会用 Ruby 来完成,但是像 perl 或 python 这样的语言也会使这项任务变得非常简单。例如,您可以使用关联数组,其中关键字作为键,出现次数作为值。(但如果您需要存储有关事件的更多信息,这可能过于简单)。

我不确定您是要存储每个文件的信息,还是要存储整个数据集的信息?我想这不会太难合并。

我不确定一旦获得数据后如何处理数据 - 将其导出到电子表格会很好,如果这可以满足您的需求。或者,从长远来看,您可能会发现编写一些额外的代码来为您很好地显示数据会更容易。取决于你想对数据做什么(例如,如果你想在练习结束时只生成几个图表并将它们放入报告中,那么导出到 CSV 可能最有意义,而如果你想生成一年中每天都有一组新数据,然后构建一个自动执行此操作的工具几乎可以肯定是最好的主意。

编辑:我刚刚发现,由于您正在研究历史,因此您的文档可能不会随着时间而改变,而是反映了一系列已经发生的变化。很抱歉造成误解。无论如何,我认为我上面所说的几乎所有内容仍然适用,但我想你会倾向于导出到 CSV 或你有什么而不​​是自动显示。

听起来像是一个有趣的项目——祝你好运!

于 2008-11-24T22:08:43.710 回答
1

我会在文件上做一个“grep”来找到所有包含你的关键词的行。(Grep -f 可用于指定要搜索的单词输入文件(将 grep 的输出通过管道传输到文件)。这将为您提供包含单词实例的行列表。然后执行“sed”用换行符替换你的单词分隔符(很可能是空格),给你一个单独的单词文件(每行一个单词)。现在再次运行 grep,使用相同的单词列表,除了这次指定 -c (获取计数具有指定单词的行数;即原始文件中单词出现的计数)。

两遍方法只是让“sed”的生活更轻松;第一个 grep 应该消除很多行。

您可以在基本的 linux 命令行命令中完成这一切。一旦你对这个过程感到满意,你就可以很容易地将它全部放入 shell 脚本中。

于 2008-11-24T22:37:21.030 回答
1

另一个 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并将 Robert Gamble 的答案放入wc-ans-test时,输出文件如下所示:

File,linux,frequencies,science,words
wc-ans-test,2,2,2,12
wc-test,1,3,1,3

这是一个逗号分隔值 (csv) 文件(但您可以在脚本中更改分隔符)。它应该对任何电子表格应用程序都是可读的。对于绘图,我建议使用gnuplot完全可编写脚本的 ,因此您可以独立于输入数据调整输出。

于 2008-11-25T00:44:20.213 回答
1

大脚本见鬼去吧。如果你愿意抓住所有的话,试试这个 shell 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管道(未测试)。

于 2009-04-11T01:25:02.960 回答