【初心者向け機械学習】コピペと最小限のコード修正で行う数値予測(回帰分析)


# 最小限の努力で機械学習を試したい初心者へ

この記事は機械学習を全くやっていないビジネスマンがはじめての機械学習を行って分析と数値予測の結果を得るために、他のどの情報源よりも丁寧に導きを行う記事を目指します。
 
そのため基本的にこの記事だけで分析と予測が完了するようにしますが、
かなり長い記事、コードになってしまいました。ご容赦ください。

尚、いいねをいただけたら数値予測の回帰だけでなく、分類問題のコードも公開します。
 

**「データを流し込み最小限のアレンジで機械学習の数値予測がしたい」**
**「数値予測に影響を与える特徴量名・変数名を定量的に比較したい」**
 
これはデータ分析を行うビジネスマンの共通の願いではありますが、機械学習はファーストステップの心理ハードルが高く、前職の某広告代理店では機械学習の導入は意外と進んでおりませんでした。

そして、結果的に極めて簡単な集計分析でしか資料作りが行われていませんでした。
（資金力があれば、DataRobotや外部発注を進めているのでしょうが・・）
 


とはいえ、分析を生業にしている方ならば、

**・初心者でも機械学習を使って数値の予測（回帰分析）を試してみたい。**
**・機械学習でどれだけの精度の分析が可能なのか簡単に確認してみたい。**
**・どの特徴量・変数が結果に影響を与えているのか、定量的に比較したい。**
 
と思うはずです。
 
この希望に応えるべく、CSVデータ(Excelデータから簡単に作れます)と最小限のコード加工を行うだけで未知の数値予測（回帰予測）を行い、かつ各々の特徴量（変数）の予測への影響度を定量比較したデータファイルを作成するコードを用意しました。
 
関数の作成等をほとんどせず、上から順にコードへ簡単な修正を行えば、初心者でも最小限の努力で機械学習が完了・理解できる作りになっています。

# 機械学習モデルの内容
機械学習に用いているモデルは、前処理（事前のデータ加工）の量を抑えられる決定木、ランダムフォレスト、勾配ブースティング木という決定木系の３モデルです。
 
標準化などの前処理が必要ないモデルであり、
精度や説明性のバランスの良いモデルだからです。
モデルの詳細説明は割愛します。
 

分析内容は以下の通りです。
 

**①決定木、ランダムフォレスト、勾配ブースティング木の決定木系の３モデルを活用
②予測に影響の大きな特徴量はランダムフォレストモデルで検証
③カテゴリデータのワンホットエンコーディングを実施
④訓練データ、予測したいデータの片方にしかない特徴量・変数の差分修正を実施
⑤標準化は決定木系モデルなので行なわない
⑥次元削減は行わない**
 

特に④番は自前で用意すると手間のかかりますが、実務で発生しやすい前処理であり、少しのコード修正で自動的に対応できるようにしました。

# 必要な前提要件
 
**・学習させたい事例のCSVデータがある**
１行目にヘッダーとして変数名・特徴量名が並び、A列に予測したい変数（目的変数）の名前とその実測値、B列以降に予測に必要な変数（目的変数）の名前と数字・カテゴリ情報が並ぶような学習用CSVデータがある。
 
→エクセルデータはCSVデータに変換してください。
 
 
**・予測したい事例のCSVデータがある**
学習用エクセルデータと同じく、１行目にヘッダーとして変数名・特徴量名が並び、B列以降に予測に必要な変数（目的変数）の名前と数字・カテゴリ情報が並ぶが、A列には予測したい変数（目的変数）の名前と空欄が入っているような学習用CSVデータがある。
 
→エクセルデータはCSVデータに変換してください。
 
**・Pythonの使用環境がある**
JupyterNotebookやGoogleコラボなどのPython環境とPandas、scikit-learnを使える環境がある

→AnacondaやWinPythonの活用などで楽にできます。環境構築はある種手間がかかりますが、以下のようなサイトを活用してpythonというコードが動く状態にまでしてください。
https://ai-inter1.com/python-install/
 
 
# 具体的な分析ステップ
**①学習用データファイル、予測用データファイルををCSVに変換し、Pythonの分析ファイルと同じフォルダに入れる**

**②学習用コードをJupyterNotebookにコピペして学習用データによる訓練を実行し、各特徴量・変数の予測への影響度データを CSVファイルで吐き出す。**
 
**③予測用コードをJupyterNotebookにコピペして予測用データによる予測を実行し、算出した数値をCSVファイルで吐き出す。**

それぞれに分けてコードを記載します。
コードのコピーを自分のPython環境に貼りながら、
"#説明文"の部分を見て修正が必要な箇所を自分で書き換えてください。

書き換えが終わったら実行をするだけで分析結果ファイルと数値予測結果ファイルが得られるようになっています。


# ステップ①学習データによる訓練と特徴量・変数の重要度定量比較
#### 学習データの取得と確認

```python

import pandas as pd

# 学習用データを読み込みます
# 'train_csv'を'学習用データファイルの名前.csv'に書き換えてください
df_origin = pd.read_csv('train_csv',encoding='cp932')

#表示して適切に読み込みが終わっているか確認
display("オリジナルデータ",df_origin.head())
```


#### 予測に使う特徴量・変数と予測したいデータをそれぞれX,yに格納

```python
# 1列目(B列)から最終列以前を予測に使う特徴量・変数Xに設定
X  = df_origin.iloc[:, 1:]

# 0列（A列）を正解データyに設定
y  = df_origin.iloc[:, 0]
```




#### ワンホットエンコーディングが必要なカテゴリカルデータを探す

```python
#整数/小数/テキスト(オブジェクト)/真偽型の各データ型を持つ特徴量・変数名を格納
X_int_list = X.select_dtypes(include=int).columns
X_float_list = X.select_dtypes(include=float).columns
X_object_list = X.select_dtypes(include=object).columns
X_bool_list = X.select_dtypes(include=bool).columns

#それぞれに格納した特徴量・変数名を表示
print("整数型(int型)の特徴量・変数名：", X_int_list)
print("少数型(float型)の特徴量・変数名：", X_float_list)
print("カテゴリ型(object型)の特徴量・変数名：", X_object_list)
print("真偽(bool型)の特徴量・変数名：", X_bool_list)
```



#### ワンホットエンコーディングを実施
```python
# カテゴリカルデータが入っている特徴量・変数の名前をコピペしてをリストに入れる
# プリントされた特徴量・変数名データからカテゴリ変数の名前を全てコピペして
# "['変数A', '変数B', '変数C']"を書き換えて下さい
X_category_column_list = ['変数A', '変数B', '変数C']

# ワンホットエンコーディングを行う特徴量・変数名のリストを表示
print("ワンホットエンコーディングを行う特徴量・変数名のリスト",X_category_column_list)

# ワンホットエンコーディングを行う特徴量・変数名のリストを作成
ohe_columns = X_category_column_list

# ワンホットエンコーディングを実施
X_ohe = pd.get_dummies(X, dummy_na=True, columns=ohe_columns)
display("ワンホットエンコーディングを行なった結果のXデータ",X_ohe.head())
```



#### 連続値データ（カテゴリカルデータ以外）の欠損値補完を行う
```python
from sklearn.preprocessing import Imputer

# インピュータークラスのインスタンス作成
# カテゴリカルデータの欠損値はワンホットエンコーディングで処理したが、連続値データの欠損値NaNを保管する
# 平均値で置き換える場合 strategy='mean'
# 中央値で置き換える場合 strategy='median'
# 最頻値で置き換える場合 strategy='most_frequent'
imp_mean = Imputer(missing_values='NaN', strategy='mean', axis=0)
imp_median = Imputer(missing_values='NaN', strategy='median', axis=0)
imp_most_frequent = Imputer(missing_values='NaN', strategy='most_frequent', axis=0)


# 平均値・中央値・最頻値それぞれで欠損値補完をする指標をどれにするかリスト化します
# "['変数', '変数', '変数']"の中身を書き換えて下さい
# 空のリストになっても問題ありません
imp_mean_columns_list = ['変数名1', '変数名6']
imp_median_columns_list = ['変数名2','変数名3', '変数名4', '変数名5']
imp_most_frequent_columns_list = []


#欠損処理を行う元となるデータフレームを作成
X_ohe_imp = X_ohe.copy()


# 指定した特徴量・変数名におけるX_trainの各特徴量の平均値を学習、穴埋めを行う
if len(imp_mean_columns_list) > 0:
    imp_mean.fit(X_ohe.loc[:,imp_mean_columns_list])
    X_ohe_imp.loc[:,imp_mean_columns_list] = imp_mean.transform(X_ohe.loc[:,imp_mean_columns_list])
# 指定した特徴量・変数名におけるX_trainの各特徴量の中央値を学習、穴埋めを行う
if len(imp_median_columns_list) > 0:
    imp_median.fit(X_ohe.loc[:,imp_median_columns_list])
    X_ohe_imp.loc[:,imp_median_columns_list] = imp_median.transform(X_ohe.loc[:,imp_median_columns_list])
# 指定した特徴量・変数名におけるX_trainの各特徴量の最頻値を学習、穴埋めを行う
if len(imp_most_frequent_columns_list) > 0:
    imp_most_frequent.fit(X_ohe.loc[:,imp_most_frequent_columns_list])
    X_ohe_imp.loc[:,imp_most_frequent_columns_list] = imp_most_frequent.transform(X_ohe.loc[:,imp_most_frequent_columns_list])


# 最終的なX_ohe_impのデータフレームを表示
display(X_ohe_imp.head())
```




#### 訓練データと検証用データに分割
```python
from sklearn.model_selection import train_test_split


# 交差検証(holdout)のため、学習データを訓練用と検証用に分割
X_train,X_valid,y_train,y_valid = train_test_split(X_ohe_imp,
                                                 y,
                                                 test_size=0.2,
                                                 random_state=0)

# データフレームを配列に整形
y_train = y_train.values.ravel()
y_valid = y_valid.values.ravel()
```



#### モデルを作成して訓練と性能スコア検証を実施
```python
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor
from sklearn.metrics import r2_score

#３つのモデルを作成
tree = DecisionTreeRegressor(random_state=0)
rf = RandomForestRegressor(random_state=0)
grb = GradientBoostingRegressor(random_state=0)

#３つのモデルで訓練と検証データの予測を行う
tree.fit(X_train,y_train)
rf.fit(X_train,y_train) 
grb.fit(X_train,y_train)

#３つのモデルの予測結果を算出
y_tree_pred = tree.predict(X_valid)
y_rf_pred = rf.predict(X_valid)
y_grb_pred = grb.predict(X_valid)

#３つのモデルの予測精度をR2スコアで算出
print('DecisionTree R2_score: %.6f' % r2_score(y_valid, y_tree_pred))
print('RondomForest R2_score: %.6f' % r2_score(y_valid, y_rf_pred))
print('GradientBoodtingRegreddor R2_score: %.6f' % r2_score(y_valid, y_grb_pred))
```



#### 検証データの実測値と予測値の関係をグラフ化

```python
#予測データと実測値をグラフ化する関数を作成
import matplotlib.pyplot as plt
def scatter(y_pred, y):
    plt.gca().set_aspect('equal', adjustable='box')
    plt.ylabel('y_valid')
    plt.xlabel('y_pred')
    plt.plot([y.min(), y.max()], [y.min(), y.max()])
    plt.plot(y_pred, y, '.')
```

#### 決定木モデルのグラフを表示
```python
# 予測データ（y_pred）と真値（y_test）を描画
plt.title('DecisionTree')
scatter(y_tree_pred, y_valid)
```

#### ランダムフォレストモデルのグラフを表示
```python
# 予測データ（y_pred）と真値（y_test）を描画
plt.title('RondomForest')
scatter(y_rf_pred,y_valid)
```

#### 勾配ブースティング木モデルのグラフを表示
```python
# 予測データ（y_pred）と真値（y_test）を描画
plt.title('GradientBoodtingRegreddor')
scatter(y_grb_pred,y_valid)
```



#### ランダムフォレストモデルでの特徴量・変数重要度データを作成・表示
```python
X_importance = pd.DataFrame(rf.feature_importances_,
                            columns=['feature_importances'],
                            index=X_ohe_imp.columns)
X_importance = X_importance.sort_values(by=["feature_importances"],
                                        ascending=False)
display("important feature top10:",X_importance.head(10))


# 特徴量・変数の重要度をCSVファイルとして吐き出します
# 指定したいファイル名がある場合
# 'RandomForest_feature_importances.csv'を書き換えて下さい
X_importance.to_csv('RandomForest_feature_importances.csv',
　　　　　　　　　　　　index=None)
```
→尚、吐き出したファイルに記載されている特徴量・変数重要度は、「その変数を使ってMSEスコアがどれだけ下がったかという指標を、その変数によって分類したデータ点数の重みを掛け合わせて正規化した値」である。



# ステップ②テストデータの予測結果ファイルの作成

####予測用データの取得と確認
```python
#JupyterNotebookと同じフォルダにある学習用データを読み込む
df_test_origin = pd.read_csv('./datasets/house_price_dataset_test_quita.csv',encoding='cp932')

#表示して適切に読み込みが終わっているか確認
display("オリジナルデータ",df_test_origin.head())
df_test_origin.shape
```


#### 予測に使う特徴量・変数と予測したいデータをそれぞれX,yに格納

```python
# 1列目(B列)から最終列以前を予測に使う特徴量・変数Xに設定
X_test  = df_test_origin.iloc[:, 1:]
X_test.shape
```


#### 訓練データと同じカラムでワンホットエンコーディングを行う
```python
# 訓練データでワンホットエンコーディングを行なった特徴量・変数リストを用いて
# ワンホットエンコーディングを実施
X_test_ohe = pd.get_dummies(X_test,
                            dummy_na=True,
                            columns=ohe_columns)
# 結果を表示
display("ワンホットエンコーディング後のX_testデータ",X_test_ohe.head())
```



#### 訓練データ・学習データ片方にしかに存在しない特徴量・変数を確認

```python
#予測したいデータのカラムの格納
X_test_columns = X_test_ohe.columns.values

#モデルにあるカラムとテストデータにあるカラムの差を確認
cols_train = set(X_ohe.columns.values)
cols_score = set(X_test_ohe.columns.values)

# モデルにはあったがテストデータにはないデータ項目
diff1 = cols_train - cols_score
print('学習データのみに存在する特徴量・変数名項目: %s' % diff1)

# テストデータにはあるがモデルになかったデータ項目
diff2 = cols_score - cols_train
print('テストデータのみに存在する特徴量・変数名項目: %s' % diff2)
```



#### 訓練データに存在していない特徴量・変数を削除
```python
#訓練データの特徴量・変数をもったデータフレームを作成
X_cols_mix = pd.DataFrame(None, columns=cols_train)

#訓練データの特徴量・変数を持ったテストデータセットを作成
X_test_ohe_arranged = pd.concat([X_cols_mix, X_test_ohe])
display("訓練データの特徴量・変数を持ったデータセット",X_test_ohe_arranged.head())

#テストデータにしかない特徴量・変数を削除する
X_test_ohe_arranged = X_test_ohe_arranged.drop(list(set(X_test_ohe.columns.values)-set(X_ohe.columns.values)),
                         axis=1)

# テストデータにはなかったワンホットエンコーディングのラベルを全て０で埋める
X_test_ohe_arranged.loc[:,list(cols_train-cols_score)] = \
    X_test_ohe_arranged.loc[:,list(cols_train-cols_score)].fillna(0, axis=1)

# 列順を訓練データのワンホットエンコーディングを行なった際の順番に変更
X_test_ohe_arranged = X_test_ohe_arranged.reindex(X_ohe.columns.values,axis=1)
display("欠損値処理以外を全て終えたテストデータのデータフレーム:",X_test_ohe_arranged.head())
```


#### テストデータの欠損値を訓練データで学習したInputerで補完

```python
#欠損処理を行う元となるテストデータフレームを作成
X_test_ohe_arranged_imp = X_test_ohe_arranged.copy()

# 指定した特徴量・変数名においてX_trainの各特徴量の平均値で穴埋めを行う
if len(imp_mean_columns_list) > 0:
    X_test_ohe_arranged_imp.loc[:,imp_mean_columns_list] = imp_mean.transform(X_test_ohe_arranged.loc[:,imp_mean_columns_list])
# 指定した特徴量・変数名においてX_trainの各特徴量の中央値で穴埋めを行う
if len(imp_median_columns_list) > 0:
    X_test_ohe_arranged_imp.loc[:,imp_median_columns_list] = imp_median.transform(X_test_ohe_arranged.loc[:,imp_median_columns_list])
# 指定した特徴量・変数名においてX_trainの各特徴量の最頻値で穴埋めを行う
if len(imp_most_frequent_columns_list) > 0:
    X_test_ohe_arranged_imp.loc[:,imp_most_frequent_columns_list] = imp_most_frequent.transform(X_test_ohe_arranged.loc[:,imp_most_frequent_columns_list])

display("最終的なテストデータフレーム",X_test_ohe_arranged_imp.head())
```



#### 訓練したモデルとテストデータを用いた予測を実施し結果ファイルを吐き出す

```python
#予測を実施
y_test_tree_pred = tree.predict(X_test_ohe_arranged_imp)
y_test_rf_pred = rf.predict(X_test_ohe_arranged_imp)
y_test_grb_pred = grb.predict(X_test_ohe_arranged_imp)

# オリジナルのテストデータフレームをコピーする
df_test_prediction = df_test_origin.copy()

# ランダムフォレストの予測結果を最初の列に埋めて返す
df_test_prediction.insert(0, 'Decision Tree Prediction', y_test_tree_pred)
df_test_prediction.insert(1, 'Random Forest Prediction', y_test_rf_pred)
df_test_prediction.insert(2, 'Gradient Boosting Prediction', y_test_grb_pred)

display("決定木/ランダムフォレスト/勾配ブースティングによる予測値を埋めたテストデータ",df_test_prediction.head())

#CSVファイルに予測結果を吐き出します
# 指定したいファイル名がある場合
# 'prediction_completed.csv'を書き換えて下さい
df_test_prediction.to_csv('prediction_completed.csv',index=None)
```