【Income Dataset】ロジスティック回帰を用いた二値分類



## 目次

[1. はじめに](#1-はじめに)
[2. 今回の目標](#2-今回の目標)
[3. データセットと実行環境](#3-データセットと実行環境)
[4. 実行](#4-実行)
　　[4-1. データの準備](#4-1-データの準備)
　　[4-2. 分析の進め方を確認](#4-2-分析の進め方を確認)
　　[4-3. データの特徴を可視化](#4-3-データの特徴を可視化)
　　[4-4. データの整備](#4-4-データの整備)
　　[4-5. データを用いた分析](#4-5-データを用いた分析)
　　[4-6. データの修正](#4-6-データの修正)
　　[4-7. 最終的なコード](#4-7-最終的なコード)
[5. まとめ](#5-まとめ)
[6. 参考記事](#6-参考記事)

## 1. はじめに
### 自己紹介
はじめまして！
私はとある製造系ベンチャーで、製造支援や受発注管理などを行っている、26歳の若手社員です。

普段の業務では大量のExcelデータを処理/分析し、工場の方々が少しでも安定的に生産できるように尽力しています。
例えばどんなことをしているかというと、、、

<ul>
    <li>見込み生産数の算出
        <ul>
            <li>何でも受注してすぐに作れるわけではなく、事前に材料購入をしたり、生産計画を組む必要があります。
        </ul>
    <li>原価計算
        <ul>
            <li>製造にかかるコストを最適に算出できるよう、材料/設備/図面のデータを整えています。
        </ul>
</ul>

### 機械学習のきっかけ
仕事上では、pivotやグラフを使って、実績推移と経験則で分析/予測をします。
ただ、あくまで人が（私が）予測をしている訳です。

そんな時にふと、機械学習を使えばより高精度な予測が出せるのでは、と思ったことがきっかけでした。
上手くいけば、私の分析業務自体もずいぶん楽になるような気がしました。

### Aidemy（データ分析３か月コース）を受講
とはいえ、これまでまったくプログラミングに触れてこなかった私が、
独学で習得するのは不可能と考えました。

そこで、国から「第四次産業革命スキル習得講座」に認定されているAidemyを受講することにしました。
期間内に受講し終わると、受講料の約70％が還付されることが特徴です。

### 受講を通しての学び
受講前は、機械学習はかなり限定的な用途でしか使えないと思っていました。
（うまくいけば業務で使えるな…くらいの気持ちです）

**しかし、データ分析をきっかけに自然言語処理、画像分析、深層学習などに触れる内に、未知のプロダクト開発や社会課題解決にも使える、と理解しました。**

今では業務の傍ら、データ分析で教育系の社会課題を解こうと頭をひねっています。
近いうちにデータサイエンティストとして一歩を踏み出せたらと考えています。

## 2. 今回の目標
今回は学習の総復習をかねて、ロジスティック回帰を用いた二値分類の実行を行います。

私と同じく、まったく初めてデータ分析に触れる方が、この記事を読めば一通り実行までできることが今回の目標です。

## 3. データセットと実行環境
Kaggleのデータセットおよび環境を使用します。

URL : https://www.kaggle.com/datasets/mastmustu/income

ファイル名は「Income Dataset（年収データ）」です。
学歴/雇用形態/配偶者の有無など、給与が5万ドル以上かどうかを予測するためのデータセットです。

## 4. 実行
### 4-1. データの準備
データセットを読み込むためのコードを実行します。

#### <font color="blue">✓</font> コード
```python
# 使用するライブラリをインポート
import numpy as np  # データ処理用ライブラリ
import pandas as pd # 数列計算用ライブラリ
from pandas import DataFrame # pandasが用意するデータ構造

# 訓練データの読み込み
train_df = pd.read_csv("/kaggle/input/income/train.csv")

# 訓練データの表示
train_df
```

#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/cc5a9a1b-bb56-f8a7-b0b5-cac840288185.png)
43,957人の調査データと、15列の年収に関連するデータがあることが確認できました。

欠損値やデータの型も確認します。
#### <font color="blue">✓</font> コード
```python
# データ内容を確認
train_df.info()
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/56f2ba58-5fac-d838-3544-f397b7b3a9ab.png)
後述しますが、欠損値のあるカラムは今回使用しません。

※また、コンペのsubmitは行わないので、テストデータも使用しません。

### 4-2. 分析の進め方を確認
今回、学歴/配偶者の関係/所有資産の特徴から、給与が5万ドル以上かを予測するモデルを作成したいと思います。

訓練データの15列目「income_>50K」に1が立つ人の特徴を、1~14列の特定のカラム（列）を用いてモデル構築します。
使用するカラムは以下です。

|カラム名|解説|データ型|
| --- | --- |--- |
|age|年齢|int64|
|education|最終学歴|object|
|marital-status|婚姻状況|object|
|gender|性別|object|
|capital-gain|資本利益|int64|
|capital-loss|資本損益|int64|
|income_>50K|給与が$50K以上|int64|

### 4-3. データの特徴を可視化
分析に入る前に、データを可視化し特徴を調べてみたいと思います。

#### <font color="blue">✓</font> コード
```python
# 使用するライブラリをインポート
# どちらもデータを可視化するライブラリです
import matplotlib.pyplot as plt
import seaborn as sns 
```

まずは年齢と給与の関係性です。
30代から比率が増え、40~50代でもっとも上昇することが分かります。

#### <font color="blue">✓</font> コード
```python
# Ageについて可視化
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "age", bins=30, kde=False)
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/bba90411-3d32-6cac-98b7-579605fd076b.png)

次に、最終学歴と給与の関係性です。
Doctorate(博士),Masters(修士),Prof-school(専門),bachelor(学士)の比率が高いです。
教育レベルと給与には相関性があると推測できます。

#### <font color="blue">✓</font> コード
```python
# educationについて可視化。
plt.figure(figsize=(20,10))
sns.countplot(x="education", hue="income_>50K", data=train_df) 
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/06b184f9-7cbd-2e4d-3edd-e09fedb494af.png)

では、婚姻状況はどうでしょうか。
未婚者や離婚者よりも、婚姻関係にある人の方が給与が5万ドル以上の比率が高いです。

#### <font color="blue">✓</font> コード
```python
# marital-statusについて可視化。
plt.figure(figsize=(20,10))
sns.countplot(x="marital-status", hue="income_>50K", data=train_df) 
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/1cfad05b-ca5f-5dd3-bb59-52ff6c8a4aed.png)

性別についても確認します。
男性の方が比率が高いようです。
#### <font color="blue">✓</font> コード
```python
# genderについて可視化。
sns.countplot(x="gender", hue="income_>50K", data=train_df) 
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/2c458427-21d0-e1d9-7f98-3750c8ae6fcd.png)

次はcapital-gain（資本利益）です。
一定数の人が、資本利益のみで5万ドル以上の収入があるようです。

#### <font color="blue">✓</font> コード
```python
# capital-gainについて可視化。
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "capital-gain", bins=30, kde=False)
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/7fad5322-cbe3-6219-381b-540a799316af.png)

最後に、capital-loss（資本損益）を確認します。
ほとんど人が0ですが、一定の人が2,000ドル周辺で損失を出しているようです。

#### <font color="blue">✓</font> コード
```python
# capital-lossについて可視化。
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "capital-loss", bins=30, kde=False)
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/44c93b4b-f1d9-7ce7-3d6f-fc568f4af74a.png)

### 4-4. データの整備
ここまでで各カラムの特徴と、給与との相関性を確認しました。
モデル構築にあたり、データの整備が必要です。

#### ① 異常値の処理（ビン分割）
capital-gainとcapital-lossは、ほとんどの人が0のため、このまま分析に使用するとデータに悪影響します。
そこで、カテゴリーで分けることにします。

#### <font color="blue">✓</font> コード
```python
# capital-gainは3つのカテゴリーにビン分割しラベリングします。
# capital-gainが、0:ない、less than 50K:5万ドル未満、over 50K:5万ドル以上
pd.cut(train_df['capital-gain'],[-1,0,50000,99999],
       labels = ['0','less than 50K', 'over 50K'])

# capital-gainをビン分割したカラムを追加します。
train_df['capital-gain_bins'] = pd.cut(train_df['capital-gain'],[-1,0,50000,99999],
                                      labels = ['0','less than 50K', 'over 50K'])

# capital-lossは2つのカテゴリーにビン分割しラベリングします。
# capital-lossが、not:ない、have:ある
pd.cut(train_df['capital-loss'], [-1,0,5000],
       labels = ['not','have'])

# capital-lossをビン分割したカラムを追加します。
train_df['capital-loss_bins'] = pd.cut(train_df['capital-loss'], [-1,0,5000],
                                      labels = ['not','have'])

# データを確認
train_df.head()
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/5511b4b8-d7a4-39ac-a7d2-920d720b8992.png)
右にカラムが追加されたことが確認できました。

#### ② One-Hot Encoding
先ほど追加した2カラムと、使用予定のeducation,marital-status,genderはカテゴリカル変数です。
カテゴリカル変数に対しては、One-Hot Encodingを施して、モデルが扱えるよう変換します。

#### <font color="blue">✓</font> コード
```python
# capital-gain_bins、capital-loss_binsをOne-Hot_Encodingで変換
train_df = pd.get_dummies(train_df, columns= ["capital-gain_bins",
                                              "capital-loss_bins",
                                             ])

# education,marital-status,genderをOne-Hot_Encodingで変換
train_df = pd.get_dummies(train_df, columns= ["education",
                                        "marital-status",
                                        "gender"
                                        ])
# データを確認
train_df.head()
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/e8a59102-374d-998a-15dd-d1009ecfa3f2.png)
右にカラムが追加されたことが確認できました。

### 4-5. データを用いた分析
特徴量作成が完了したので、訓練データをさらに訓練データと検証データに分けます。
そして、実際にロジスティック回帰を行います。

#### <font color="blue">✓</font> コード
```python
# 使用するライブラリをインポート
from sklearn.model_selection import train_test_split # 訓練データと検証データを作成

# 訓練データのincome_>50Kをtargetにする
target = train_df['income_>50K']

# 今回学習に用いないカラムを削除
drop_col = [
    'workclass',
    'fnlwgt',
    'educational-num',
    'occupation',
    'race',
    'relationship',
    'capital-gain',
    'capital-loss',
    'hours-per-week',
    'native-country',
    'income_>50K'
    ]

train = train_df.drop(drop_col, axis=1)

# 訓練用と検証用に分ける
X_train ,X_val ,y_train ,y_val = train_test_split(
    train, target, 
    test_size=0.2, shuffle=True, random_state=0
    )

# 使用するライブラリをインポート
# 予測モデルを構築。今回はロジスティック回帰を用います。
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# モデルを定義し学習
model = LogisticRegression() 
model.fit(X_train, y_train)

# 訓練データに対しての予測を行い、正答率を算出
y_pred = model.predict(X_train)
print(accuracy_score(y_train, y_pred))
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/25b2aa08-a53e-2f67-d258-2942dfe51d1c.png)

### 4-6. データの修正
正答率は出せましたが、エラーが表記されました。
意味は「収束に失敗したため、反復回数を増やすかデータをスケーリングしてください」とのことです。

**解決方法①**
エラーの原因は、モデルが収束していないためでした。
そこで、反復回数（max_iter）を明示的に設定して、反復回数を増やします。

#### <font color="blue">✓</font> コード
```python
# モデルを定義し学習。今回は反復回数を1000とする。
model = LogisticRegression(max_iter = 1000) 
model.fit(X_train, y_train)

# 訓練データに対しての予測を行い、正答率を算出
y_pred = model.predict(X_train)
print(accuracy_score(y_train, y_pred))
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/8796748d-4122-8df4-cfcd-89678cddd1e3.png)
警告が消えました。
max_iterの初期値は100ですが、今回は1,000にしました。

======
**解決方法②**
次に別のsolverを指定します。
#### <font color="blue">✓</font> コード
```python
# モデルを定義し学習
model = LogisticRegression(solver='liblinear') 
model.fit(X_train, y_train)

# 訓練データに対しての予測を行い、正答率を算出
y_pred = model.predict(X_train)
print(accuracy_score(y_train, y_pred))

y_fix = model.predict(X_val)
print(accuracy_score(y_val, y_fix))
```
#### <font color="blue">✓</font> 出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3179561/02110841-467c-cb49-a0b2-1d18ee5d8a8f.png)
警告が消えました。
solverの初期設定はlbfgsですが、今回はliblinearにしました。

なお、他にもnewton-cgやsagやsagaなどと色々あるようです。
※sagとsagaは試してみた結果、収束しませんでした。

### 4-7. 最終的なコード

<details><summary>すべてのコードをまとめたものはこちらです</summary>
<div>

```python
# 使用するライブラリをインポート
import numpy as np  
import pandas as pd 
from pandas import DataFrame 
import matplotlib.pyplot as plt
import seaborn as sns 

# 訓練データの読み込み
train_df = pd.read_csv("/kaggle/input/income/train.csv")

# Ageについて可視化
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "age", bins=30, kde=False)

# educationについて可視化
plt.figure(figsize=(20,10))
sns.countplot(x="education", hue="income_>50K", data=train_df) 

# marital-statusについて可視化
plt.figure(figsize=(20,10))
sns.countplot(x="marital-status", hue="income_>50K", data=train_df) 

# genderについて可視化
sns.countplot(x="gender", hue="income_>50K", data=train_df) 

# capital-gainについて可視化
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "capital-gain", bins=30, kde=False)

# capital-lossについて可視化
fig = sns.FacetGrid(train_df, col="income_>50K", hue="income_>50K", height=4)
fig.map(sns.histplot, "capital-loss", bins=30, kde=False)

# capital-gainとcapital-lossをビン分割したカラムを追加
train_df['capital-gain_bins'] = pd.cut(train_df['capital-gain'],[-1,0,50000,99999],
                                      labels = ['0','less than 50K', 'over 50K'])

train_df['capital-loss_bins'] = pd.cut(train_df['capital-loss'], [-1,0,5000],
                                      labels = ['not','have'])

# カテゴリカル関数をOne-Hot_Encodingで変換
train_df = pd.get_dummies(train_df, columns= ["capital-gain_bins",
                                              "capital-loss_bins"
                                             ])

train_df = pd.get_dummies(train_df, columns= ["education",
                                              "marital-status",
                                              "gender"
                                             ])

# 使用するライブラリをインポート
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 訓練データのincome_>50Kをtargetにする
target = train_df['income_>50K']

# 今回学習に用いないカラムを削除
drop_col = [
    'workclass',
    'fnlwgt',
    'educational-num',
    'occupation',
    'race',
    'relationship',
    'capital-gain',
    'capital-loss',
    'hours-per-week',
    'native-country',
    'income_>50K'
    ]

train = train_df.drop(drop_col, axis=1)

# 訓練用と検証用に分ける
X_train ,X_val ,y_train ,y_val = train_test_split(
    train, target, 
    test_size=0.2, shuffle=True, random_state=0
    )

# モデルを定義し学習
# 反復回数を増やしてエラーを回避します
model = LogisticRegression(max_iter = 1000) 
model.fit(X_train, y_train)

# 訓練データに対しての予測を行い、正答率を算出
y_pred = model.predict(X_train)
print(accuracy_score(y_train, y_pred))
```
</div>
</details>

## 5. まとめ

いかがでしたでしょうか？
基礎的な内容ですが、ロジスティック回帰で実行までできました。

ここまで自力で出来るようになったのもAidemyの教材のおかげです。

もしこの記事でデータ分析に興味を持っていただけたなら、
ぜひ受講をおすすめします！

## 6. 参考記事

▽同じデータセットを使用されている記事です▽

[【文系出身初心者】年収について結婚・独身の関係性を予測してみた](https://qiita.com/TGI/items/1f6bf98f66a94a2e3054)

▽Qiitaのアンカーリンクがうまく使えなかった時、参考にした記事です▽

[Qiitaの記事でページ内リンクを使う](https://qiita.com/5zm/items/044ae86993c8e693b7e9)