wip_Fakerでリアルっぽいデータセットを作ってみる

# はじめに

pythonでデータを作るとなると大抵はnampyで乱数生成するパターンが多いですが、テストや予測学習などでもっとリアリティあるデータが欲しいと思うことがあります。
電話番号とか、住所とか、日付とか、もっとリアルに感じるデータセットを生成してみたい。

イメージとしてはtoC向けサービスの顧客情報のような構造のデータセットを作りたいです。
| カラム名 | 型 | 補足 |
| --- | --- | --- |
| ID | 文字列 | ユニーク、英字3文字+数値8桁 |
| お名前 | 文字列 | 同姓同名パターンも可（最悪削除) |
| お名前(フリガナ) | 文字列 | 上記のフリガナ |
| サービス加入日 | 日付 | 2010/01/01~2022/06/30 |
| 生年月日 | 日付 | 1910/01/01~2003/12/31 |
| 性別 | 文字列 | 男性、女性、その他 |
| 電話番号 | 文字列 | 0から始まる11～12桁(海外の番号は除く) |
| 住所 | 文字列 | 群や区レベルまであれば十分 |
| メール配信 | 数値(フラグ) | 7:3ぐらいの割合でランダム |

ということで、備忘録代わりに作成してみました。

# どうやって作るの？
Fakerというモジュールをインポートすることで、それっぽいデータセットを生成できるみたいです。試しに生成してみました。

# Fakerを導入する
まずはpipコマンドでFakerをインポートします。

```
pip install Faker

```

# データセットを生成する
Fakerをインポートします。
以下は名前、住所、適当な文章を生成したい場合のコードです。


```python
from faker import Faker

datagenerator = Faker(['ja_JP'])

print('名前: ' + datagenerator.name())
print('\n')
print('住所: ' + datagenerator.address())
print('\n')
print('文章: ' + datagenerator.text())


#名前: 福田 充
#住所: 神奈川県日野市長畑24丁目2番16号 コーポ長畑729
#文章: あなた自身電話キャビネット教会クロステント叔父。分割ないリニア部隊持ってる供給部隊虐待。持つバーゲン戦略的助けて。持っていました溝創傷明らかにする憲法。メニューブラケット舗装デッド見落とす。葉屋根裏保持するコンペリンク。ヒールヘアあなた自身ヒット催眠術憲法。倫理スペルスマッシュ花嫁目的通行料金。今運ニュース陶器。カレッジ花嫁サワー中央自体溝。
```
## ID
これは単純にpandasで作成したデータ量分のデータフレームを作成し、npで連番を振り、文字列を足してあげれば実現できます。

今回は2万行弱で生成しています。
```python
# サンプルデータセット生成時にIDを生成する。
df_sample = pd.DataFrame(
    np.arange(1,19801, dtype='int'),
    columns=["ID"],
)

df_sample
```
![生成結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/d87e30cb-8c03-03bc-8ad4-f0608398bd07.png)

IDに文字列を混ぜ込んだものにしたい場合は、上記を実施したのちに文字列型にし、適当な文字列を挿入すれば問題ないかと思います。
例として、先頭に文字列が3文字付属する11文字のID列であれば以下の通りです。

```python
#0埋め
df_sample["ID"] = df_sample["ID"].astype(str)
df_sample["ID"] = df_sample["ID"].str.zfill(8)
#英字挿入
df_sample["ID"] = df_sample["ID"].map(lambda a: "EXD" + a)
display(df_sample.head())

```
![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/67561cac-04c5-7366-6f24-26ec9648583b.png)
## 性別
単純にif分で処理します。
なお、python3.10以降ではケース文が使った書き方も可能です。
```python
# 性別
sex = list(range(1, 4))
w = [57,40,3]


def generate_sex(value):
    value = value[0]
    if value == 1:
        return "男性"
    elif value == 2:
        return "女性"
    else:
        return "その他"


df_sample["性別"] = [
    generate_sex(random.choices(sex, weights=w)) for i in range(1, 19801)
]
display(df_sample.head(5))
print(df_sample["性別"].value_counts())
```
今回は出現割合を考慮した書き方をしています。`sex` という判定用のリストを用意し、`w`にて出現割合を50:47:3の割合で指定しています。

そして`random.choices()`を利用してwの重みに応じた性別を生成しています。
![生成結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/baf63861-c9ad-9384-95c5-105c0ed5c57c.png)


## お名前
単純に名前を生成するのであれば`datagenerator.name()`メソッドが利用できますが、今回は性別に応じて適切な名前を生成するため以下のように関数を用意しています。

```python
# 性別に対してそれぞれの名前をランダムに振る関数
def generate_name(data):
    data_m = data[data["性別"] == "男性"].copy()
    data_f = data[data["性別"] == "女性"].copy()
    data_a = data[data["性別"] == "その他"].copy()

    data_m["お名前"] = [datagenerator.name_male() for i in range(len(data_m))]
    data_f["お名前"] = [datagenerator.name_female() for i in range(len(data_f))]
    data_a["お名前"] = [datagenerator.name_nonbinary() for i in range(len(data_a))]

    data_all = pd.concat([data_m, data_f, data_a], axis=0)
    return data_all["お名前"]


#名前の生成
df_sample["お名前"] = generate_name(df_sample)
display(df_sample)

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/ab6359e8-2742-7393-b0aa-bedc47c9fe6b.png)

## お名前(フリガナ)
フリガナはpykakasiメソッドをインポートすることで、名前列に対応したフリガナを生成できます。

```python
import pykakasi

datagenerator = Faker(['ja_JP'])
kks = pykakasi.kakasi()
kks.setMode("J","K")
conversion = kks.getConverter()

df_sample["フリガナ"] = df_sample["お名前"].map(lambda a: conversion.do(a))
df_sample.head(5)
```
`kks.setMode("J","K")`は漢字をフリガナに変換するように設定を入れています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/b8bd2d54-98f3-7369-978e-2fb43d938610.png)


## 年齢
12月18日追記
先に生年月日を生成している場合は、基準日から生年月日を差し引き、年齢を求める事は可能です。

また、生年月日を生成していない場合は標準偏差と分散を設定すれば生成可能です。
(後日作成)


## 住所
`datagenerator.address() `を使用します。

```python
# print(datagenerator.address())
df_sample["住所"] = [
    datagenerator.address() for i in range(1, 100)
]
df_sample.head(5)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/a3deb54d-4e9c-b2b8-c9b9-3a6c3a59f86c.png)

## 電話番号
`phone_number()`を利用して電話番号を生成します。

```python
# 電話番号生成
df_sample["電話番号"] = [
    datagenerator.phone_number() for i in range(1, 19801)
]
display(df_sample.head(3))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/4e0981d9-b7eb-90d1-fb27-bb444c829777.png)

## サービス加入日・生年月日
まずはランダムに日付を返す関数を作成します。
```python
# ある期間の中でランダムな日付を生成する関数
import random

def str_time_prop(start, end, format, prop):
    """
    2つのフォーマットされた時刻の範囲の割合で時刻を取得する。
    propはstartの後にどのような割合で時間を取るかを指定する。
    """
    stime = dt.strptime(start, format)
    etime = dt.strptime(end, format)
    ptime = stime + prop * (etime - stime)

    return dt.date(ptime)

def random_date(start, end, prop):
    return str_time_prop(start, end, '%Y/%m/%d', prop)

random_date("1910/01/01", "2022/01/23", random.random())


df_sample["生年月日"] = [
    random_date("1950/01/01", "2003/01/01", random.random())
    for i in range(1, 19801)
]
df_sample["サービス加入日"] = [
    random_date("2010/01/01", "2022/06/30", random.random())
    for i in range(1, 19801)
]
df_sample.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/1435cc64-fc3b-f4ce-e1b5-a14cdd68cd28.png)



## メール配信(フラグ値をある割合で偏らせたい)
これはフラグを生成するリストを用意し、重みを設定することで、意図した割合でフラグ値を生成できます。
今回はメール配信フラグ列を生成してみます。

```python
# メール配信フラグ
mail = list(range(0, 2))
w = [7,3]

def generate_mail(value):
    value = value[0]
    return value

df_sample["メール配信"] = [
    generate_mail(random.choices(mail, weights=w)) for i in range(1, 19801)
]
display(df_sample.head(5))
print(df_sample["メール配信"].value_counts())
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571046/db343056-f72c-1f93-999c-03556cb896a7.png)

# 最後に
あくまで現時点でソースを参考にデータセットを生成してみましたが、
より良いコード例があれば知りたいと思っています。

# 参考
[【Python】日本語をローマ字に変換する](https://rurukblog.com/post/pythonpykakasi/)
[Fakerドキュメント](https://faker.readthedocs.io/en/stable/index.html)