言語処理100本ノック-81(一括置換):複合語からなる国名への対処

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の81本目「複合語からなる国名への対処」の記録です。
今回も[前回の「コーパスの整形」](https://qiita.com/FukuharaYohei/items/56a5a34ab8417baead76)に引き続き前処理系で、主な処理は正規表現を使った文字置換です。ただ、国名リストを作る部分で手作業で面倒なことをしています。そのせいでプログラミング自体は難しくないのですが、時間がかかってしまいました。

# 参考リンク

|リンク|備考|
|:--|:--|
|[081.複合語からなる国名への対処.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/081.%E8%A4%87%E5%90%88%E8%AA%9E%E3%81%8B%E3%82%89%E3%81%AA%E3%82%8B%E5%9B%BD%E5%90%8D%E3%81%B8%E3%81%AE%E5%AF%BE%E5%87%A6.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:81](http://qiita.com/segavvy/items/216888c3549cea3d8e81)|言語処理100本ノックで常にお世話になっています|
|[言語処理100本ノック 2015年版 (80～82)](http://kenichia.hatenablog.com/entry/2016/03/03/170718)|第9章は参考にしました|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

# 課題
## [第9章: ベクトル空間法 (I)](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch9)
>[enwiki-20150112-400-r10-105752.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r10-105752.txt.bz2)は，2015年1月12日時点の英語のWikipedia記事のうち，約400語以上で構成される記事の中から，ランダムに1/10サンプリングした105,752記事のテキストをbzip2形式で圧縮したものである．このテキストをコーパスとして，単語の意味を表すベクトル（分散表現）を学習したい．第9章の前半では，コーパスから作成した単語文脈共起行列に主成分分析を適用し，単語ベクトルを学習する過程を，いくつかの処理に分けて実装する．第9章の後半では，学習で得られた単語ベクトル（300次元）を用い，単語の類似度計算やアナロジー（類推）を行う．
>
>なお，問題83を素直に実装すると，大量（約7GB）の主記憶が必要になる． メモリが不足する場合は，処理を工夫するか，1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)を用いよ．

今回は*「1/100サンプリングのコーパス[enwiki-20150112-400-r100-10576.txt.bz2](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/enwiki-20150112-400-r100-10576.txt.bz2)」*を使っています。

### 81. 複合語からなる国名への対処
> 英語では，複数の語の連接が意味を成すことがある．例えば，アメリカ合衆国は"United States"，イギリスは"United Kingdom"と表現されるが，"United"や"States"，"Kingdom"という単語だけでは，指し示している概念・実体が曖昧である．そこで，コーパス中に含まれる複合語を認識し，複合語を1語として扱うことで，複合語の意味を推定したい．しかしながら，複合語を正確に認定するのは大変むずかしいので，ここでは複合語からなる国名を認定したい．

> インターネット上から国名リストを各自で入手し，80のコーパス中に出現する複合語の国名に関して，スペースをアンダーバーに置換せよ．例えば，"United States"は"United_States"，"Isle of Man"は"Isle_of_Man"になるはずである．

「インターネット上から国名リストを各自で入手し」が面倒です・・・

# 回答
## 国名リスト作成
### 1. 国名リスト取得
適当にググって[「Country codes/names」](http://www.fao.org/countryprofiles/iso3list/en/)なるページが良さそうと思ったのですが、問題文にある"Isle of Man"がありません。
"Isle of Man"はISO 3166-1にあるようなので、[WikipediaのISO 3166-1]
(https://en.wikipedia.org/wiki/ISO_3166-1)からリストを取得しました。
つまり、以下の3つから国名リストを作成しています。

1. [「Country codes/names」](http://www.fao.org/countryprofiles/iso3list/en/)の`
Short name`列
2. [「Country codes/names」](http://www.fao.org/countryprofiles/iso3list/en/)の`
Official name`列
3. [「Wikipedia ISO 3166-1」]
(https://en.wikipedia.org/wiki/ISO_3166-1)の`English short name`列

### 2. 先頭 the の除去
[「Country codes/names」](http://www.fao.org/countryprofiles/iso3list/en/)の`Official name`列から取得した名前には先頭に`the`がついているものがあり、後に邪魔なので除去しました。

### 3. 重複削除
3つから取得しているので国名が一部重複しており、重複削除しました。

### 4. 単一名削除
今回のお題は「複合語からなる国名」であり、単一語の国名は不要です。EXCEL上で`=COUNTIF(A1,"* *")`をして空白を挟んでいた国名を複合語と判断し、EXCEL関数結果が0の国名を除去しました。
※ノック96本目「国名に関するベクトルの抽出」で単一名も必要となるので、EXCELは残しておくと後が楽です。

### 5. マニュアル微調整
一部、そのまま使えないものがあり、マニュアルで微調整しました。手間がかかります・・・
下記が一例です。

|元|変更後|
|:-:|:-:|
|Bolivia (Plurinational State of)|Plurinational State of Bolivia|
|Cocos (Keeling) Islands|Cocos Keeling Islands<br>Cocos Keeling<br>Cocos Islands<br>Keeling Islands|

最終的に247個の国名ができました。

## 回答プログラム [081.複合語からなる国名への対処.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/09.%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E7%A9%BA%E9%96%93%E6%B3%95%20(I)/081.%E8%A4%87%E5%90%88%E8%AA%9E%E3%81%8B%E3%82%89%E3%81%AA%E3%82%8B%E5%9B%BD%E5%90%8D%E3%81%B8%E3%81%AE%E5%AF%BE%E5%87%A6.ipynb)
プログラムです。処理は短くたいしたことがありません(私はスキル不足で2から3時間ほどかけて作っていますが・・・)。
ただ、247個の国名の数だけ全文検索をして置換をするので、処理時間は約12分かかります。[記事「言語処理100本ノック 2015年版 (80～82)」](http://kenichia.hatenablog.com/entry/2016/03/03/170718)のように`sed`コマンド使えばもっと速いのでしょうか？

```python
import re

# ファイルから改行コードを除去し、ソートのために単語数を先頭に付加
with open('./081.countries.txt') as countires:
    country_num = [[len(country.split()), country.rstrip('\n')] for country in countires]

country_num.sort(reverse=True)

with open('./080.corpus.txt') as file_in:
    body = file_in.read()

for i, country in enumerate(country_num):
    print(i, country[1])
    regex = re.compile(country[1], re.IGNORECASE)
    body = regex.sub(country[1].replace(' ', '_'), body)

with open('./081.corpus.txt', mode='w') as file_out:
    file_out.write(body)
```

## 回答解説
国名一覧ファイルを読み込み、単語数をリストに付加して降順ソートしています。これは"United States of America"に対して先に単語数が少ない"United_States"を使って置換し、"United_States of America"としないためです。

```python
# ファイルから改行コードを除去し、ソートのために単語数を先頭に付加
with open('./081.countries.txt') as countires:
    country_num = [[len(country.split()), country.rstrip('\n')] for country in countires]

country_num.sort(reverse=True)
```

正規表現では`re.INGNORECASE`とすることで、大文字小文字を識別しないで置換対象にしています(この揺らぎが役に立つかは確認していないです)。

```python
regex = re.compile(country[1], re.IGNORECASE)
```