我是一名非计算机科学专业的学生,正在撰写历史论文,包括确定多个文本中特定术语的频率,然后随着时间的推移绘制这些频率以确定变化和趋势。虽然我已经弄清楚如何确定给定文本文件的单词频率,但我正在处理(相对于我而言)大量文件(> 100)并且为了一致性而希望限制频率中包含的单词计入一组特定的术语(类似于“停止列表”的反义词)
这应该保持非常简单。最后,我需要的是我处理的每个文本文件的特定单词的频率,最好是电子表格格式(制表符描述文件),这样我就可以使用该数据创建图形和可视化。
我日常使用Linux,使用命令行很舒服,并且喜欢开源解决方案(或者我可以使用WINE运行的东西)。但这不是一个要求:
我认为有两种方法可以解决这个问题:
有什么想法吗?
答案 0 :(得分: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打破单词边界可能无法在所有情况下以您想要的方式工作,例如,如果您的文本文件包含跨行连字符的单词,则需要执行更智能的操作以匹配这些单词。在这种情况下,您可以检查一行中的最后一个字符是否为连字符,如果是,只需删除连字符并读取另一行,然后再将该行拆分为单词。
编辑:更新版本,处理不区分大小写的字词,并处理跨行的带连字符的字词。
请注意,如果存在带连字符的单词,其中一些是跨行的,有些则不是,则不会全部找到它们,因为它只删除了一行末尾的连字符。在这种情况下,您可能只想在删除连字符后删除所有连字符并匹配单词。您只需在拆分功能之前添加以下行即可完成此操作:
s/-//g;
答案 1 :(得分: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参数是一个包含您感兴趣的单词的文件,每行一个。
答案 2 :(得分:2)
首先熟悉词法分析以及如何编写扫描仪生成器规范。阅读使用YACC,Lex,Bison或我个人最喜欢的工具JFlex等工具的介绍。在这里您可以定义构成令牌的内容。在这里您可以了解如何创建标记化器。
接下来,您将拥有所谓的种子列表。停止列表的反面通常称为开始列表或有限词典。 Lexicon也是一件值得学习的好事。应用程序的一部分需要将开始列表加载到内存中,以便快速查询。存储的典型方式是每行一个单词的文件,然后在应用程序的开头读取一次,就像地图一样。您可能想了解散列的概念。
从这里开始,您需要考虑存储结果所需的基本算法和数据结构。分布很容易表示为二维稀疏数组。学习稀疏矩阵的基础知识。你不需要6个月的线性代数来理解它的作用。
因为您正在使用更大的文件,所以我会提倡基于流的方法。不要将整个文件读入内存。将其作为流进入令牌生成器,生成令牌流。
在算法的下一部分中,考虑如何将令牌列表转换为仅包含所需单词的列表。如果你考虑一下,列表就在内存中并且可能非常大,所以最好在开始时过滤掉非启动词。因此,在从tokenizer获取新令牌并将其添加到令牌列表之前的关键点,在内存中的start-words-list中查找该单词是否为起始单词。如果是这样,请将其保留在输出令牌列表中。否则忽略它并移动到下一个标记,直到读取整个文件。
现在您只有一个感兴趣的令牌列表。问题是,您没有考虑其他索引指标,如位置和案例以及上下文。因此,您实际上不需要所有令牌的列表。你真的只想要一个具有相关数量的不同令牌的稀疏矩阵。
因此,首先创建一个空的稀疏矩阵。然后考虑在解析期间插入新找到的令牌。当它发生时,如果它在列表中增加它的计数或以其他方式插入一个计数为1的新令牌。这次,在解析文件的末尾,你有一个不同的令牌列表,每个令牌的频率至少为1。
该列表现在已经在内存中,您可以随心所欲地执行任何操作。将其转储到CSV文件将是一个简单的过程,迭代条目并为每行写入每个条目及其计数。
就此而言,请查看名为“GATE”的非商业产品或TextAnalyst等商业产品或http://textanalysis.info列出的产品
答案 3 :(得分:1)
我猜测新文件会随着时间的推移而引入,这就是事情的变化吗?
我认为你最好的选择就是选择类似你的选项2.如果您想要做的只是计算关键字的出现次数,那么对文件进行预处理并不多。我只会浏览每个文件一次,每次出现列表中的单词时都要计算。就个人而言,我会用Ruby做,但像perl或python这样的语言也会让这个任务变得非常简单。例如,您可以使用关联数组,其中关键字作为键,并将出现次数作为值。 (但如果您需要存储有关事件的更多信息,这可能过于简单了。)
我不确定您是要存储每个文件的信息还是整个数据集的信息?我想这不会太难融入。
我不确定在获得数据后如何处理数据 - 将其导出到电子表格会很好,如果这样可以满足您的需求。或者你可能会发现,从长远来看,只需编写一些额外的代码就可以很好地为你显示数据。取决于你想要对数据做什么(例如,如果你想在练习结束时只生成几个图表并将它们放入报告中,那么导出到CSV可能最有意义,而如果你想生成一年一天的新数据集,然后构建一个自动执行此操作的工具几乎肯定是最好的主意。
编辑:我只是想通了,因为你正在研究历史,你的文件可能不会随着时间而改变,而是反映了已经发生的一系列变化。很抱歉误解了。无论如何,我认为我上面说的几乎所有内容仍然适用,但我想你会倾向于导出到CSV或者你有什么而不是自动显示。
听起来像一个有趣的项目 - 祝你好运!
本
答案 4 :(得分:1)
我会对文件执行“grep”以查找包含关键字的所有行。 (grep -f可用于指定要搜索的单词的输入文件(将grep的输出传递给文件)。这将为您提供包含单词实例的行列表。然后执行“sed”到用换行符替换你的单词分隔符(很可能是空格),给你一个单独的单词文件(每行一个单词)。现在再次运行grep,使用相同的单词列表,除了这次指定-c(以获得计数)具有指定单词的行;即原始文件中单词出现次数)。
双通法简化了“sed”的生活;第一个grep应该消除很多行。
您可以在基本的linux命令行命令中执行此操作。一旦你对这个过程感到满意,你就可以很容易地把它全部放到shell脚本中。
答案 5 :(得分: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
,它是完全可编写脚本的,因此您可以独立于输入数据调整输出。
答案 6 :(得分: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
插入管道(未测试)。