素人の言語処理100本ノック:39


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第4章: 形態素解析
>夏目漱石の小説『吾輩は猫である』の文章（neko.txt）をMeCabを使って形態素解析し，その結果をneko.txt.mecabというファイルに保存せよ．このファイルを用いて，以下の問に対応するプログラムを実装せよ．
>
>なお，問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい．

###39. Zipfの法則
>単語の出現頻度順位を横軸，その出現頻度を縦軸として，両対数グラフをプロットせよ．

####出来上がったコード：

```python:main.py
# coding: utf-8
import MeCab
from collections import Counter
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

fname = 'neko.txt'
fname_parsed = 'neko.txt.mecab'


def parse_neko():
	'''「吾輩は猫である」を形態素解析
	「吾輩は猫である」(neko.txt)を形態素解析してneko.txt.mecabに保存する
	'''

	with open(fname) as data_file, \
			open(fname_parsed, mode='w') as out_file:

		mecab = MeCab.Tagger()
		out_file.write(mecab.parse(data_file.read()))


def neco_lines():
	'''「吾輩は猫である」の形態素解析結果のジェネレータ
	「吾輩は猫である」の形態素解析結果を順次読み込んで、各形態素を
	・表層形（surface）
	・基本形（base）
	・品詞（pos）
	・品詞細分類1（pos1）
	の4つをキーとする辞書に格納し、1文ずつ、この辞書のリストとして返す

	戻り値：
	1文の各形態素を辞書化したリスト
	'''
	with open(fname_parsed) as file_parsed:

		morphemes = []
		for line in file_parsed:

			# 表層形はtab区切り、それ以外は','区切りでバラす
			cols = line.split('\t')
			if(len(cols) < 2):
				raise StopIteration		# 区切りがなければ終了
			res_cols = cols[1].split(',')

			# 辞書作成、リストに追加
			morpheme = {
				'surface': cols[0],
				'base': res_cols[6],
				'pos': res_cols[0],
				'pos1': res_cols[1]
			}
			morphemes.append(morpheme)

			# 品詞細分類1が'句点'なら文の終わりと判定
			if res_cols[1] == '句点':
				yield morphemes
				morphemes = []


# 形態素解析
parse_neko()

# Counterオブジェクトに単語をセット
word_counter = Counter()
for line in neco_lines():
	word_counter.update([morpheme['surface'] for morpheme in line])

# 全件取得
list_word = word_counter.most_common()

# 出現数のリスト取得
counts = list(zip(*list_word))[1]

# グラフで使うフォント情報(デフォルトのままでは日本語が表示できない)
fp = FontProperties(
	fname='/usr/share/fonts/truetype/takao-gothic/TakaoGothic.ttf'
)

# 散布図のデータ指定
plt.scatter(
	range(1, len(counts) + 1),	# x軸：順位
	counts						# y軸：出現頻度
)

# 軸の値の範囲の調整
plt.xlim(1, len(counts) + 1)
plt.ylim(1, counts[0])

# 対数グラフに
plt.xscale('log')
plt.yscale('log')

# グラフのタイトル、ラベル指定
plt.title("39. Zipfの法則", fontproperties=fp)
plt.xlabel('出現度順位', fontproperties=fp)
plt.ylabel('出現頻度', fontproperties=fp)

# グリッドを表示
plt.grid(axis='both')

# 表示
plt.show()
```

####実行結果：
![Kobito.agDusk.png](https://qiita-image-store.s3.amazonaws.com/0/139624/76623921-eaef-a16b-ff0a-86e25cd5ccbd.png "Kobito.agDusk.png")


###散布図
`parse_neko()`と`neco_lines()`は[前問](http://qiita.com/segavvy/items/c53c35827524f875ba2d)と同じです。

今回は散布図にしてみました。散布図は[`pyplot.scatter()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.scatter)にx軸の配列とy軸の配列を渡せば作ってくれます。簡単！

対数グラフにするには、[`pyplot.xscale()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.xscale)と[`pyplot.yscale()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.yscale)で指定します。

###Zipfの法則とは
ジップとかジフとか読むそうで、何かを集計した時に、その出現頻度の高い順に並べると`（個々の順位）×（その出現頻度）`が一定になる法則だそうです。この法則通りの場合は、両対数グラフに書くと斜め下がりの直線になります。確かになっていますね。
単語の出現頻度だけでなく、いろいろな自然現象、社会現象で成り立つ場合があるらしいのです。なんだか不思議。興味のある方はググってみてください。

　
40本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第4章で用いているデータは[青空文庫](http://www.aozora.gr.jp)で公開されている夏目漱石の長編小説『吾輩は猫である』が元になっています。*