分類木の動作確認用サンプルデータセットを生成するpythonスクリプト

# やりたいこと

* 機械学習で使うダミーのデータセット（顧客リストのサンプル）を作成したい。
* 顧客リストはCSVファイルに出力したい。
* 顧客リストの特徴を任意に設定したい。
* 顧客リストの特徴とは「年配の男性会社役員が買っている」「関西の既婚女性が買っている」といったデータの傾向。
* そのためのスクリプトをpythonで実装したい。

# 生成したデータセットの利用例

決定木（分類木）を実装して、そのモデルがデータセットを正しく分類できるか？
を確かめたい場合、モデルに読み込ませるCSVファイルとして利用する。
CSV作成時に特徴を任意に設定できるので、モデルがその特徴を分類できたなら、
モデルが正しく動作していると判断できる。


# 生成したいデータセットの仕様

### カラム

* 性別: 顧客の性別（1:男性 / 0:女性）
* 年齢: 顧客の年齢（数値）
* 婚姻: 顧客の婚姻状況（0:未婚 / 1:既婚）
* 都道府県: 顧客が住んでいる都道府県（47都道府県のいずれか）
* 職業: 顧客の職業（1～8のいずれか。項目は後述）
* 趣味: 顧客の趣味（1～7のいずれか。項目は後述）
* y: 目的変数。たとえば「ある商品を購入したか、していないか」（'yes:購入した' / 'no:購入していない'）

### 職業

1:会社員
2:会社役員
3:自営業
4:フリーランス
5:学生
6:専業主婦
7:無職
8:その他

### 趣味

* 1:読書
* 2:映画
* 3:音楽
* 4:アウトドア
* 5:自動車
* 6:オートバイ
* 7:テレビゲーム

### データの例

たとえば下記のようなデータセットを生成したい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/978a7e0c-c172-6f20-a1d0-9f245927f643.png)


この例では、
1行目の顧客は、
男性で、55歳で、既婚者で、兵庫県に住んでいて、会社員で、オートバイが趣味であり、商品を購入した顧客である。

2行目の顧客は、
女性で、31歳で、未婚で、和歌山県に住んでいて、会社員で、自動車が趣味であり、商品を購入していない顧客である。

左右の表は、いずれも同じデータセットである。
左の表は、各値を日本語で表記したものである。右の表は、各値を数値で表記したものである。


このような顧客リストがあるとして、
「商品を買った人の特徴（傾向）を知りたい」というケース。
男女別では、どちらが多く買っているのか？
住んでいる地域によって違いはあるのか？
年齢や職業は関係あるのか？
など「どんな人が買ったのか？」の傾向が分かれば、マーケティングの施策に役立つ。
たとえば、新規でダイレクトメールを打つ場合、反応率を最大化するためには、
送付先の地域を限定すべきか？年齢や性別で絞るべきか？などが判断しやすくなる。

# 実装

```python
import random
import csv

##################################################
# マスタデータを定義する。
##################################################
def getMstHobby():
    mst = []
    mst.append('趣味')
    mst.append('読書')
    mst.append('映画')
    mst.append('音楽')
    mst.append('アウトドア')
    mst.append('自動車')
    mst.append('オートバイ')
    mst.append('テレビゲーム')
    return mst

def getMstJob():
    mst = []
    mst.append('職業')
    mst.append('会社員')
    mst.append('会社役員')
    mst.append('自営業')
    mst.append('フリーランス')
    mst.append('学生')
    mst.append('専業主婦')
    mst.append('無職')
    mst.append('その他')
    return mst

def getMstGender():
    mst = []
    mst.append('女性')
    mst.append('男性')
    return mst

def getMstMarriage():
    mst = []
    mst.append('未婚')
    mst.append('既婚')
    return mst

def getMstPref():
    mst = []
    mst.append('都道府県')
    mst.append('北海道')
    mst.append('青森県')
    mst.append('岩手県')
    mst.append('宮城県')
    mst.append('秋田県')
    mst.append('山形県')
    mst.append('福島県')
    mst.append('茨城県')
    mst.append('栃木県')
    mst.append('群馬県')
    mst.append('埼玉県')
    mst.append('千葉県')
    mst.append('東京都')
    mst.append('神奈川県')
    mst.append('新潟県')
    mst.append('富山県')
    mst.append('石川県')
    mst.append('福井県')
    mst.append('山梨県')
    mst.append('長野県')
    mst.append('岐阜県')
    mst.append('静岡県')
    mst.append('愛知県')
    mst.append('三重県')
    mst.append('滋賀県')
    mst.append('京都府')
    mst.append('大阪府')
    mst.append('兵庫県')
    mst.append('奈良県')
    mst.append('和歌山県')
    mst.append('鳥取県')
    mst.append('島根県')
    mst.append('岡山県')
    mst.append('広島県')
    mst.append('山口県')
    mst.append('徳島県')
    mst.append('香川県')
    mst.append('愛媛県')
    mst.append('高知県')
    mst.append('福岡県')
    mst.append('佐賀県')
    mst.append('長崎県')
    mst.append('熊本県')
    mst.append('大分県')
    mst.append('宮崎県')
    mst.append('鹿児島県')
    mst.append('沖縄県')
    return mst

##################################################
# リストを受け取りcsvファイルに出力する。
##################################################
def outputCsv(fileName, listData):
    f = open(fileName, 'w')
    writer = csv.writer(f, lineterminator='\n')
    writer.writerows(listData)
    f.close()    

##################################################
# データセットの特徴を設定する。
##################################################
def setFeatures(gender, age, marriage, pref, job, hobby):

    if gender == '男性' and age >= 40 and job == '会社員':
        return 'yes'
   
    if gender == '女性' and age <= 29:
        return 'yes'
   
    if marriage == '既婚' and job == '会社員':
        return 'yes'

    return 'no'

##################################################
# 処理開始。
##################################################

mst_gender = getMstGender()
mst_pref = getMstPref()
mst_job = getMstJob()
mst_hobby = getMstHobby()
mst_marriage = getMstMarriage()

users_label = []
users_int = []

# CSVファイルのヘッダ。
csv_header_en = ['gender', 'age', 'marriage', 'pref', 'job', 'hobby', 'y']
csv_header_jp = ['性別', '年齢', '婚姻', '都道府県', '職業', '趣味', 'y']
users_int.append(csv_header_en)
users_label.append(csv_header_jp)

# 作成するCSVのレコード件数。
recordNum = 50000

# CSVレコードを生成する。
for num in range(recordNum):
    ageInt = random.randint(20, 80) # 最少年齢と最大年齢
    prefInt = random.randint(1, 47) # 都道府県番号。北海道なら1, 東京都なら13
    prefLabel = mst_pref[prefInt]
    genderInt = random.randint(0, 1) # 0:女性, 1:男性
    genderLabel = mst_gender[genderInt]
    marriageInt = random.randint(0, 1) # 0:未婚, 1:既婚
    marriageLabel = mst_marriage[marriageInt] 
    jobInt = random.randint(1, 7) # 「その他」を除く職業
    jobLabel = mst_job[jobInt]
    hobbyInt = random.randint(1, 7)
    hobbyLabel = mst_hobby[hobbyInt]
    y = setFeatures(genderLabel, ageInt, marriageLabel, prefLabel, jobLabel, hobbyLabel)
    dataInt = [genderInt, ageInt, marriageInt, prefInt, jobInt, hobbyInt, y]
    dataLabel = [genderLabel, ageInt, marriageLabel, prefLabel, jobLabel, hobbyLabel, y]
    users_int.append(dataInt)
    users_label.append(dataLabel)

# CSVファイルに出力する。
outputCsv('out_int.csv', users_int)
outputCsv('out_label.csv', users_label)


```

# 解説

まずランダムに顧客を生成して、
目的変数y（買ったか、買わなかったか）は、生成された各カラムの値に応じて、
条件文で分岐させることで「どんな顧客が買ったのか？」の特徴を設定している。

関数`setFeatures`

で特徴を設定している。
この関数を自由に修正すれば、データセットに任意の傾向を持たせることができる。

この実装例では、

* 40歳以上の男性会社員が買った。
* 29歳以下の女性（職業問わず）が買った。
* 既婚の会社員が買った。

という特徴を持たせている。
現実のデータセットは、このような綺麗な分類にはならないが、
上記の条件に合致しなくても、ランダムにyesを返す処理を加えれば、
「条件に合致していないけど買った顧客」を混在させるこも可能。


# 出力結果の例


### out_int.csv

```csv
gender,age,marriage,pref,job,hobby,y
0,22,1,45,7,7,yes
1,20,1,4,3,6,no
0,64,1,18,6,7,no
0,44,0,29,1,4,no
0,69,0,18,2,5,no
0,49,1,20,7,1,no
0,40,1,41,7,4,no
```

### out_label.csv

```csv
性別,年齢,婚姻,都道府県,職業,趣味,y
女性,22,既婚,宮崎県,無職,テレビゲーム,yes
男性,20,既婚,宮城県,自営業,オートバイ,no
女性,64,既婚,福井県,専業主婦,テレビゲーム,no
女性,44,未婚,奈良県,会社員,アウトドア,no
女性,69,未婚,福井県,会社役員,自動車,no
女性,49,既婚,長野県,無職,読書,no
女性,40,既婚,佐賀県,無職,アウトドア,no
```

# ためしに決定木で分類させてみると

```python
import pandas as pd
import numpy as np
import pydotplus
from sklearn.tree import export_graphviz
from sklearn.tree import DecisionTreeClassifier as DT
from IPython.display import Image
train = pd.read_csv("csv/out_label.csv",delimiter=",")
y = train["y"]
trainx = train.iloc[:,0:6]
trainxd = pd.get_dummies(trainx)
clf3 = DT(max_depth=20, min_samples_leaf=500)
clf3.fit(trainxd,y)
export_graphviz(clf3, out_file="tree_clf3.dot", feature_names=trainxd.columns, class_names=["0","1"], filled=True, rounded=True)
graph = pydotplus.graphviz.graph_from_dot_file('tree_clf3.dot')
Image(graph.create_png())
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/ab1c4c31-8240-2d2d-3eb0-d01711a3a0bf.png)

# まとめ

モデルの動作を確認する際は、
モデルに読み込ませるデータセットの特徴（答え）をあらかじめ把握しておき、
「その答えの通りに分類できたか？」を確認すると分かりやすい。
分類できていれば、そのモデルの実装は正しいと判断できるし、
分類できていなければ、実装が悪いのか、パラメータが適切ではないのか、など原因を探っていく。