テキストファイルの単語頻度辞書の作成

Python 配布プリント (10) by 水谷正大
http://www.ic.daito.ac.jp/~mizutani/python/index.html
コマンドラインのパラメータで読み込むべきテキストファイルを指定して、1行ずつ読み込みながら単語を
切り出してリストに追加するという作業を考える。
[重要な前提]
スクリプトは文字符号化 UTF-8 で保存されている(さらに改行文字 ’\n’ の符号化法は LF である)とし
よう。
12 文字列のメソッド
次の文字列 str は ALICE’S ADVENTURES IN WONDERLAND の冒頭段落である。
str = "Alice was beginning to get very tired of sitting by her sister on the bank,
and of having nothing to do: once or twice she had peeped into the book her sister
was reading, but it had no pictures or conversations in it, ‘and what is the use
of a book,’ thought Alice ‘without pictures or conversation?’"
12.1 文字列から削除すべき文字
英文文字列から、単語の切れ目を空白␣として単語を切り出すために、まず不要となる文字を削除すること
を考えよう。
上の例のように、カンマ (,) やピリオド (.) だけでなく、引用符 (‘/’/")、コロンとセミコロン (:;)、括
弧 (()[]{}) やアンパサンド (&)、等号 (=)、スラッシュ (/) などがある。Today was cold(though I felt
comfortable). から ( を削除すると coldthough が単語となってしまう。削除したい文字を空白␣に置き換
えよう。
文字列 string において、指定した文字列 before を見つけて文字列 after で置き換えた文字列を返すメ
ソッド replace があり、次のように使う。
文字列内の文字を置き換える
string.replace(before, after)
このことを Python Shell で確かめてみよう。文字列 st に Lincolin の Gettysburg(Nov. 19, 1863)での
短い演説の最終節が代入されている。st 内のカンマ (’,’) を文字列 ’XX’ に置き換えた結果を示す。replace
は対象となる文字列の内容には影響しないことに注意する。
>>> st = "shall have a new birth of freedom -- and that government of the people,
by the people, for the people, shall not perish from the earth"
>>> st.replace(’,’,’XX’)
’shall have a new birth of freedom -- and that government of the peopleXX
by the peopleXX for the peopleXX shall not perish from the earth.’
>>> st
’shall have a new birth of freedom -- and that government of the people,
by the people, for the people, shall not perish from the earth.’
また、対象となる文字列には存在しないしない文字列を置き換えようとしたときは何も起こらない (ありがた
い!) ことがわかる。
>>> st.replace(’(’,’ ’)
’shall have a new birth of freedom -- and that government of the people,
by the people, for the people, shall not perish from the earth.’
1
ALICE’S ADVENTURES IN WONDERLAND の冒頭を代入した文字列 str において、カンマ (’,’) を
空白␣に置き換えた結果を見るには次のように実行すればよい。
str = "Alice was beginning ......pictures or conversation?’"
modified = str.replace(’,’, ’ ’)
print modified
[問題 1] 上のスクリプトを実際に実行せよ。さらに、引用符 (‘/’/") および疑問符 (?) を空白␣に置き換えた
結果を表示せよ。さらにまた、コロン (:) あるいは何か削除すべき文字を削除した結果を表示せよ。
12.2 論理の階段を減らす–綿密に調査し慎重に確かめる
Python スクリプトによる処理とは、扱うデータサイズに無関係に同じコードを使って処理するということ
だ。スクリプトが目的とする処理を正しく実行しているかを、事前に自動検証することは不可能なことが証明
されている*1 。
たとえ短いスクリプトであっても、スクリプトにいきなり巨大な仕事をさせることは無謀なだけで無神経す
ぎる。目的の処理を記述するスクリプトを一気に書き上げることはせず、解くべき問題をさらに小さな問題に
分割して、それらの小さな問題を検証しながら慎重に解くことを優先させる。論理を積み上げることが問題を
解くことになるという ‘誤った考え’ を捨てることが肝要で、むしろ可能な限り論理の階段を少なくするように
考えることが論理的思考の実態である。
文字列 string において、指定した文字列 sep によって
string = sub1 + sep + sub2 + . . . + sep subn−1 + sep + subn
というように部分文字列 {subk } に分割し、リスト
[ sub1 ,sub2 , . . . , subn−1 , subn ]
を返すメソッド split(sep) があり、次のように使う。
文字列 sep で部分文字列を切り出す
string.split(sep)
表 1 に文字列に対する代表的メソッドを紹介した。
[問題 1] で行ったように、文字列 modified の文字並びが 1 つ以上の空白␣によって区切られているとする。
このとき文字列メソッド split() を使うと次のようになることが Python Shell で確かめられる。
>>> modefied.split()
[’Alice’, ’was’, ’beginning’, ’to’, ’get’, ’very’, ’tired’, ’of’, ’sitting’, ’by’,
’her’, ’sister’, ’on’, ’the’, ’bank’, ’and’, ’of’, ’having’, ’nothing’, ’to’, ’do’,
’once’, ’or’, ’twice’, ’she’, ’had’, ’peeped’, ’into’, ’the’, ’book’, ’her’,
’sister’, ’was’, ’reading’, ’but’, ’it’, ’had’, ’no’, ’pictures’, ’or’,
’conversations’, ’in’, ’it’, ’and’, ’what’, ’is’, ’the’, ’use’, ’of’, ’a’, ’book’,
’thought’, ’Alice’, ’without’, ’pictures’, ’or’, ’conversation’]
[問題 2] [問題 1] の続き。
上の事実を次のようにスクリプトを実行して確かめよ。
str = "Alice was beginning ......pictures or conversation?’"
modified = str.replace(’,’, ’ ’)
...
*1
Turing 機械が与えられた任意の入力を受理するかはは判定不能である。ソフトウエア検証の一般的問題は計算機では解くことが
できない。
2
... 空白文字で区切られるように置き換え処理を行う
word_list = modified.split()
print word_list
以上からわかるように、文字列内に登場する英単語が 1 つ以上の連続する空白␣で区切られていれば、文字
列に登場した部分文字列としての英単語をリストとして取り出すことは簡単にできる。
したがって、まず文字列から余分な文字を空白で置き換えて英単語だけが空白文字␣で区切られるようにす
ることを考える。次の関数 modefy_line(string) は、文字列 string を与えると、文字列メソッド lower()
を使って、文字列を全て小文字にした上で(大文字で始まる固有名詞を考えない)、英単語だけが切り出せる
ように不要だと考えられる文字を空白文字に置き換え処理を行った末の文字列を返す(戻り値は文字列)。
注目すべきことが 2 点ある。1 つは、文字列に対しメソッドは連続適用が可能であることだ。replace を連
続適用して改行文字’\n’ および tab 文字’\t’ を空白に置き換えている(ここでは 2 回連続適用だが、さら
に重ねて適用してよい)。もう 1 つは、置き換える文字を文字列 removing_character_string に与えてお
いて、for 文で文字列から 1 文字ずつ取り出しながら置き換え作業を行っていることだ(この文字列に改行や
tab 文字も含めてもよかったのだが)。
置き換え文字列をどのように与えるべきかは、目的のテキストを子細に徹底的に検討して、その妥当性を慎
重に吟味すべきである。この作業をおろそかにしてはいけない。大規模なテキストを対象に一括処理すること
を目的としている。ここに十分な時間をかけることは十分な意味がある。
def modify_line(string):
removing_character_string = "‘’()<>[]{}&/?=-_:;,."
modefied_str =string.lower()
modefied_str = modefied_str.replace(’"’,’ ’)
modefied_str = modefied_str.replace(’\n’,’’).replace(’\t’,’ ’)
for s in removing_character_string:
modefied_str = modefied_str.replace(s,’ ’)
return(modefied_str)
[問題 3] [問題 2] の続き。
上の関数 modefy_line(string) が定義されているとすれば、スクリプトのメイン部は次のように簡素に
記述できる。以下のようにして実行してみよ。
# ------ main -------str = "Alice was ......pictures or conversation?’"
modified = modefy_line(str)
word_list = modified.split()
print word_list
3
操作例
意味
string.lower()
文字列 string の全ての文字を小文字にして返す。string は変わらな
い。
string.upper()
文字列 string の全ての文字を大文字にして返す。string は変わらな
い。
string.count(item)
文字列 string 内の文字列 item の登場回数を返す。
string.find(item)
文字列 string 内の文字列 item が初めて登場する位置を返す。
string.rfind(item)
文字列 string 内の文字列 item が最後に見つかった位置を返す。
string.startswith(item)
文字列 string が文字列 item 文字列で始まるかどうかで True また
は False を返す。
string.endswith(item)
文字列 string が文字列 item で終わるかどうかで True または False
を返す。
string.center(w)
文字列 string を長さ w のフィールド内の中央に配置して返す。文
字列内容は変わらない。
string.rjust(w)
文字列 string を長さ w のフィールド内に右寄せして返す。string
は変わらない。
string.ljust(w)
文字列を長さ w のフィールド内に左寄せして返す。string は変わら
ない。
string.replace(before, after)
文字列 string 内の文字列 before を検索し、文字列 after にすべて
置き換えた結果を返す。string は変わらない。
string.split(sep)
文字列 string を、指定した文字列 sep を区切りとして部分文字列に
分割して、部分文字列のリストを返す (文字列 sep は部分文字列に
は現れない)。ただし、文字列 sep を明示せず省略したときは空白 1
文字” ”で分割される。
sep.join(alist)
文字列を要素とするリスト list の要素を文字列 sep で挟み込んで連
接した文字列を返す。sep が ””(空文字列)のときはリスト alist の
要素をそのまま並べた文字列を、sep が 空白 (” ”) としたときは空
白を挟んで並べた文字列が返る。
表1
文字列の代表的メソッド
13 リスト処理
以上で、文字列に対して登場する単語を切り出して、それらをリストに並べることができた。
コマンドライン引数で処理対象のテキストファイル document.txt を指定して次のように実行することを
考えてみよう。
$ python make_dictionary.py document.txt
make_dictionary.py は指定したテキストに含まれる単語 word をキーとし、その登場頻度 freq を値と
する辞書要素
word: freq
を作成するスクリプトである。
次は、コマンドライン引数で指定したテキストを1行ずつ読み込んで文字列 line に格納し、文字列から英
単語を空白␣で区切られた文字列を返す関数 modify_line(line) を文字列メソッド split() で英単語リス
ト wlist として代入、これをプリントし続ける。ただし、メインスクリプトでは単語頻度辞書を作成する関
数 make_word_frequency_dictionary はこれから考えるもので以下では使っていない。
make dictionary.py の素朴過ぎる骨格
# -*- coding: utf-8 -*4
import sys
def modify_line(string):
...
省略
# 単語リスト word_list から単語頻度辞書を作成
def make_word_frequency_dictionary(word_list):
...
省略
#------
main script
-------
fh = open(sys.argv[1], ’r’)
while line:
wlist = modify_line(line).split()
print wlist
line = fh.readline()
fh.close()
[問題 4] [問題 3] の続き。
ALICE’S ADVENTURES IN WONDERLAND の冒頭から数段分(2 段以上、5 段未満程度の適切な分
量)を alice.txt として保存する。
コマンドラインから alice.txt を指定して、上のスクリプト(関数 make_word_frequency_dictionary(word_list)
を省く)を実行してみよ。
このスクリプトでは、ファイルから 1 行ずつ読み取って作成した単語リストをプリントするだけで ‘捨てて
しまって’ いる。これらの単語リストを連結してテキストに登場する全ての単語(もちろん、重複するために、
その登場頻度を問題にしている)を 1 つのリストとして得ることが今の課題だ。
13.1 リストの追加合併
今の課題は、既にあるリスト list に新規に得たリスト blist を追加して、改めて拡大したリスト list
を得ることである。この操作には次のようにしてリストに対して拡大メソッド extend() を使う。リストメ
ソッドによって、リスト内容がどう変化するかに注意する。このメソッドは値を返さない(None を返す)が、
被操作リスト list 内容は変更され、既にある list 要素の並びに加えてリスト blist の要素全体が追加され
ている。
リスト list の末尾にリスト blist 要素全体を追加した list を得る
list.extend(blist)
このことは次のように Python Shell で確かめることができる。
>>> a = [1, 2, ’meron’]
>>> b = [’apple’, 7, ’orange’, 3]
>>> a.extend(b)
>>> a
[1, 2, ’meron’, ’apple’, 7, ’orange’, 3]
>>> b
[’apple’, 7, ’orange’, 3]
メソッドの操作対象となったリスト内容が変化し、一方、追加側のリストには影響を及ぼさないことがわかる。
5
リスト拡大メソッドを使うことによって、まず空リスト word_list = [] を用意しおき、次のようにして、
開いたテキストファイルを 1 行ずつ文字列 line として読み込んで、空白文字␣によって切り出した単語リス
ト wlist をその都度、リスト拡大メソッドによって word_list に追加してテキスト全体に登場した重複のあ
る単語リストを得ることができる。
#------
main script
-------
fh = open(sys.argv[1], ’r’)
word_list = []
line = fh.readline()
while line:
wlist = modify_line(line).split()
word_list.extend(wlist)
line = fh.readline()
fh.close()
表 2 にリストに対する代表的メソッドを示した。
操作例
意味
list.append(item)
リスト list の末尾に要素 item を追加する。list の内容は変化する。
list.insert(k, item)
リスト list の k 番目位置に末尾に要素 item を挿入する。list の内容は変化す
る。
list.extend(blist)
リスト list に別に用意したリスト blist を末尾に追加する。list の内容は変化
する。
list.pop()
リスト lsit の末尾要素を削除し、その要素を返す。list の内容は変化する。
list.pop(k)
リストの k 位置要素を削除し、その要素を返す。list の内容は変化する。
list.remove(item)
リスト list 内で値 item を最初に持つ要素を削除する。list の内容は変化す
る。該当する要素が無ければエラー。
list.reverse()
リスト list 要素の並びを逆にする。
list.index(item)
リスト内で item が最初に登場する位置を返す。リストに item がなければエ
ラー。
list.count(item)
リスト list 内にある item の数を返す。リストに item がなければ 0 を返す。
list.sort()
リスト list 内の要素を並べ替える。sort(key = None, reverse = None) とい
うオプション key と reverse をもつ。オプションを省略したときはデフォル
ト値 None が指定されたとする。reverse=True とすると降順並べ替えする。
key を使って、比較を行う前にリストの各要素に対して呼び出される関数を指
定できる。
表2
リストに対する代表的メソッド
14 単語リストから単語頻度辞書を作成する
既に紹介したことだが、ディクショナリ dict に指定したキー key が含まれているかどうかを確認する論
理式を次のように書く。
dict に指定した key があるかを確認
key in dict
6
key がディクショナリに存在していれば True を、なければ False の値を取る*2 。
また、指定したキー key がディクショナリ dict に登録されていないときに関連付けられる値 value とし
て新たに登録、または登録されている key に値 value で関連付けられる値を更新するには次の構文を使う。
ディクショナリの更新
dict[key] = value
これらを使うと、単語リスト word_list から単語頻度辞書を作成する関数 make_word_frequency_dictionary(word_list)
を次のように定義することができる。
空のディクショナリ word_frequency_dict を空集合として準備しておき、与えた単語リスト word_list
の先頭から要素を取り出して word とし、word がディクショナリ word_frequency_dict にキーとして含
まれるかを判定し、もし含まれていれば関連付けられた値 word_frequency_dict[word] を演算子 = で
1 だけ更新、ディクショナリに含まれていなければ関連付けられた値を 1 として登録する。こうして、単
語リスト word_list 内の全ての単語をキーとし、その頻度を関連付けられた値として持つディクショナリ
word_frequency_dict が得られる。
単語リスト word list から単語頻度辞書を作成
def make_word_frequency_dictionary(word_list):
word_frequency_dict = {}
for word in word_list:
if word in word_frequency_dict:
word_frequency_dict[word] += 1
else:
word_frequency_dict[word] = 1
return(word_frequency_dict)
こうして、指定したテキストファイルに登場する単語頻度辞書 word_frequency_dict を作成、その結果
を印刷するスクリプト make_dictionary.py の基本形が以下のように得る。
make dictionary.py の基本形
# -*- coding: utf-8 -*import sys
def modify_line(string):
...
省略
def make_word_frequency_dictionary(word_list):
...
省略
#------
main script
-------
fh = open(sys.argv[1], ’r’)
word_list = []
line = fh.readline()
while line:
wlist = modify_line(line).split()
word_list.extend(wlist)
*2
Python2 まで使われていたディクショナリメソッド dict.has_key(key) は Python3 で廃止されたため、このメソッドによる
キーの存在確認は非推奨である。
7
line = fh.readline()
fh.close()
word_frequency_dict = make_word_frequency_dictionary(word_list)
print word_frequency_dict
[問題 5] [問題 4] の続き。
ALICE’S ADVENTURES IN WONDERLAND の冒頭から数段分(2 段以上、5 段未満程度の適切な分
量)に登場する英単語の頻度辞書を作成するスクリプトを書いて、その辞書内容の印刷結果を報告せよ。
ディクショナリに対する基本メソッドを表 3 に示した。
操作例
意味
dict.items()
ディクショナリ dict の要素であるキー key とその値 value をタプル (key, value)
とするリストを返す。dict 内容は変化しない。
dict.keys()
ディクショナリ dict の要素の全てのキーを要素とするリストを返す。dict 内容は変
化しない。
dict.values()
ディクショナリ dict の要素の全ての値を要素とするリストを返す。dict 内容は変化
しない。
表 3 ディクショナリに対する代表的メソッド
8