ページ 11

(Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 07:49
by 028el
お世話になります。パズルゲームもまだ完成していませんが(涙)、こちらの課題も提出しなければなりません。C言語ではなくてPythonなのですが、どなた様かお知恵をお貸しくださいませんでしょうか。
頻出単語分析のプログラムです。コマンドライン引数で渡されたテキストの英単語の総数と上位10の頻出単語を表示するというものです。ただし、コマンドライン引数で渡すテキストは複数で、個々のテキストの分析とともにテキスト合計の分析も行います。
個々のテキストの単語の総数、上位10の頻出単語の表示までは非常に拙いながらも成功しましたが、総合上位10の頻出単語の表示で詰まってしまいました。さらに、それを視覚的に表示しなければいけないのですが、その点もどうしたらよいのか見当がつきません。文書ではうまく説明できていないと思います。表示画面のサンプルは以下の通りです。

コード:

$ python textanalyzer.py -i thesis.txt lol.txt loro.txt

### FILE: thesis.txt

32758 Words

the        1637 (  4.996%) ==============================
i           511 (  1.557%) ==========
a           327 (  0.998%) ======
is          102 (  0.311%) ==
my          100 (  0.305%) ==
cool         50 (  0.152%) =
python       32 (  0.098%) =
linux        31 (  0.098%) =
some         28 (  0.085%) =
image        21 (  0.064%) =

### FILE: lol.txt

1000 Words

lol        1000 (100.000%) ==============================

### FILE: loro.txt

1000 Words

loro        600 ( 60.000%) ==============================
orol        400 ( 40.000%) ====================

### TOTAL

34758 Words

the        1637 (  4.710%) ==============================
lol        1000 (  2.877%) ==================
loro        600 (  1.726%) ============
i           511 (  1.470%) ========
orol        400 (  1.151%) =======
a           327 (  0.941%) =====
is          102 (  0.293%) ==
my          100 (  0.288%) ==
cool         50 (  0.144%) =
python       32 (  0.092%) =

以下がコードです。

コード:


#!/user/bin/env python3

import sys

argvs = sys.argv
argc = len(argvs)

if (argc < 2):
	print ('Usage: # python %s filename' % argvs[0])
	quit()

elif (argc == 2):
	print ('\n ### FILE: %s\n' % argvs[1])
	f= open(argvs[1])
	data = f.read()

	# counting of 1st file
	words = {}
	i = 0
	for word in data.split():
			words[word] = words.get(word, 0)+1
			i=i+len(word.split())
	print (' %d Words\n' % i)
		
	# sort by count of 1st file
	d = [(v,k) for k,v in words.items()]
	d.sort()
	d.reverse()
	for count, word in d[:10]:
			print ( " %s   \t:%d (%.3f%% ) " % (word,count,(count/i)*100) )
	
	f.close()

elif (argc == 3):
	print ('\n ### FILE: %s\n' % argvs[1])
	f= open(argvs[1])
	data = f.read()

	# counting of 1st file
	words = {}
	i = 0
	for word in data.split():
			words[word] = words.get(word, 0)+1
			i=i+len(word.split())
	print (' %d Words\n' % i)
		
	# sort by count of 1st file
	d = [(v,k) for k,v in words.items()]
	d.sort()
	d.reverse()
	for count, word in d[:10]:
			print ( " %s\b\t:%d (%.3f%% ) " % (word,count,(count/i)*100) )
	
	print ('\n ### FILE: %s\n' % argvs[2])
	f= open(argvs[2])
	data = f.read()

	# counting of 2nd file
	words = {}
	j = 0
	for word in data.split():
			words[word] = words.get(word, 0)+1
			j=j+len(word.split())
	print (' %d Words\n' % j)
		
	# sort by count of 2nd file
	d = [(v,k) for k,v in words.items()]
	d.sort()
	d.reverse()
	for count, word in d[:10]:
			print ( " %s\b\t:%d (%.3f%% ) " % (word,count,(count/j)*100) )
			
	print ('\n ### Total \n')
	print (' %d Words\n' % (i+j))
	
	f.close()

else:
	print ('Usage: # python %s filename filename' % argvs[0])
	quit()


Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 10:36
by mikko
まず、引数として任意の個数のファイル名が与えられる事に対応する必要があるのではないかと思います。そのためには実質コピペになっている部分のコードをリファクタする必要があるでしょうね……
ヒント:

コード:

for fn in sys.argv[1:]:
        print(fn)

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 14:05
by かずま
視覚的表示はこんな風にすればいかがでしょうか?

コード:

def output(words, n, total):
    print('%d Words\n' % n)
    d = [(v,k) for k,v in words.items()]
    d.sort(reverse = True)
    m = d[0][0]
    for count, word in d[:10]:
        rate = 100.0 * count / n
        k = int((32 * count + m - 1.0)/ m)
        print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))
        total[word] = total.get(word, 0) + count
 
words = {
    'a' : 327, 'cool' : 50, 'i' : 511, 'image' : 21, 'is' : 102,
    'linux' : 31, 'my' : 100, 'python' : 32, 'some' : 28, 'the' : 1637
}
n = 32758
total = {}
total_count = 0
output(words, n, total)

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 18:11
by 028el
mikko さま

返答ありがとうございます。お礼が遅くなってしまいすみません。
リファクタという言葉も知らなかったくらいで、勉強させていただきました。
ヒントを参考にさせていただきます。

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 18:20
by 028el
かずまさま

返信が遅くなりすみません。
お力添えのお陰で視覚的表示の部分は解決できました!
この度もまた本当にありがとうございます。

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月10日(日) 19:15
by かずま
028el さんが書きました:お力添えのお陰で視覚的表示の部分は解決できました!
解決できたということは次の式が理解できたということでしょうか?

コード:

        k = int((32 * count + m - 1.0)/ m)
これを次のように訂正します。

コード:

        k = (32 * count + m - 1) / m
なぜ、こんな式になっているのか分かりますか?

次のコードも不要でした。

コード:

total_count = 0

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月11日(月) 05:50
by 028el
かずまさま

再び返信が遅くなってしまいすみません。
あやふやな知識と見様見真似でやっているので恥ずかしいのですが、
K= int ((32 * count + m - 1,0)/m)
の部分は一番多い単語に32個の=を表示させ、それ以下の単語の=の数を比率できめているところだと理解していましたが、正しいでしょうか。
1,0の部分はそれでも動作に問題がなかったので、Pythonではこう書くのかな?くらいの認識でした。訂正しました。
ご指南ありがとうございます。まだまだ勉強不足です。

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月11日(月) 10:20
by かずま
028el さんが書きました:K= int ((32 * count + m - 1,0)/m)
の部分は一番多い単語に32個の=を表示させ、それ以下の単語の=の数を比率できめているところだと理解していましたが、正しいでしょうか。
はい、その理解で正しいです。
ただ、1,0 ではなく 1.0 です。comma ではなく小数点。
大文字 K ではなく、小文字の k。

Python の割り算は、整数の場合は浮動小数点数と違って商が切り捨てです。
直前のパーセントの計算で、
rate = count / n * 100 だと結果が常に 0 になります。
rate = 100 * count / n だと、小数点以下が 0 になります。そこで
rate = 100.0 * count / n として浮動小数点数の計算にしています。

このことに引きずられて、'=' の個数 k の計算も、最初
k = 32.0 * count / m としました。
そうしたら、'=' * k でエラーになりました。あわてて
k = int(32.0 * count / m) としました。
int() は切り捨てですから、count が小さい場合、k は 0 になります。
期待する表示では、どんなに小さくても 0 でなければ 1個の '=' を出すようなので、
切り上げにするため、m - 1 を追加し、
k = int(32.0 * count + m - 1) / m としました。
割り算の直前に整数を浮動小数点数に変換すればよいと考え
k = int(32 * count + m - 1.0) / m としました。

最終的に割り算の結果を int に変換するのなら、最初から int の割り算でよかったわけで
k = (32 * count + m - 1) / m になりました。

理解できたかどうか確認したのは、次の 2点を学んでほしかったということです。

- 整数の割り算は浮動小数点数と違って、結果が切り捨て
- 切り上げにしたければ、(分母 - 1) を 分子に加える

課題の解決を目指して頑張ってください。
結果はここに載せてほしいのですが、コードタグを code=python にすると括弧が
表示されないというバグがあるようなので、私は code=text にしています。

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月11日(月) 19:02
by 028el
かずまさま

- 整数の割り算は浮動小数点数と違って、結果が切り捨て
- 切り上げにしたければ、(分母 - 1) を 分子に加える

ありがとうございます。これは良く覚えておきます。

課題の提出期限が日本時間の早朝だったので、不完全ではありましたがとにかく提出しました。
(期限内に提出しないと評価が0になってしまうので。)
テキスト全体の頻出単語上位10の部分は未解決、コピーペーストになっているコードもそのままなので、本当は隠しておきたいところですが、コードは以下の通りです。
最初にお伺いを立てるとき、コードタグをcode=pythonにしたのですが、おっしゃる通りうまくいきませんでした。括弧だけでなく、コードも一部消えてしまいました。
まだ解決にはせずこのままにしておきます。

コード:

#!/user/bin/env python3

import sys

argvs = sys.argv
argc = len(argvs)

if (argc < 2):
	print ('Usage: # python %s filename' % argvs[0])
	quit()

elif (argc == 2):
	print ('\n ### FILE: %s\n' % argvs[1])
	f= open(argvs[1])
	data = f.read()

	words = {}
	i = 0
	for word in data.split():
		words[word] = words.get(word, 0)+1
		i=i+len(word.split())
	print (' %d Words\n' % i)
		
	d = [(v,k) for k,v in words.items()]
	d.sort(reverse = True)
	m = d[0][0]
	for count, word in d[:10]:
			rate = 100.0 * count / i
			k = int((30 * count + m - 1)/ m)
			print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))

	f.close()

elif (argc == 3):
	#analyse of 1st file	
	print ('\n ### FILE: %s\n' % argvs[1])
	f= open(argvs[1])
	data = f.read()

	words = {}
	i = 0
	for word in data.split():
		words[word] = words.get(word, 0)+1
		i=i+len(word.split())
	print (' %d Words\n' % i)
		
	d = [(v,k) for k,v in words.items()]
	d.sort(reverse = True)
	m = d[0][0]
	for count, word in d[:10]:
			rate = 100.0 * count / i
			k = int((30 * count + m - 1)/ m)
			print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))

	#analyse of 2nd file
	print ('\n ### FILE: %s\n' % argvs[2])
	f= open(argvs[2])
	data = f.read()

	words = {}
	j = 0
	for word in data.split():
		words[word] = words.get(word, 0)+1
		j=j+len(word.split())
	print (' %d Words\n' % j)
		
	d = [(v,k) for k,v in words.items()]
	d.sort(reverse = True)
	m = d[0][0]
	for count, word in d[:10]:
			rate = 100.0 * count / j
			k = int((30 * count + m - 1)/ m)
			print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))
	
	print ('\n ### Total \n')
	print (' %d Words\n' % (i+j))
	
	f.close()

else:
	print ('Usage: # python %s filename (max 2 file)' % argvs[0])
	quit()

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月11日(月) 22:10
by かずま
mikkoさんのアドバイスを取り入れていませんね。
同じ処理をファイルの個数だけ実行するならループを使うべきです。
また、同じ処理が何度も出てくるなら、それは関数にまとめた方がいいでしょう。

コード:

import sys
 
def output(words, n, total):
    print('%d Words\n' % n)
    d = [(v,k) for k,v in words.items()]
    d.sort(reverse = True)
    m = d[0][0]
    for count, word in d[:10]:
        rate = 100.0 * count / n
        k = (32 * count + m - 1)/ m
        print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))
        total[word] = total.get(word, 0) + count

if (len(sys.argv) < 2):
    print ('Usage: # python %s filename' % sys.argv[0])
    quit()
 
total = {}
total_count = 0
for f in sys.argv[1:]:
    print ('\n ### FILE: %s\n' % f)
    f = open(f)
    data = f.read()
    s = data.split() 
    words = {}
    for word in s:
        words[word] = words.get(word, 0) + 1
    n = len(s)
    output(words, n, total)
    total_count += n
    f.close()
    
print ('\n ### Total \n')
output(total, total_count, {})

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月12日(火) 04:38
by 028el
かずまさま

そうなんです、mikkoさんのくださったヒントを取り入れられませんでした。
関数とループは一番大事なところだと思いますが、いつも尻込みしてしまいます。
そのせいで超初心者の域を出られません。
コードをじっくり見て勉強させていただきます。
今回のトピックでは本当にお世話になりました。
この件はこれで解決とさせていただきます。

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月12日(火) 09:53
by かずま
プログラムにバグがあったので訂正します。

3つのファイルに次のような単語がその個数 含まれていたとします。

コード:

aa 20    ba 20    ca 20
ab 19    bb 19    cb 19
ac 18    bc 18    cc 18
 :
aj 11    bj 11    cj 11
zz 10    zz 10    zz 10
トータルでは zz が 30 でトップなのですが、トータルの計算を各ファイルの
先頭10個だけしか見ずにやっているので結果に表示されません。
次のように修正します。

コード:

import sys
 
def output(words, n):
    print('%d Words\n' % n)
    d = [(v,k) for k,v in words.items()]
    d.sort(reverse = True)
    m = d[0][0]
    for count, word in d[:10]:
        rate = 100.0 * count / n
        k = int((32 * count + m - 1.0)/ m)
        print('%-10s %4d (%7.3f%%) %s' % (word, count, rate, '=' * k))

if (len(sys.argv) < 2):
    print ('Usage: # python %s filename' % sys.argv[0])
    quit()
 
total = {}
total_count = 0
for fn in sys.argv[1:]:
    print ('\n ### FILE: %s\n' % fn)
    f = open(fn)
    data = f.read()
    s = data.split() 
    words = {}
    for word in s:
        words[word] = words.get(word, 0) + 1
        total[word] = total.get(word, 0) + 1
    n = len(s)
    output(words, n)
    total_count += n
    f.close()
    
print ('\n ### Total \n')
output(total, total_count)

Re: (Python) 頻出単語分析プログラム

Posted: 2016年7月13日(水) 17:27
by 028el
かずまさま

バグに気付きませんでした。
ありがとうございます。
お礼が遅くなってすみませんでした。