14

私はコンピュータ サイエンスを専攻していない学生で、歴史の論文を書いています。この論文では、多数のテキストで特定の用語の頻度を特定し、これらの頻度を経時的にプロットして変化と傾向を特定します。特定のテキストファイルの単語頻度を決定する方法を理解しましたが、(比較的、私にとっては) 多数のファイル (>100) を扱っており、一貫性のために、頻度カウントに含まれる単語を制限したいと考えています特定の用語のセット (「ストップ リスト」の反対のようなもの)

これは非常に単純に保つ必要があります。最後に必要なのは、処理する各テキスト ファイルの特定の単語の頻度だけです。できればスプレッドシート形式 (タブで区切られたファイル) で、そのデータを使用してグラフや視覚化を作成できます。

私は日常的に Linux を使用しており、コマンド ラインを快適に使用できます。また、オープン ソース ソリューション (または WINE で実行できるもの) が大好きです。ただし、これは要件ではありません。

この問題を解決するには、次の 2 つの方法があります。

  1. 事前に定義されたリストを除いて、テキスト ファイル内のすべての単語を取り除き、そこから頻度をカウントする方法を見つけます。または、次のようにします。
  2. 事前に定義されたリストの用語のみを使用して頻度カウントを行う方法を見つけてください。

何か案は?

4

7 に答える 7

7

私は2番目のアイデアに行きます。これは、提供された最初のファイルから単語のリストを読み取り、タブ区切り形式で提供された 2 番目のファイルからリスト内の各単語の数を出力する簡単な Perl プログラムです。最初のファイルの単語のリストは、1 行に 1 つずつ提供する必要があります。

#!/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;
于 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 引数は、関心のある単語を 1 行に 1 つずつ含むファイルです。

于 2008-11-24T22:51:11.923 に答える
2

まず、字句解析とスキャナー ジェネレーターの仕様の書き方に慣れてください。YACC、Lex、Bison、または私の個人的なお気に入りである JFlex などのツールの使用に関する紹介を読んでください。ここでは、トークンを構成するものを定義します。ここでは、トークナイザーの作成方法について学習します。

次に、シード リストと呼ばれるものがあります。ストップ リストの反対は、通常、スタート リストまたは制限付きレキシコンと呼ばれます。レキシコンも学ぶのに良いものです。アプリの一部は、すばやくクエリできるように、開始リストをメモリに読み込む必要があります。保存する一般的な方法は、1 行に 1 語のファイルを作成し、これをアプリの開始時にマップのようなものに一度読み込むことです。ハッシュの概念について学びたいと思うかもしれません。

ここからは、結果を格納するために必要な基本的なアルゴリズムとデータ構造について考えます。分布は、2 次元のスパース配列として簡単に表現できます。疎行列の基礎を学びます。線形代数が何をするかを理解するのに 6 か月も線形代数を学ぶ必要はありません。

より大きなファイルを扱っているため、ストリーム ベースのアプローチをお勧めします。ファイル全体をメモリに読み込まないでください。トークンのストリームを生成するトークナイザーにストリームとして読み込みます。

アルゴリズムの次の部分では、トークン リストを必要な単語だけを含むリストに変換する方法について考えます。考えてみると、リストはメモリ内にあり、非常に大きくなる可能性があるため、最初に非開始単語を除外することをお勧めします。したがって、トークナイザーから新しいトークンを取得し、それをトークン リストに追加する前に、メモリ内の start-words-list を検索して、その単語が開始単語かどうかを確認する重要なポイントで、その場合は、出力トークン リストに保持します。それ以外の場合は無視して、ファイル全体が読み取られるまで次のトークンに移動します。

これで、関心のあるトークンのみのリストができました。問題は、位置、ケース、コンテキストなどの他のインデックス作成メトリックを見ていないということです。したがって、すべてのトークンのリストは必要ありません。本当に必要なのは、カウントが関連付けられた個別のトークンの疎行列です。

したがって、最初に空の疎行列を作成します。次に、解析中の新しく見つかったトークンの挿入について考えてみましょう。それが発生すると、リストにある場合はそのカウントをインクリメントするか、カウントが 1 の新しいトークンを挿入します。今回は、ファイルの解析の最後に、それぞれの頻度が少なくとも1.

そのリストは現在メモリ内にあり、やりたいことが何でもできます。それを CSV ファイルにダンプすることは、エントリを反復処理し、各エントリを行ごとにそのカウントとともに書き込む簡単なプロセスです。

さらに言えば、「GATE」と呼ばれる非商用製品、または TextAnalyst のような商用製品、またはhttp://textanalysis.infoにリストされている製品を見てください。

于 2008-11-24T22:26:59.977 に答える
1

時間の経過とともに新しいファイルが導入されると思いますが、それが物事の変化の仕方ですか?

あなたの最善の策は、オプション 2 のようなものを使用することだと思います。キーワードの出現回数を数えることだけが必要な場合は、ファイルを前処理する意味はあまりありません。リスト内の単語が表示されるたびにカウントして、各ファイルを 1 回調べます。個人的には Ruby で行いますが、perl や python などの言語を使用すると、この作業がかなり簡単になります。たとえば、キーワードをキーとして連想配列を使用し、値として出現回数を使用できます。(ただし、発生に関する詳細情報を保存する必要がある場合、これは単純すぎる可能性があります)。

ファイルごとに情報を保存したいのか、それともデータセット全体について保存したいのかわかりません。取り入れるのはさほど難しくないと思います。

取得したデータをどうするかはわかりません。必要なものが得られるのであれば、スプレッドシートにエクスポートしても問題ありません。または、長期的には、データを適切に表示する追加のコードを少し書くだけの方が簡単であることに気付くかもしれません。データで何をしたいかによって異なります (たとえば、演習の最後にいくつかのグラフを作成してレポートに入れたい場合は、CSV にエクスポートするのがおそらく最も理にかなっています。 1 年間毎日新しいデータ セットを取得する場合、それを自動的に行うツールを構築することが、ほぼ間違いなく最良のアイデアです。

編集: あなたは歴史を勉強しているので、ドキュメントは時間の経過とともに変更されているのではなく、すでに発生した一連の変更を反映している可能性があることがわかりました。誤解してすみません。とにかく、上で述べたほとんどすべてが当てはまると思いますが、自動表示ではなく、CSV にエクスポートするか、何を持っているかに傾くと思います。

楽しいプロジェクトのようですね。頑張ってください!

ベン

于 2008-11-24T22:08:43.710 に答える
1

ファイルに対して「grep」を実行して、キーワードを含むすべての行を見つけます。(grep -f を使用して、検索する単語の入力ファイルを指定できます (grep の出力をファイルにパイプします)。これにより、単語のインスタンスを含む行のリストが表示されます。次に、「sed」を実行して単語区切り文字 (ほとんどの場合スペース) を改行に置き換えて、個別の単語 (1 行に 1 単語) のファイルを作成します. 同じ単語リストを使用して grep を再度実行しますが、今回は -c を指定します (カウントを取得するには)指定された単語を含む行の数 (つまり、元のファイルでの単語の出現回数)。

2 パス方式は、単純に "sed" の作業を楽にします。最初の grep は多くの行を削除するはずです。

これはすべて、基本的な Linux コマンドライン コマンドで実行できます。このプロセスに慣れたら、すべてをシェル スクリプトに簡単に組み込むことができます。

于 2008-11-24T22:37:21.030 に答える
1

大きなスクリプトで地獄に。すべての単語を取得したい場合は、次のシェル 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 に答える
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-testRobert 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 に答える