【Python】ダミー変数、本番実装時にどうする?

Pythonで既存のデータで学習・テストをする際にカテゴリーデータは、ダミー変数(One-Hotエンコーディング)を作りますよね。

学習データやテストデータについては、機械学習モデルを作る前に、pandasのget_dummies()を使いますよね。

ですが、本番実装時に1行のデータを受けてそれをモデルに入れたいとします。ダミー変数化される前の元のカテゴリカルデータしかないデータです。

さて、困りました。どうすればいいのでしょうか?

方法は、3つあるようです。

  1.  pd.get_dummies() + reindex() を使う
  2. pd.CategoricalDtypeを使う
  3. sklearn.preprocessing.OneHotEncoder を使う

それぞれのメリットとデメリットは下記のとおりです。

メリット デメリット
pd.get_dummies() + reindex()
  • シンプルで理解しやすい。
  • pandasの基本的な機能を活用しているため、他のデータ操作と統合しやすい。
  • カテゴリ変数が多い場合や、高頻度で新しいカテゴリが追加される場合、手動でreindexするのは面倒。
  • ダミー変数のカラム順序を管理する必要がある。
pd.CategoricalDtype を使う
  • カテゴリの順序を事前に定義できるため、データの一貫性が保たれる。
  • 学習データとテストデータで同じカテゴリカル変数を簡単に管理できる。
  • 事前にカテゴリを全て定義しておく必要があり、動的なカテゴリ追加には対応しづらい。
  • pd.CategoricalDtypeの使用方法に慣れていない場合は若干の学習コストがある。
sklearn.preprocessing.OneHotEncoder を使う
  • 高度な前処理機能を提供し、新しいカテゴリのデータが出現した際の処理も柔軟に行える(例:handle_unknown='ignore')。
  • Scikit-learnを利用する他の機械学習パイプラインと統合しやすい。
  • Scikit-learnの依存関係を追加する必要があるため、軽量なスクリプトには不向きな場合がある。
  • 初期設定や使用方法が若干複雑。

 

次にスクリプトの例を示します。
まずはテストに使うデータを作ります。
カテゴリーデータがないので、EntranceDirectionというデータを付与します。

import pandas as pd
import numpy as np
from sklearn.datasets import fetch_california_housing

# カリフォルニア住宅データをロード
california_housing_data = fetch_california_housing()
exp_data = pd.DataFrame(california_housing_data.data, columns=california_housing_data.feature_names)
tar_data = pd.DataFrame(california_housing_data.target, columns=['HousingPrices'])
data = pd.concat([exp_data, tar_data], axis=1)

# EntranceDirection, HouseMaker, Parkling のカテゴリデータをランダムに作成
np.random.seed(42)  # 再現性のためのシード
data['EntranceDirection'] = np.random.choice(['east', 'west', 'south', 'north'], size=data.shape[0])
data['HouseMaker'] = np.random.choice(['A', 'B', 'C', 'D', 'E', 'F'], size=data.shape[0])
data['Parkling'] = np.random.choice(['covered', 'uncovered', 'none'], size=data.shape[0])

display(data.head())
print(data.shape)
print(data.dtypes)

1. pd.get_dummies() + reindex() を使う

●データの読み込みとモデル作成

import pandas as pd
from sklearn.linear_model import LinearRegression
import joblib
import numpy as np
from sklearn.datasets import fetch_california_housing

# カリフォルニア住宅データをロード
california_housing_data = fetch_california_housing()
exp_data = pd.DataFrame(california_housing_data.data, columns=california_housing_data.feature_names)
tar_data = pd.DataFrame(california_housing_data.target, columns=['HousingPrices'])
data = pd.concat([exp_data, tar_data], axis=1)

# EntranceDirection, HouseMaker, Parkling のカテゴリデータをランダムに作成
np.random.seed(42)  # 再現性のためのシード
data['EntranceDirection'] = np.random.choice(['east', 'west', 'south', 'north'], size=data.shape[0])
data['HouseMaker'] = np.random.choice(['A', 'B', 'C', 'D', 'E', 'F'], size=data.shape[0])
data['Parkling'] = np.random.choice(['covered', 'uncovered', 'none'], size=data.shape[0])

# カテゴリカル変数をダミー変数化
data = pd.get_dummies(data, columns=['EntranceDirection', 'HouseMaker', 'Parkling'])

# モデルの作成
X = data.drop('HousingPrices', axis=1)
y = data['HousingPrices']
model = LinearRegression()
model.fit(X, y)

# モデルとカラム情報の保存
joblib.dump(model, 'model_get_dummies.pkl')
joblib.dump(X.columns, 'columns_get_dummies.pkl')

 

●新しいデータの処理と予測

import pandas as pd
import joblib

# モデルとカラム情報をロード
model = joblib.load('model_get_dummies.pkl')
columns = joblib.load('columns_get_dummies.pkl')

# 新しいデータを読み込み
new_data = pd.DataFrame({'MedInc': [5], 'HouseAge': [30], 'AveRooms': [6], 'AveBedrms': [1], 
                         'Population': [1000], 'AveOccup': [3], 'Latitude': [34], 'Longitude': [-118], 
                         'EntranceDirection': ['east'], 'HouseMaker': ['A'], 'Parkling': ['covered']})
new_data = pd.get_dummies(new_data, columns=['EntranceDirection', 'HouseMaker', 'Parkling'])
new_data = new_data.reindex(columns=columns, fill_value=0)

# 予測
predictions = model.predict(new_data)
print(predictions)

 

2. pd.CategoricalDtypeを使う

●データの読み込みとモデル作成

import pandas as pd
from sklearn.linear_model import LinearRegression
import joblib
import numpy as np
from sklearn.datasets import fetch_california_housing

# カリフォルニア住宅データをロード
california_housing_data = fetch_california_housing()
exp_data = pd.DataFrame(california_housing_data.data, columns=california_housing_data.feature_names)
tar_data = pd.DataFrame(california_housing_data.target, columns=['HousingPrices'])
data = pd.concat([exp_data, tar_data], axis=1)

# EntranceDirection, HouseMaker, Parkling のカテゴリデータをランダムに作成
np.random.seed(42)  # 再現性のためのシード
data['EntranceDirection'] = np.random.choice(['east', 'west', 'south', 'north'], size=data.shape[0])
data['HouseMaker'] = np.random.choice(['A', 'B', 'C', 'D', 'E', 'F'], size=data.shape[0])
data['Parkling'] = np.random.choice(['covered', 'uncovered', 'none'], size=data.shape[0])

# カテゴリデータの型を定義
cat_type_entrance = pd.api.types.CategoricalDtype(categories=['east', 'west', 'south', 'north'])
cat_type_house = pd.api.types.CategoricalDtype(categories=['A', 'B', 'C', 'D', 'E', 'F'])
cat_type_parkling = pd.api.types.CategoricalDtype(categories=['covered', 'uncovered', 'none'])

# カテゴリカル変数をダミー変数化
data = data.astype({'EntranceDirection': cat_type_entrance, 'HouseMaker': cat_type_house, 'Parkling': cat_type_parkling})
data = pd.get_dummies(data, columns=['EntranceDirection', 'HouseMaker', 'Parkling'])

# モデルの作成
X = data.drop('HousingPrices', axis=1)
y = data['HousingPrices']
model = LinearRegression()
model.fit(X, y)

# モデルとカテゴリ型、カラム情報の保存
joblib.dump(model, 'model_categoricaldtype.pkl')
joblib.dump([cat_type_entrance, cat_type_house, cat_type_parkling], 'cat_types.pkl')
joblib.dump(X.columns, 'columns_categoricaldtype.pkl')

 

●新しいデータの処理と予測

import pandas as pd
import joblib

# モデルとカテゴリ型、カラム情報をロード
model = joblib.load('model_categoricaldtype.pkl')
cat_types = joblib.load('cat_types.pkl')
cat_type_entrance, cat_type_house, cat_type_parkling = cat_types
columns = joblib.load('columns_categoricaldtype.pkl')

# 新しいデータを読み込み
new_data = pd.DataFrame({'MedInc': [5], 'HouseAge': [30], 'AveRooms': [6], 'AveBedrms': [1], 
                         'Population': [1000], 'AveOccup': [3], 'Latitude': [34], 'Longitude': [-118], 
                         'EntranceDirection': ['east'], 'HouseMaker': ['A'], 'Parkling': ['covered']})

# カテゴリカル変数をダミー変数化
new_data = new_data.astype({'EntranceDirection': cat_type_entrance, 'HouseMaker': cat_type_house, 'Parkling': cat_type_parkling})
new_data = pd.get_dummies(new_data, columns=['EntranceDirection', 'HouseMaker', 'Parkling'])
new_data = new_data.reindex(columns=columns, fill_value=0)

# 予測
predictions = model.predict(new_data)
print(predictions)

 

3. sklearn.preprocessing.OneHotEncoder を使う

●データの読み込みとモデル作成

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import OneHotEncoder
import joblib
import numpy as np
from sklearn.datasets import fetch_california_housing

# カリフォルニア住宅データをロード
california_housing_data = fetch_california_housing()
exp_data = pd.DataFrame(california_housing_data.data, columns=california_housing_data.feature_names)
tar_data = pd.DataFrame(california_housing_data.target, columns=['HousingPrices'])
data = pd.concat([exp_data, tar_data], axis=1)

# EntranceDirection, HouseMaker, Parkling のカテゴリデータをランダムに作成
np.random.seed(42)  # 再現性のためのシード
data['EntranceDirection'] = np.random.choice(['east', 'west', 'south', 'north'], size=data.shape[0])
data['HouseMaker'] = np.random.choice(['A', 'B', 'C', 'D', 'E', 'F'], size=data.shape[0])
data['Parkling'] = np.random.choice(['covered', 'uncovered', 'none'], size=data.shape[0])

# OneHotEncoderでダミー変数化
encoded = encoder.fit_transform(data[['EntranceDirection', 'HouseMaker', 'Parkling']]).toarray()
encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(['EntranceDirection', 'HouseMaker', 'Parkling']))
data_encoded = pd.concat([data.drop(['EntranceDirection', 'HouseMaker', 'Parkling'], axis=1), encoded_df], axis=1)

# 特徴名を文字列に変換
data_encoded.columns = data_encoded.columns.astype(str)

# モデルの作成
X = data_encoded.drop('HousingPrices', axis=1)
y = data['HousingPrices']
model = LinearRegression()
model.fit(X, y)

# モデルとエンコーダーの保存
joblib.dump(model, 'model_onehotencoder.pkl')
joblib.dump(encoder, 'encoder.pkl')

 

●新しいデータの処理と予測

import pandas as pd
import joblib
from sklearn.preprocessing import OneHotEncoder

# モデルとエンコーダーをロード
model = joblib.load('model_onehotencoder.pkl')
encoder = joblib.load('encoder.pkl')

# 新しいデータを読み込み
new_data = pd.DataFrame({'MedInc': [5], 'HouseAge': [30], 'AveRooms': [6], 'AveBedrms': [1], 
                         'Population': [1000], 'AveOccup': [3], 'Latitude': [34], 'Longitude': [-118], 
                         'EntranceDirection': ['east'], 'HouseMaker': ['A'], 'Parkling': ['covered']})
new_encoded = encoder.transform(new_data[['EntranceDirection', 'HouseMaker', 'Parkling']]).toarray()
new_encoded_df = pd.DataFrame(new_encoded, columns=encoder.get_feature_names_out(['EntranceDirection', 'HouseMaker', 'Parkling']))
new_data_encoded = pd.concat([new_data.drop(['EntranceDirection', 'HouseMaker', 'Parkling'], axis=1), new_encoded_df], axis=1)

# 特徴名を文字列に変換
new_data_encoded.columns = new_data_encoded.columns.astype(str)

# 予測
predictions = model.predict(new_data_encoded)
print(predictions)

 

これらの3つの方法をメリット・デメリットを確認しながら適切に実装していきたいと思います。

(参考)
データ取得:こちら

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


This site uses Akismet to reduce spam. Learn how your comment data is processed.