PythonでGenderAPIとPykakasiを使って氏名から性別を予測する

##はじめに
氏名から性別を予測したい利用シーンがあるかと思います。
例えば、会員制のサービスで登録フォームで性別を聞くとCVRが下がってしまう、だったら予測で補おう！などと言ったシーンでしょうか。

氏名から性別を予測する方法は、機械学習で分類器を生成し予測する方法や外部APIを用いて予測を行う方法などいくつかあります。
今回はPythonで名前からGenderAPIを用いて性別を予測するアプローチとなります。

[GenderAPI](https://gender-api.com/)はアメリカの企業で、膨大な氏名データから性別予測をこれまで行ってきたようです。
類似するサービスはいくつかありますが、今回はこのGenderAPIを用いて性別予測を行います。

##前準備
###Gender API
まずは、[GenderAPI](https://gender-api.com/)のアカウントを作成しましょう。
作成後、API_KEYを取得します。
無料で使用したい方は、500の氏名までなら無料で使用することができます

###擬似個人情報取得
[PersonalGenerator](https://hogehoge.tk/personal/generator/)を使って、擬似的に個人情報を生成します。
自由に表示する項目を選択できますが、今回は、正解判定も行いたいので、連番,氏名,氏名（カタカナ）,性別を取得します。
今回は30人程度の氏名から性別を予測してみます。
<img width="338" alt="スクリーンショット 2020-08-10 15.27.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689282/b0349bd5-7fc6-49b1-aa66-fd9806c3fe50.png">


###Pykakasi
予測対象の氏名はFirst_nameになり、名前を漢字、カタカナ、ひらがな、ローマ字のいづれで予測させるかは精度に大きく影響します。
結論、海外のサービスだからか、ローマ字に変換し、予測させた方が精度が最も高かったです。（検証過程は割愛します。）

ですので、下の名前からローマ字変換を行う必要があります。
使い方については、開発者のドキュメントが参考になります。[pykakasiの使い方](https://crimnut.hateblo.jp/entry/2018/08/29/180455)
以下の2点のパッケージをインストールします。

```
pip install six semidbm
pip install pykakasi
```

##性別予測
###pythonで性別予測
実際に対象者30人の性別を予測していきます。
大まかな手順は以下の通りです。
1. 対象者のdataframeを用意、全角スペースで分割し名前列を生成
2. Pykakasiインスタンスを生成し、ローマ字変換する設定をし、名前を変換しローマ字列を生成
3. ローマ字リストをGenderAPIに渡し、予測結果を取得
4. 予測結果と元のdataframeをマージ

```gender_estimation.py
import sys
import json
from urllib import request, parse
from urllib.request import urlopen
import pandas as pd
import pykakasi


class GenderEstimation:
    """
    ローマ字変換した氏名から性別を予測
    """
    __GENDER_API_BASE_URL = 'https://gender-api.com/get?'
    __API_KEY = "your api_key"
    def create_estimated_genders_date_frame(self):
        df = pd.DataFrame(self._estimate_gender())
        print('\n{}人の性別予測を完了しました。'.format((len(df))))
        df.columns = [
            'estimated_gender', 'accuracy', 'samples', 'duration'
        ]
        df1 = self._create_member_data_frame()
        estimated_genders_df = pd.merge(df1, df, left_index=True, right_index=True)
        
        return estimated_genders_df
    
    def _estimate_gender(self):
        unique_names = self._convert_first_name_to_romaji()
        genders = []
        print(u'{}人の性別予測を行います'.format(len(unique_names)))
        for name in unique_names:
            res = request.urlopen(self._gender_api_endpoint(params={
                'name': name,
                'country': 'JP',
                'key': self.__API_KEY
            }))
            decoded = res.read().decode('utf-8')
            data = json.loads(decoded)
            genders.append(
                [data['gender'], data['accuracy'], data['samples'], data['duration']])
            
        return genders
    
    def _gender_api_endpoint(self, params):
        return '{base_url}{param_str}'.format(
            base_url=self.__GENDER_API_BASE_URL, param_str=parse.urlencode(params))
    
    def _convert_first_name_to_romaji(self):
        df = self._create_member_data_frame()
        df['first_name_roma'] = df['first_name'].apply(
            lambda x: self._set_kakasi(x))
        
        return df['first_name_roma']
    
    def _set_kakasi(self, x):
        kakasi = pykakasi.kakasi()
        kakasi.setMode('H', 'a')
        kakasi.setMode('K', 'a')
        kakasi.setMode('J', 'a')
        kakasi.setMode('r', 'Hepburn')
        kakasi.setMode('s', False)
        kakasi.setMode('C', False)
        
        return kakasi.getConverter().do(x)

    def _create_member_data_frame(self):
        df = pd.read_csv('personal_infomation.csv').rename(columns={
            '連番':'row_num',
            '氏名':'name',
            '氏名（カタカナ）':'name_katakana',
            '性別':'gender'
        })
        df['first_name']=df.name_katakana.str.split().str[1]
        print(u"{}人の予測対象者を抽出します。".format(len(df)))
        return df
```

###性別予測結果
予測結果のdataframeは以下のようになりました。
GenderAPIの予測に関するレスポンスは以下のような定義になります。

| estimated_gender | accuracy | samples | duration |
|:--|:--|:--|:--|
| 性別予測結果 | 予測の正しさ | 予測に用いたサンプルサイズ | 1callまでの経過時間 |


<img width="784" alt="スクリーンショット 2020-08-10 15.29.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689282/22781e18-861a-cfcb-b136-799952f46ac4.png">

###性別予測精度検証
最後に、性別予測結果の精度について検証しましょう。
以下の表の用に、正解と予測結果とその数をプロットし、マトリックスを生成します
正解率はほぼほぼ100%となりました。
今回の場合、１件だけ本当は女性なのに、男性と予測してしまいました。
やはり「イオリ」など男性とも女性とも取れる名前の予測は困難なようです。

| 正解 | 予測 | num |
|:--|:--|--:|
| male | male | 11 |
| male | female | 0 |
| male | unknown | 0 |
| female | male | 1 |
| female | female | 18 |
| female | unknown | 0 |
| unknown | male | 0 |
| unknown | female | 0 |
| unknown | unknown | 0 |

| 予測/正解 | male | female | unknown | 正解率 |
|:--|--:|--:|--:|:--|
| male | 11 | 0 | 0 | 100.00% |
| female | 1 | 18 | 0 | 94.74% |
| unknown | 0 | 0 | 0 | 0% |