Competition


Kaggle - Titanic 生存予測


get_dummies train test 差分

https://qiita.com/yutowac/items/0052114affbee6fd9cb4

https://shikaolog.com/pandas-dummy-variable-categorical/


アルゴリズム

 

 参考 データ分析初心者向け!主要な予測方法を徹底解説!(前編)

 

・k近傍法

 新しいデータが既存のデータのどのクラスに属するかを、近くのデータポイントを基に決定する

 

・サポートベクターマシン

 データを分けるための最適な境界を見つける。非線形問題に対してもカーネルを使って適応できる

 

・ランダムフォレスト

 複数の決定木を使って多数決を行うことで予測を行う手法。過学習を防ぐために有効

 

・勾配ブースティング決定木

 予測の精度を上げるために、弱い予測モデルを繰り返し学習させ、最終的に強いモデルを構築する手法


1)初期処理 》 ファイル読み込み、データ確認

import pandas as pd

# ファイル読み込み
train = pd.read_csv('/kaggle/input/titanic/train.csv')  # トレーニングセット
test = pd.read_csv('/kaggle/input/titanic/test.csv')    # テストセット

# データ確認
# トレーニングセット
train.head(3)

# テストセット
test.head(3)

# 型
train.dtypes

# 欠損値(トレーニングセット)
train.isnull().sum()

#欠損値(テストセット)
test.isnull().sum()

2)欠損値のない数値のみのモデルを作成してベースラインをとる

# トレーニングセットで変数定義
x = train[['Pclass','SibSp','Parch']]  # 説明変数:最初は数値型の欠損値がないカラムを使用
y = train['Survived']                  # 目的変数

# x,yをトレーニング用とテスト用に分割
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=0)

# 【k近傍法】を使用したモデルを作成
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
knn.fit(X_train, Y_train)  # トレーニング用

# scoreを使って精度を確認
"""
精度 = 予測値が「陽性」で、かつ、実際の正解値も「陽性」だった正解数 ÷「陽性」と予測した全てのデータ数
トレーニングとテストの精度の差が小さいのが理想的
一方の精度が高すぎる場合は過学習傾向にある(ex 98%)
"""
print(knn.score(X_train, Y_train))  # トレーニング用
print(knn.score(X_test, Y_test))    # テスト用

Result

0.6751497005988024

0.6502242152466368

# テストセットで変数定義
x_for_submit = test[['Pclass','SibSp','Parch']]  # 説明変数

# テストセットによる予測を行う
survived = knn.predict(x_for_submit)

# 提出用データフレームを作成
submit = pd.DataFrame({"PassengerId":test["PassengerId"], "Survived":survived})

# 提出ファイルを出力
submit.to_csv('submit01.csv', index=False)

3)特徴量設計の手法

① カテゴリ変数をダミー変数化する 》 get_dummies()

ダミー変数化前

  Pclass SibSp Parch Sex
0 3 1 0 male
1 1 1 0 female
2 3 0 0 female

 ダミー変数化後

  SibSp Parch Pclass_1 Pclass_2 Pclass_3 Sex_female Sex_male
0 1 0 0 0 1 0 1
1 1 0 1 0 0 1 0
2 0 0 0 0 1 1 0
# トレーニングセットで変数定義
"""
カテゴリ変数
 Pclass:1 = 1st, 2 = 2nd, 3 = 3rd
 sex:male, female
Sexを追加
"""
x = train[['Pclass','SibSp','Parch', 'Sex']]  # 説明変数
y = train['Survived']                         # 目的変数

# カテゴリ変数をダミー変数化する
x = pd.get_dummies(x, columns=['Pclass','Sex'], dtype=int)

# 特徴量の見直し結果

# トレーニング用とテスト用に分割
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=0)

# モデル作成
knn = KNeighborsClassifier()
knn.fit(X_train, Y_train) 

# 精度確認
print(knn.score(X_train, Y_train)) 
print(knn.score(X_test, Y_test))

Result

0.811377245508982

0.7982062780269058

 

② 標準化(異なるスケールの特徴量(例えば、年齢と収入)を一貫した形で扱うことができる) 》 StandardScaler()

標準化を行うことによって、特徴量の比率を揃えることが出来る。例えば100点満点のテストと50点満点のテスト

があったとして点数の比率、単位が違う場合でも標準化を利用することでそれらの影響を受けずに点数を評価でる。

 

X_train_scaler

array([[-0.46037161, -0.47720996, -0.48380773, ..., 0.90562878,

     -0.72705166, 0.72705166],

   [ 2.98532288, 1.95619654, -0.48323058, ..., 0.90562878,

     1.37541808, -1.37541808],

   [ 0.40105202, -0.47720996, -0.32165051, ..., 0.90562878,

     -0.72705166, 0.72705166],

     ...,

# トレーニングセットで変数定義
"""
桁数の大きいFare(運賃)を追加 例 7.2500
"""
x = train[['Pclass','SibSp','Parch', 'Sex','Fare']]  # 説明変数
y = train['Survived']                                # 目的変数

# ダミー変数化
x = pd.get_dummies(x, columns=['Pclass','Sex'], dtype=int)

# トレーニング用とテスト用に分割
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=0)

# 標準化
from sklearn.preprocessing import StandardScaler
# インスタンス生成
scaler = StandardScaler()
# データの平均と標準偏差を計算
scaler.fit(X_train)                         
# データを標準化
X_train_scaler = scaler.transform(X_train)
X_test_scaler = scaler.transform(X_test)

# モデル作成
knn = KNeighborsClassifier()
knn.fit(X_train_scaler, Y_train) 

# 精度確認
print(knn.score(X_train_scaler, Y_train)) 
print(knn.score(X_test_scaler, Y_test))

Result

0.8458083832335329

0.8161434977578476

 

③ NULLに平均値をセット 》 fillna()

# テストセットで変数定義
x_for_submit = test[['Pclass','SibSp','Parch', 'Sex','Fare']]  # 説明変数

# 欠損値を平均値に置換(穴埋め)
x_for_submit['Fare'] = x_for_submit['Fare'].fillna(x_for_submit['Fare'].mean())

# ダミー変数化
x_for_submit = pd.get_dummies(x_for_submit, columns=['Pclass','Sex'], dtype=int)

# 標準化
"""
テストセットに対しては、インスタンス生成やfitを行わず、transformのみ実施する
"""
x_for_submit_scaler = scaler.transform(x_for_submit)

# テストセットによる予測
"""
predictのINPUTに欠損値が含まれていると以下の例外が発生する。当処理を実施する前にfillna等で置換が必要
ValueError: Input X contains NaN.
"""
survived = knn.predict(x_for_submit_scaler)

# 提出用Dataframe作成
submit = pd.DataFrame({"PassengerId":test["PassengerId"], "Survived":survived})

submit.head(3)

# 提出ファイル(csv)出力
submit.to_csv('submit02.csv', index=False)

4)他のモデルを試して精度を確認

・モデル作成までの一連作業

# トレーニングセットで変数定義
x = train[['Pclass','SibSp','Parch', 'Sex','Fare']]  # 説明変数
y = train['Survived']                                # 目的変数

# ダミー変数化
x = pd.get_dummies(x, columns=['Pclass','Sex'], dtype=int)

# トレーニング用とテスト用に分割
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=0)

# 標準化
scaler = StandardScaler()
scaler.fit(X_train)                         
X_train_scaler = scaler.transform(X_train)
X_test_scaler = scaler.transform(X_test)

》サポートベクターマシン

from sklearn.svm import LinearSVC

# モデル作成 svc = LinearSVC() svc.fit(X_train_scaler, Y_train) # 精度確認 print(knn.score(X_train_scaler, Y_train)) print(knn.score(X_test_scaler, Y_test))

Result

0.8458083832335329

0.8161434977578476

 

》ランダムフォレスト

from sklearn.ensemble import RandomForestClassifier

# モデル作成 rf = RandomForestClassifier() rf.fit(X_train_scaler, Y_train) # 精度確認 print(rf.score(X_train_scaler, Y_train)) print(rf.score(X_test_scaler, Y_test))

Result

0.9221556886227545

0.8251121076233184

 

》勾配ブースティング決定木 - Scikit-Learn

from sklearn.ensemble import GradientBoostingClassifier

# モデル作成 gr = GradientBoostingClassifier() gr.fit(X_train_scaler, Y_train) # 精度確認 print(gr.score(X_train_scaler, Y_train)) print(gr.score(X_test_scaler, Y_test))

Result

0.8712574850299402

0.8026905829596412

》勾配ブースティング決定木 - XGBoost

from xgboost import XGBClassifier

# モデル作成 xg = XGBClassifier() xg.fit(X_train_scaler, Y_train) # 精度確認 print(xg.score(X_train_scaler, Y_train)) print(xg.score(X_test_scaler, Y_test))

Result

0.9041916167664671

0.820627802690583

 

》一番精度の高かったランダムフォレストのモデルで提出する作業

# テストセットで変数定義
x_for_submit = test[['Pclass','SibSp','Parch', 'Sex','Fare']]  # 説明変数

# 欠損値を平均値に置換(穴埋め)
x_for_submit['Fare'] = x_for_submit['Fare'].fillna(x_for_submit['Fare'].mean())

# ダミー変数化
x_for_submit = pd.get_dummies(x_for_submit, columns=['Pclass','Sex'], dtype=int)

# 標準化
x_for_submit_scaler = scaler.transform(x_for_submit)

# テストセットによる予測
survived = rf.predict(x_for_submit_scaler)

# 提出用Dataframe作成
submit = pd.DataFrame({"PassengerId":test["PassengerId"], "Survived":survived})

# 提出ファイル(csv)出力
submit.to_csv('submit03.csv', index=False)

5)説明変数の重要度を確認

df = pd.DataFrame({"feature_names":x_for_submit.columns, "coefficiet":rf.feature_importances_})
df.sort_values("coefficiet", ascending=False)
  feature_names coefficiet
2 Fare 0.424016
6 Sex_female 0.180267
7 Sex_male 0.172370
0 SibSp 0.063386
1 Parch 0.062222
5 Pclass_3 0.044798
3 Pclass_1 0.031634
4 Pclass_2 0.021308

SIGNATE - SMBC 電力価格の予測


■ smbc13.ipynb (11_2.の説明変数 + 休日フラグ)

1. 初期処理

import pandas as pd

# データ可視化
import matplotlib.pyplot as plt
import seaborn as sns

# 説明変数の対象カラム
cols_trn = [
    # 発電実績データ
    #'generation_biomass',
    #'generation_fossil_brown_coal/lignite',
    #'generation_fossil_gas',
    'generation_fossil_hard_coal',
    #'generation_fossil_oil',
    #'generation_hydro_pumped_storage_consumption',
    #'generation_hydro_run_of_river_and_poundage',
    #'generation_hydro_water_reservoir',
    #'generation_nuclear',
    #'generation_other',
    'generation_other_renewable',
    'generation_solar',
    #'generation_waste',
    #'generation_wind_onshore',
    # 総電力需要実績
    'total_load_actual',
    # 総電力需要実績(24時間前)
    'total_load_actual24',
    # 主要5都市の気象データ(天候を示す各種記述データ以外)
    #'valencia_temp',
    #'valencia_temp_min',
    #'valencia_temp_max',
    #'valencia_pressure',
    #'valencia_humidity',
    #'valencia_wind_speed',
    #'valencia_wind_deg',
    #'valencia_rain_1h',
    #'valencia_rain_3h',
    #'valencia_snow_3h',
    #'valencia_clouds_all',
    #'valencia_weather_id',
    #'madrid_temp',
    #'madrid_temp_min',
    #'madrid_temp_max',
    #'madrid_pressure',
    #'madrid_humidity',
    #'madrid_wind_speed',
    #'madrid_wind_deg',
    #'madrid_rain_1h',
    #'madrid_rain_3h',
    #'madrid_snow_3h',
    #'madrid_clouds_all',
    #'madrid_weather_id',
    #'bilbao_temp',
    #'bilbao_temp_min',
    #'bilbao_temp_max',
    #'bilbao_pressure',
    #'bilbao_humidity',
    #'bilbao_wind_speed',
    #'bilbao_wind_deg',
    #'bilbao_rain_1h',
    #'bilbao_rain_3h',
    #'bilbao_snow_3h',
    #'bilbao_clouds_all',
    #'bilbao_weather_id',
    #'barcelona_temp',
    #'barcelona_temp_min',
    #'barcelona_temp_max',
    'barcelona_pressure',
    #'barcelona_humidity',
    #'barcelona_wind_speed',
    #'barcelona_wind_deg',
    #'barcelona_rain_1h',
    #'barcelona_rain_3h',
    #'barcelona_snow_3h',
    #'barcelona_clouds_all',
    #'barcelona_weather_id',
    #'seville_temp',
    #'seville_temp_min',
    #'seville_temp_max',
    #'seville_pressure',
    #'seville_humidity',
    #'seville_wind_speed',
    #'seville_wind_deg',
    #'seville_rain_1h',
    #'seville_rain_3h',
    #'seville_snow_3h',
    #'seville_clouds_all',
    #'seville_weather_id',
    # 主要5都市の気象データ(天候を説明)
    #'valencia_weather_main',
    #'madrid_weather_main',
    #'bilbao_weather_main',
    #'barcelona_weather_main',
    #'seville_weather_main'
    # 休日フラグ
    'holiday_flag'
]

# 欠損値補完の対象カラム
cols_nul = [
    # 発電実績データ
    #'generation_biomass',
    #'generation_fossil_brown_coal/lignite',
    #'generation_fossil_gas',
    'generation_fossil_hard_coal',
    #'generation_fossil_oil',
    #'generation_hydro_pumped_storage_consumption',
    #'generation_hydro_run_of_river_and_poundage',
    #'generation_hydro_water_reservoir',
    #'generation_nuclear',
    #'generation_other',
    'generation_other_renewable',
    'generation_solar',
    #'generation_waste',
    #'generation_wind_onshore',
    # 総電力需要実績
    'total_load_actual',
    # 総電力需要実績(24時間前)
    'total_load_actual24'
]

# ダミー変数化の対象カラム
cols_dmy = [
    # 天気の説明
    #'valencia_weather_main',
    #'madrid_weather_main',
    #'bilbao_weather_main',
    #'barcelona_weather_main',
    #'seville_weather_main'
]

2. ファイル読み込み

train = pd.read_csv('./data/input/train.csv', parse_dates=['time'])
test = pd.read_csv('./data/input/test.csv', parse_dates=['time']) 

3. カテゴリー変数をダミー変数化する前の事前作業 - 列数・列順番の調整

※説明変数からカテゴリー変数を除外したため当処理はコメントアウト

"""

for col in cols_dmy:
    uniq_elem = train[col].unique()
    train[col] = pd.Categorical(train[col], uniq_elem)
    test[col] = pd.Categorical(test[col], uniq_elem)

"""

4. 24時間前の総電力需要実績(total_load_actual)を付加

# time列をタイムゾーン付きdatetimeに変換
train['time'] = pd.to_datetime(train['time'], utc=True)

# 24時間前の総電力需要実績カラムを追加(初期値NaN)
train['total_load_actual24'] = pd.NA

# 処理高速化のため、'time'をインデックス設定したDataFramから'total_load_actual'を取り出したSeriesを作成
time_to_load = train.set_index('time')['total_load_actual']

for idx, row in train.iterrows():
    # 24時間前の時間を算出
    wk_time24 = row['time'] - pd.Timedelta(hours=24)

    # 24時間前と一致する行の検索
    if wk_time24 in time_to_load:
        # 一致する行のtotal_load_actualを取得
        wk_total = time_to_load[wk_time24]
    else:
        wk_total = pd.NA

    # total_load_actual24に設定
    train.at[idx, 'total_load_actual24'] = wk_total

# object型をfloat64へ変換
train['total_load_actual24'] = pd.to_numeric(train['total_load_actual24'], errors='coerce')

5. timeの休日フラグを設定

import holidays

# スペインの祝日設定(期間は2015〜2017年)
es_holidays = holidays.Spain(years=[2015, 2016, 2017])

# time列をUTCに変換(もとのタイムゾーン付きデータに対応)
train['time'] = pd.to_datetime(train['time'], utc=True)

# 時刻を現地時間(Europe/Madrid)に変換
train['time_local'] = train['time'].dt.tz_convert('Europe/Madrid')

# 曜日と祝日でフラグ作成
train['holiday_flag'] = train['time_local'].apply(
    lambda x: 1 if x.weekday() >= 5 or x.date() in es_holidays else 0
)

6. トレーニングセットで変数定義

x = train[cols_trn]        # x : 説明変数
y = train['price_actual']  # y : 目的変数 

7. 欠損値に直前・直後の非欠損値を設定

x.loc[:, cols_nul] = x[cols_nul].ffill().bfill().infer_objects(copy=False) 

8. カテゴリ変数をダミー変数化

"""

x = pd.get_dummies(x, columns=cols_dmy, dtype=int)

"""

9. データ可視化 - ヒートマップ

"""

x_sub = x.copy()
x_sub['price_actual'] = train['price_actual']

corr = x_sub.corr()
plt.figure(figsize=(16,12))
#plt.figure(figsize=(512,384))
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", square=True)
plt.title("価格と主要変数の相関")
plt.show()

#pd.DataFrame(list(x_sub.columns)).to_csv('./data/check/x_sub_columns.csv', index=False)

"""

10. x,yをトレーニング用とテスト用に分割

from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(x, y, random_state=0) 

11. 標準化

"""

from sklearn.preprocessing import StandardScaler
# インスタンス生成
scaler = StandardScaler()
# データの平均と標準偏差を計算
scaler.fit(X_train)                         
# データを標準化
X_train_scaler = scaler.transform(X_train)
X_test_scaler = scaler.transform(X_test)

"""

12. モデル生成 - LightGBMのscikit-learn API

import lightgbm as lgb

model = lgb.LGBMRegressor(
    objective='regression',   # 目的は回帰
    num_leaves=31,
    learning_rate=0.05,
    n_estimators=100
)
model.fit(X_train, Y_train)

# 精度確認
print(model.score(X_train, Y_train)) 
print(model.score(X_test, Y_test))

Result

0.628423587974599

0.5911748362715796


※以降、提出ファイルの生成処理

 

1. 24時間前の総電力需要実績(total_load_actual)を付加

test_utc = test.copy()

# time列をタイムゾーン付きdatetimeに変換
test_utc['time'] = pd.to_datetime(test_utc['time'], utc=True)

# 24時間前の総電力需要実績カラムを追加(初期値NaN)
test_utc['total_load_actual24'] = pd.NA

# 処理高速化のため、'time'をインデックス設定したDataFramから'total_load_actual'を取り出したSeriesを作成
time_to_load = test_utc.set_index('time')['total_load_actual']

for idx, row in test_utc.iterrows():
    # 24時間前の時間を算出
    wk_time24 = row['time'] - pd.Timedelta(hours=24)

    # 24時間前と一致する行の検索
    if wk_time24 in time_to_load:
        # 一致する行のtotal_load_actualを取得
        wk_total = time_to_load[wk_time24]
    else:
        wk_total = pd.NA

    # total_load_actual24に設定
    test_utc.at[idx, 'total_load_actual24'] = wk_total

# object型をfloat64へ変換
test_utc['total_load_actual24'] = pd.to_numeric(test_utc['total_load_actual24'], errors='coerce') 

2. timeの休日フラグを設定

# time列をUTCに変換(もとのタイムゾーン付きデータに対応)
test_utc['time'] = pd.to_datetime(test_utc['time'], utc=True)

# 時刻を現地時間(Europe/Madrid)に変換
test_utc['time_local'] = test_utc['time'].dt.tz_convert('Europe/Madrid')

# 曜日と祝日でフラグ作成
test_utc['holiday_flag'] = test_utc['time_local'].apply(
    lambda x: 1 if x.weekday() >= 5 or x.date() in es_holidays else 0
)

3. テストセットで変数定義

x_for_submit = test_utc[cols_trn]
#x_for_submit = test[cols_trn]

4. 欠損値を平均値に置換

x_for_submit.loc[:, cols_nul] = x_for_submit[cols_nul].ffill().bfill().infer_objects(copy=False)

5. カテゴリ変数をダミー変数化

"""

x_for_submit = pd.get_dummies(x_for_submit, columns=cols_dmy, dtype=int)

"""

6. 標準化

"""
テストセットに対しては、インスタンス生成やfitを行わず、transformのみ実施する
"""
#x_for_submit_scaler = scaler.transform(x_for_submit)

7. テストセットによる予測を行う

price_actual = model.predict(x_for_submit)

8. 提出用データフレームを作成

submit = pd.DataFrame({"time":test["time"], "price_actual":price_actual})

9. 提出ファイルを出力

submit.to_csv('./data/submit/submit13.csv', header=False, index=False)

■ サンプル:LightGBM+XGBoostでアンサンブル


1. モデル生成 - LightGBMのscikit-learn API

import lightgbm as lgb

lgb_model  = lgb.LGBMRegressor(
    objective='regression',   # 目的は回帰
    num_leaves=31,
    learning_rate=0.05,
    n_estimators=100, 
    random_state=42
    )
lgb_model.fit(X_train, Y_train)

2. モデル生成 - XGBoost

from xgboost import XGBRegressor

xgb_model = XGBRegressor(
    n_estimators=100, 
    random_state=42
    )
xgb_model.fit(X_train, Y_train)

3. テストセットによる予測を行う - アンサンブル(単純平均)

lgb_pred = lgb_model.predict(x_for_submit)
xgb_pred = xgb_model.predict(x_for_submit)
price_actual = (lgb_pred + xgb_pred) / 2