言語処理100本ノック2020 (30~39)

# はじめに
本記事は言語処理100本ノックの解説です。
100本のノックを全てこなした記録をQiitaに残します。
使用言語はPythonです。

https://nlp100.github.io/2020/ja/

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

# 準備
まず、MeCabとunidicを環境に入れます。
```text:コマンド
$ pip install mecab-python3 unidic
$ python -m unidic download
```
次に、neko.txtに対して形態素解析を行い、その結果をneko.txt.mecabに保存します。
```python:コード
import MeCab
import unidic
mecab = MeCab.Tagger()
with open("[PATH]/neko.txt", "r") as f, open("[PATH]/neko.txt.mecab", "w") as f2:
    lines = f.readlines()
    for text in lines:
        result = mecab.parse(text)
        f2.write(result)
```
neko.txt.mecabの先頭の10行は次のようになります。
```text:neko.txt.mecab
一	名詞,数詞,,,,,イチ,一,一,イチ,一,イチ,漢,"","","チ促","基本形","N1","",数,イチ,イチ,イチ,イチ,"2","C3","",563508399972864,2050
EOS
EOS
　	空白,,,,,,,　,　,,　,,記号,"","","","","","",補助,,,,,"","","",6330815488512,23
吾輩	代名詞,,,,,,ワガハイ,我が輩,吾輩,ワガハイ,吾輩,ワガハイ,混,"","","","","","",体,ワガハイ,ワガハイ,ワガハイ,ワガハイ,"0","","",11321954766299648,41189
は	助詞,係助詞,,,,,ハ,は,は,ワ,は,ワ,和,"","","","","","",係助,ハ,ハ,ハ,ハ,"","動詞%F2@0,名詞%F1,形容詞%F2@-1","",8059703733133824,29321
猫	名詞,普通名詞,一般,,,,ネコ,猫,猫,ネコ,猫,ネコ,和,"","","","","","",体,ネコ,ネコ,ネコ,ネコ,"1","C4","",7918141678166528,28806
で	助動詞,,,,助動詞-ダ,連用形-一般,ダ,だ,で,デ,だ,ダ,和,"","","","","","",助動,デ,ダ,デ,ダ,"","名詞%F1","",6299110739157633,22916
ある	動詞,非自立可能,,,五段-ラ行,終止形-一般,アル,有る,ある,アル,ある,アル,和,"","","","","","",用,アル,アル,アル,アル,"1","C3","",334260158472875,1216
。	補助記号,句点,,,,,,。,。,,。,,記号,"","","","","","",補助,,,,,"","","",6880571302400,25
```
# 30. 形態素解析結果の読み込み
>形態素解析結果（neko.txt.mecab）を読み込むプログラムを実装せよ．ただし，各形態素は表層形（surface），基本形（base），品詞（pos），品詞細分類1（pos1）をキーとするマッピング型に格納し，1文を形態素（マッピング型）のリストとして表現せよ．第4章の残りの問題では，ここで作ったプログラムを活用せよ．
```python:コード
with open("[PATH]/neko.txt.mecab", "r") as f:
  general_list = []
  neko_list = []
  lines = f.readlines()
  for text in lines:
    neko_dic = {}
    suf = text.split("\t")
    if suf[0] == "EOS\n":
      continue
    temp = suf[1].split(',')
    neko_dic["surface"] = suf[0]
    if len(temp) <= 7:
      neko_dic["base"] = suf[0]
    else:
      neko_dic["base"] = temp[7]
    neko_dic["pos"] = temp[0]
    neko_dic["pos1"] = temp[1]
    neko_list.append(neko_dic)
    if suf[0]=="。":
      general_list.append(neko_list)
      neko_list = []
general_list #今後の問題で使います
```
```text:出力結果
[[{'surface': '一', 'base': '一', 'pos': '名詞', 'pos1': '数詞'},
  {'surface': '\u3000', 'base': '\u3000', 'pos': '空白', 'pos1': ''},
  {'surface': '吾輩', 'base': '我が輩', 'pos': '代名詞', 'pos1': ''},
  {'surface': 'は', 'base': 'は', 'pos': '助詞', 'pos1': '係助詞'},
  {'surface': '猫', 'base': '猫', 'pos': '名詞', 'pos1': '普通名詞'},
  {'surface': 'で', 'base': 'だ', 'pos': '助動詞', 'pos1': ''},
  {'surface': 'ある', 'base': '有る', 'pos': '動詞', 'pos1': '非自立可能'},
  {'surface': '。', 'base': '。', 'pos': '補助記号', 'pos1': '句点'}],
~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
```

**コメント**
可読性が低いコードになってしまった。一応できているとは思いますが。。。
general_listは今後の問題で使います。

# 31. 動詞
>動詞の表層形をすべて抽出せよ．
```python:コード
suf_list = []
for sentense in general_list:
    for text in sentense:
        if text["pos"] == "動詞":
            suf_list.append(text["surface"])
verb = set(suf_list)
verb
```
```text:出力結果
{'振ら',
 '握ろう',
 '慾張り',
 '咎める',
 '重なっ',
 '飛び上がる',
 '打っ',
 'いで',
 'だまっ',
 '抱え込ん',
~~~~~省略~~~~~
```

**コメント**
general_listは二次元リストなので、二重ループで動詞を探します。

# 32. 動詞の基本形
>動詞の基本形をすべて抽出せよ．


```python:コード
base_list = []
for sentense in general_list:
    for text in sentense:
        if text["pos"] == "動詞":
            base_list.append(text["base"])
base_verb = set(base_list)
base_verb
```
```text:出力結果
{'絡む',
 '刈り込む',
 '抜け出す',
 '咎める',
 '飛び上がる',
 '逸らす',
 '強請る',
 '暴く',
 '直す',
 '飲み干す',
~~~~~省略~~~~~
```

**コメント**
問題31のsurfaceをbaseに変えただけ。

# 33. 「AのB」
>2つの名詞が「の」で連結されている名詞句を抽出せよ．


```python:コード
no_list = []
for sentense in general_list:
    for i in range(len(sentense)):
        if sentense[i]["surface"] == "の" and sentense[i-1]["pos"]=="名詞" and sentense[i+1]["pos"]=="名詞":
            no_list.append(sentense[i - 1]["surface"] + "の" + sentense[i + 1]["surface"])
no_list
```
```text:出力結果
['掌の上',
 '書生の顔',
 'はずの顔',
 '顔の真中',
 '穴の中',
 '書生の掌',
 '掌の裏',
 '藁の上',
 '笹原の中',
 '池の前',
~~~~~省略~~~~~
```
**コメント**
「の」を2つ以上つなげると言いたいことが書きやすくなり、よくやってしまうのですが上司に不自然になるからやめろと言われます。
# 34. 名詞の連接
>名詞の連接（連続して出現する名詞）を最長一致で抽出せよ．


```python:コード
noun_list = []
for sentense in general_list:
    count = 0
    sent = ""
    for i in range(len(sentense)):
        if sentense[i]["pos"] == "名詞":
            count += 1
            sent += sentense[i]["surface"]
        else:
            if count >= 2:
              noun_list.append(sent)
            count = 0
            sent = ""
noun_set = set(noun_list)
noun_set
```
```text:出力結果
{'今世紀',
 '蝉気',
 'ごろつき書生',
 '通り方今',
 '白シャツ',
 'T3cos',
 '二三百',
 '諸君公徳',
 '金田家',
 'シャーレマン',
~~~~~省略~~~~~
```

**コメント**
「吾輩は猫である」の連接は面白いですね。例だと「ごろつき書生」がいいですね～

# 35. 単語の出現頻度
>文章中に出現する単語とその出現頻度を求め，出現頻度の高い順に並べよ．


```python:コード
import collections
word_list = []
for sentense in general_list:
    for i in range(len(sentense)):
        if sentense[i]["pos"] != "補助記号" and sentense[i]["pos"] != "助詞" and sentense[i]["pos"] != "助動詞":
            word_list.append(sentense[i]["surface"])
c = collections.Counter(word_list)
c35 = c.most_common()
c35
```
```text:出力結果
[('し', 2471),
 ('ある', 1726),
 ('ない', 1314),
 ('いる', 1255),
 ('事', 1212),
 ('する', 1055),
 ('もの', 973),
 ('君', 971),
 ('云う', 937),
 ('主人', 934),
```

**コメント**
句読点などの補助記号は当然除外。加えて、助詞と助動詞で上位が占められるのも面白くないのでそれらも除外。各自の好みで除外する品詞を決めてください。
変数「c」は次問で使います。

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

**準備**
matplotlibで日本語を表示できるように以下のパッケージを入れてください。
```text:コマンド
$ pip install japanize-matplotlib
```
**コード**
```python:コード
import matplotlib.pyplot as plt
import japanize_matplotlib
import collections
%matplotlib inline
word_list = []
height_list = []
for i in range(10):
    word_list.append(c.most_common()[:10][i][0]) #cは前問で求める
    height_list.append(c.most_common()[:10][i][1])
plt.bar(x = word_list ,height = height_list)
```
**出力結果**
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579871/c30c2fda-b8ce-6363-10af-aab955a2bfa8.png)

**コメント**
japanize-matplotlibを入れないと日本語が□□□(豆腐)になる。ここで詰まる人が多いでしょう。自分も少々苦労しました。

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

前問と同様にjapanize-matplotlibを入れてください。
```python:コード
import itertools
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline

neko_list = []
for sentense in general_list:
    text37 = []
    Flag = 0
    for text in sentense:
        if "猫" in text["surface"]:
            Flag = 1
            continue
        if text["pos"] != "補助記号" and text["pos"] != "助詞" and text["pos"] != "助動詞":
            text37.append(text["surface"])
    if Flag == 1:
        neko_list.append(text37)
all_neko = list(itertools.chain.from_iterable(neko_list))
c = collections.Counter(all_neko)
word_list = []
height_list = []
for i in range(10):
    word_list.append(c.most_common()[:10][i][0])
    height_list.append(c.most_common()[:10][i][1])
plt.bar(x = word_list, height = height_list)
```
**出力結果**
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579871/3919f01a-a616-da68-b6ca-fbe3521efa6a.png)
**コメント**
本問でも補助記号、助詞、助動詞を除外。「猫」が出てきた文章の単語をリストに入れて集計した。

# 38. ヒストグラム
>単語の出現頻度のヒストグラムを描け．ただし，横軸は出現頻度を表し，1から単語の出現頻度の最大値までの線形目盛とする．縦軸はx軸で示される出現頻度となった単語の異なり数（種類数）である．


```python:コード
import matplotlib.pyplot as plt
word_list = []
for sentense in general_list:
    for text in sentense:
        word_list.append(text["surface"])
data38 = collections.Counter(word_list)
plt.hist(data38.values(), range(1, 30)) #30はグラフへの描画数
```
**出力結果**
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579871/0881971b-ef81-b7b4-b763-5946f273f92c.png)


**コメント**
出現頻度が30未満の単語を図示。全部載せると少数単語の差異が見れないのでこのようにしました。出現頻度1回が結構ありますね。

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

japanize-matplotlibを入れてください。
```python:コード
import matplotlib.pyplot as plt
import japanize_matplotlib
word_list = []
for sentense in general_list:
    for text in sentense:
        word_list.append(text["surface"])
data39 = collections.Counter(word_list)
temp2 = sorted((data39.values()), reverse = True)
plt.plot(temp2)
plt.xlabel('出現頻度順位')
plt.ylabel('出現頻度')
ax = plt.gca()
ax.set_yscale('log')  # メイン: y軸をlogスケールで描く
ax.set_xscale('log')
plt.show()
```
**出力結果**
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579871/56671920-194c-8705-5bae-646ba1b0011b.png)
**コメント**
ジップの法則（ジップのほうそく、Zipf's law）あるいはジフの法則とは、出現頻度が k 番目に大きい要素が、1位のものの頻度と比較して 1/kに比例するという経験則である(wikipedia参照)。
「吾輩は猫である」でも成り立つんですね。言語処理100本ノックは解きながら、自然言語処理に関する知識を付けられて面白い！

# 他章の解答

https://qiita.com/Dukapan100knock/items/d418f2aa37b2b3d16428