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


[言語処理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を用いるとよい．

###37. 頻度上位10語
>出現頻度が高い10語とその出現頻度をグラフ（例えば棒グラフなど）で表示せよ．

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

```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])

# 頻度上位10語の取得
size = 10
list_word = word_counter.most_common(size)
print(list_word)

# 単語（x軸用）と出現数（y軸用）のリストに分解
list_zipped = list(zip(*list_word))
words = list_zipped[0]
counts = list_zipped[1]

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

# 棒グラフのデータ指定
plt.bar(
	range(0, size),		# x軸の値（0,1,2...9）
	counts,				# それに対応するy軸の値
	align='center'		# x軸における棒グラフの表示位置
)

# x軸のラベルの指定
plt.xticks(
	range(0, size),		# x軸の値（0,1,2...9）
	words,				# それに対応するラベル
	fontproperties=fp	# 使うフォント情報
)

# x軸の値の範囲の調整
plt.xlim(
	xmin=-1, xmax=size	# -1〜10（左右に1の余裕を持たせて見栄え良く）
)

# グラフのタイトル、ラベル指定
plt.title(
	'37. 頻度上位10語',	# タイトル
	fontproperties=fp	# 使うフォント情報
)
plt.xlabel(
	'出現頻度が高い10語',# x軸ラベル
	fontproperties=fp	# 使うフォント情報
)
plt.ylabel(
	'出現頻度',			# y軸ラベル
	fontproperties=fp	# 使うフォント情報
)

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

# 表示
plt.show()
```

####実行結果：

```console:端末
[('の', 9198), ('。', 7486), ('て', 6798), ('、', 6772), ('は', 6418), ('に', 6177), ('を', 6047), ('と', 5486), ('が', 5339), ('た', 3968)]
```

![Kobito.F4PjId.png](https://qiita-image-store.s3.amazonaws.com/0/139624/8484070c-71c8-90e2-39ec-7a5a09fcee5c.png "Kobito.F4PjId.png")


###matplotlibのインストール
問題で奨められているmatplotlibはすでに入っていました。[問題02](http://qiita.com/segavvy/items/725b20f21951975a06fd)で入れたAnacondaに含まれていたようです。良かった^^

###matplotlibの使い方

`parse_neko()`と`neco_lines()`、および`word_counter`を求める所までは[前問](http://qiita.com/segavvy/items/932c1413e2552f208dfc)と同じです。

matplotlibそのものについては、ネットに解説がたくさんありますので詳細は割愛します。オフィシャルサイトは[こちら](http://matplotlib.org)です。

以下、いくつかポイントです。コードにも多めにコメントを入れてみました。

####初めて実行した時のwarning

matplotlibを初めて`import`すると、次のような警告が表示されます。

```console:端末
segavvy@ubuntu:~/ドキュメント/言語処理100本ノック2015/37$ python main.py 
/home/segavvy/anaconda3/lib/python3.5/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
  warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.')
```

これは、フォントキャッシュをビルドする作業だそうで、初回だけ表示されます。次回以降は出てこないので、特に気にする必要はありません。

####棒グラフ

棒グラフは[`pyplot.bar()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.bar)を使います。パラメータで必須なのは、x軸の値のリストと、それに対応するy軸の値のリストだけです。`align='center'`は表示を中央にしたかったために指定しています。指定しないと左に寄りますので試してみてください。

####グラフ用に結果を変換
`word_counter.most_common(10)`の取得結果は、

```console:list_word
[('の', 9198), ('。', 7486), ('て', 6798), ('、', 6772), ('は', 6418), ('に', 6177), ('を', 6047), ('と', 5486), ('が', 5339), ('た', 3968)]
```

という形ですが、グラフにするためには、x軸（単語）とy軸（その出現頻度）のリストに分ける必要があります。これは[問題02](http://qiita.com/segavvy/items/725b20f21951975a06fd)で出てきた`zip()`を使うと簡単です。

```console:list(zip(*list_word))
[('の', '。', 'て', '、', 'は', 'に', 'を', 'と', 'が', 'た'), (9198, 7486, 6798, 6772, 6418, 6177, 6047, 5486, 5339, 3968)]
```

####グラフで日本語を使う場合の注意

デフォルトのフォントには日本語の文字のデータがないため、日本語を表示しようとすると四角になってしまいます（豆腐とか呼ばれています）。そのため、[`FontProperties`クラス](http://matplotlib.org/api/font_manager_api.html#matplotlib.font_manager.FontProperties)で指定する必要があります。今回はUbuntu 16.04 LTSに入っていた`TakaoGothic.ttf`を指定しています。
なお、デフォルトのフォントを切り替えてしまう方法もあるようですが、今回は試していません。

####x軸の値の範囲

グラフの軸の範囲はmatplotlibが適当に決めてくれるのですが、今回の場合、x軸が-2〜10になってしまい、そのままだと左に余白ができてしまいます。

![Kobito.gvp7cv.png](https://qiita-image-store.s3.amazonaws.com/0/139624/cb93d845-b449-60b7-c2a1-ef7c4cc2a920.png "Kobito.gvp7cv.png")

そのため、[`plt.xlim()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.xlim)で範囲を-1〜10に設定しています。

####グリッドの表示

グリッドがあった方が見やすいので、[`plt.grid()`](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.grid)でy軸だけ表示してみました。

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

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