データを活用したマーケティング戦略は、ビジネスの成功に不可欠です。その中心に位置するのが、マーケティングミックスモデリング(MMM)です。
マーケティングミックスモデリング(MMM)は、過去のデータを分析する「振り返り分析」と未来のトレンドを予測する「近未来分析」の両方で有効に活用される強力なツールです。
どの広告・販促の手段に、どの程度のコストをかけて投資をするのが効率的なのか? 広告・販促の手段にかけるコストの最適投資配分です。
色々なアプローチがありますが、ストレートなのは非線形計画問題(非線形最適化)を定式化しソルバーで解くことです。具体的に言うと、PythonのScyPyである程度は実現することができます。
前回は「アドストックを考慮すべきどうか、コスト最適化の対象かどうか、特徴量を指定し自動調整し構築する線形回帰系MMM」というお話しをします。
PythonによるMMM(マーケティングミックスモデリング)とビジネス活用- 振返り分析・最適投資配分編(その3)-線形回帰系MMMの最適投資配分(特徴量指定・ハイパラ自動調整)
多くの時系列データには季節性があります。前回のモデルには、季節性などを考慮していませんでした。
今回は、「季節性を考慮したRidge MMMパイプラインを構築し最適投資配分を求める方法」のお話しをします。
Contents
データセットと最適投資配分
今回利用するデータセット
以下のデータセットを利用します。
- 目的変数:売上金額(Sales)
- 説明変数:
- メディア:TVCM、Newspaper、Webのコスト
- イベント:XMas(クリスマス)フラグ(1: 12月25日、0: その他)
今回利用するデータセットは、以下からダウンロードできます。
MMM_xmas.csv
https://www.salesanalytics.co.jp/dcbd
今回は、以下のように設定します。
- アドストックを考慮する:
- TVCMのコスト
- Newspaperのコスト
- Webのコスト
- アドストックを考慮しない:
- XMas(クリスマス)フラグ(1: 12月25日、0: その他)
- 季節成分(三角関数で表現)
季節成分を表現する三角関数(Sin, Cos)の特徴量は、データセットには含まれていません。データセットを読み込んだ後に、この特徴量を生成します。
今回構築するMMMと最適化
今回のMMM(マーケティングミックスモデリング)の予算最適化問題は、全体コスト一定の下で売上を最大化する、広告・販促の手段に対するコストの最適配分を求める問題になります。
MMMそのものが目的関数、MMMの出力である売上が目的変数になります。推定器にはRidge回帰を使います。
このRidge MMMパイプラインのハイパーパラメータは、Optunaで自動チューニングします。
特徴量は2つの視点で分けていきます。
- アドストックを考慮する特徴量 or そうでない特徴量
- 考慮する特徴量:TVCM、Newspaper、Webのコスト
- そうでない特徴量:XMas(クリスマス)フラグ、季節成分(三角関数で表現)
- 最適化対象となる特徴量 or そうではない特徴量
- 最適化対象となる特徴量:TVCM、Newspaper、Webのコスト
- そうではない特徴量:XMas(クリスマス)フラグ、季節成分(三角関数で表現)
今回のケースですと、アドストックを考慮すべき特徴量=最適化対象となる特徴量です。
準備
モジュールの読み込み
先ず、必要なモジュールを読み込みます。
以下、コードです。
from mmm_functions5 import *
Pythonファイル(mmm_functions5.py
)そのものは、以下からダウンロードできます。
mmm_functions5.py ※zipファイルを解凍してお使いください
https://www.salesanalytics.co.jp/uno8
mmm_functions5.py
には、MMMで共通して利用する処理を関数化したものや、クラス、モジュール類などを記載しています。前回作った最適化用の関数も含まれています。
mmm_functions5.py
を利用するときは、実行するPythonファイルやNotebookと同じフォルダに入れておいてください。
上手くいかないときは、mmm_functions5.py
をメモ帳などで開き内容をコピーし、実行するPythonファイルやNotebookにコードを張り付け、Pythonで関数を作ってからMMM構築などを行ってください。
データセットの読み込み
以下、コードです。
# データセット読み込み dataset = 'https://www.salesanalytics.co.jp/dcbd' df = pd.read_csv( dataset, parse_dates=['Week'], index_col='Week', )
季節性を表現する特徴量を、三角関数(Sin , Cos)で生成します。
SinとCosのセットであるフーリエ項(基底)をその程度作るのかで、季節性の表現の柔軟性が変わります。多ければ多いほど柔軟な表現になります。フーリエ項(基底)の係数は、データで後で学習し求めます。
まず、フーリエ項(基底)を生成し元のデータセットに付け加える関数を定義します。
以下、コードです。
# # 三角関数特徴量の生成関数を定義 # def add_fourier_terms(df, num, seasonal): ''' 引数df: 元のデータフレーム 引数num: フーリエ項の数(基底の数)、sinとcosのセット数 引数seasonal: フーリエ変換の周期 戻り値: フーリエ項を追加後のデータフレーム ''' # t列がdfにない場合、t列を0からdfの長さまでの連番で作成 if 't' not in df.columns: df['t'] = pd.RangeIndex(start=0, stop=len(df)) # 三角関数特徴量(フーリエ項)を追加 for i in range(1, num + 1): # sin項を追加 df['sin_' + str(i)] = np.sin(i * 2 * np.pi * df.t / seasonal) # cos項を追加 df['cos_' + str(i)] = np.cos(i * 2 * np.pi * df.t / seasonal) return df
この関数を使い、季節成分を生成しデータセットに加えます。
以下、コードです。
# 三角関数特徴量を追加したデータフレームを表示して確認 df = add_fourier_terms(df, num=5, seasonal=52.25) print(df) #確認
周期を52.25に設定しています。年間の週の数が52~53週間あり、多くは年間52週間ですが約4年に1度の間隔で53週になるからです。
以下、実行結果です。
Sales TVCM Newspaper Web xmas t sin_1 \ Week 2018-01-07 2132000 1312200 0 0 0 0 0.000000 2018-01-14 2596100 0 502900 237400 0 1 0.119963 2018-01-21 2236200 0 432100 200600 0 2 0.238193 2018-01-28 1680900 0 338400 0 0 3 0.352983 2018-02-04 2155400 0 0 234000 0 4 0.462674 ... ... ... ... ... ... ... ... 2021-11-28 833900 0 0 0 0 203 -0.660522 2021-12-05 2064700 0 516700 166800 0 204 -0.565683 2021-12-12 1689500 0 0 206600 0 205 -0.462674 2021-12-19 3399200 0 664500 0 1 206 -0.352983 2021-12-26 1461100 0 0 197600 0 207 -0.238193 cos_1 sin_2 cos_2 sin_3 cos_3 sin_4 \ Week 2018-01-07 1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 2018-01-14 0.992778 0.238193 0.971218 0.352983 0.935630 0.462674 2018-01-21 0.971218 0.462674 0.886528 0.660522 0.750806 0.820348 2018-01-28 0.935630 0.660522 0.750806 0.883026 0.469324 0.991849 2018-02-04 0.886528 0.820348 0.571865 0.991849 0.127421 0.938256 ... ... ... ... ... ... ... 2021-11-28 0.750806 -0.991849 0.127421 -0.828851 -0.559470 -0.252764 2021-12-05 0.824623 -0.932951 0.360005 -0.972981 -0.230887 -0.671733 2021-12-12 0.886528 -0.820348 0.571865 -0.991849 0.127421 -0.938256 2021-12-19 0.935630 -0.660522 0.750806 -0.883026 0.469324 -0.991849 2021-12-26 0.971218 -0.462674 0.886528 -0.660522 0.750806 -0.820348 cos_4 sin_5 cos_5 Week 2018-01-07 1.000000 0.000000 1.000000 2018-01-14 0.886528 0.565683 0.824623 2018-01-21 0.571865 0.932951 0.360005 2018-01-28 0.127421 0.972981 -0.230887 2018-02-04 -0.345941 0.671733 -0.740793 ... ... ... ... 2021-11-28 -0.967528 0.449297 -0.893383 2021-12-05 -0.740793 -0.134872 -0.990863 2021-12-12 -0.345941 -0.671733 -0.740793 2021-12-19 0.127421 -0.972981 -0.230887 2021-12-26 0.571865 -0.932951 0.360005 [208 rows x 16 columns]
季節成分を表現する特徴量が付け加えられているのが分かるかと思います。機能するかどうか分かりませんが、トレンド特徴量 t
も追加しています。
目的変数(予測対象)と説明変数に分解します。
以下、コードです。
# 説明変数Xと目的変数yに分解 X = df.drop(columns=['Sales']) y = df['Sales']
目的変数y(売上)をプロットします。
以下、コードです。
y.plot() plt.show()
以下、実行結果です。
説明変数X(各メディアのコスト)をまとめてプロットします。
以下、コードです。
X.plot(subplots=True,figsize=(12,X.shape[1]*3)) plt.show()
以下、実行結果です。季節成分を表現する特徴量も表示されています。
目的関数作り
今回の最適投資配分問題の目的関数は、Ridge MMMMパイプラインそのものになります。
アドストックを考慮する特徴量のリスト
先ず、アドストックを考慮する特徴量のリストを作ります。
以下、コードです。
# アドストックを考慮する特徴量のリストを作成 apply_effects_features = ['TVCM', 'Newspaper', 'Web']
最適なハイパーパラメータの探索
Ridge MMMパイプラインのハイパーパラメータをOptunaで探索します。
先ず、Optunaの目的関数を定義します。
以下、コードです。
# # Optunaの目的関数 # def ridge_objective_apply_effects(trial, X, y, apply_effects_features): carryover_params = [] curve_params = [] # 列名リストからインデックスのリストを作成 apply_effects_indices = [X.columns.get_loc(column) for column in apply_effects_features] no_effects_indices = list(set(range(X.shape[1])) - set(apply_effects_indices)) for feature in apply_effects_features: carryover_length = trial.suggest_int(f'carryover_length_{feature}', 1, 10) carryover_peak = trial.suggest_int(f'carryover_peak_{feature}', 1, carryover_length) carryover_rate1 = trial.suggest_float(f'carryover_rate1_{feature}', 0, 1) carryover_rate2 = trial.suggest_float(f'carryover_rate2_{feature}', 0, 1) carryover_c1 = trial.suggest_float(f'carryover_c1_{feature}', 0, 2) carryover_c2 = trial.suggest_float(f'carryover_c2_{feature}', 0, 2) carryover_params.append({ 'length': carryover_length, 'peak': carryover_peak, 'rate1': carryover_rate1, 'rate2': carryover_rate2, 'c1': carryover_c1, 'c2': carryover_c2, }) saturation_function = trial.suggest_categorical(f'saturation_function_{feature}', ['logistic', 'exponential']) if saturation_function == 'logistic': curve_param_L = trial.suggest_float(f'curve_param_L_{feature}', 0, 10) curve_param_k = trial.suggest_float(f'curve_param_k_{feature}', 0, 10) curve_param_x0 = trial.suggest_float(f'curve_param_x0_{feature}', 0, 2) curve_params.append({ 'saturation_function': saturation_function, 'L': curve_param_L, 'k': curve_param_k, 'x0': curve_param_x0, }) elif saturation_function == 'exponential': curve_param_d = trial.suggest_float(f'curve_param_d_{feature}', 0, 10) curve_params.append({ 'saturation_function': saturation_function, 'd': curve_param_d, }) alpha = trial.suggest_float('alpha', 1e-3, 1e+3) preprocessor = ColumnTransformer( transformers=[ ('effects', Pipeline([ ('carryover', CustomCarryOverTransformer(carryover_params=carryover_params)), ('saturation', CustomSaturationTransformer(curve_params=curve_params)) ]), apply_effects_indices), ('no_effects', 'passthrough', no_effects_indices) ], remainder='drop' ) pipeline = Pipeline(steps=[ ('scaler', MinMaxScaler()), ('preprocessor', preprocessor), ('ridge', Ridge(alpha=alpha)) ]) tscv = TimeSeriesSplit(n_splits=5) scores = cross_val_score(pipeline, X, y, cv=tscv, scoring='neg_mean_squared_error') rmse = np.mean([np.sqrt(-score) for score in scores]) return rmse
この目的関数を使い、最適なハイパーパラメータを探索する関数を定義します。
以下、コードです。
# # Optunaによる最適なハイパーパラメータの探索実行 # def run_optimization(objective, X, y, apply_effects_features, n_trials=1000, study=None): # Optunaのスタディの作成と最適化の実行 if study is None: study = optuna.create_study(direction='minimize') objective_with_data = partial( objective, X=X, y=y, apply_effects_features=apply_effects_features) study.optimize( objective_with_data, n_trials=n_trials, show_progress_bar=True) # 最適化の実行結果の表示 print("Best trial:") trial = study.best_trial print(f"Value: {trial.value}") print("Params: ") for key, value in trial.params.items(): print(f"{key}: {value}") return study
この関数を実行し、最適なハイパーパラメータを探索します。
以下、コードです。
study = run_optimization( ridge_objective_apply_effects, X, y, apply_effects_features, n_trials=5000)
以下、実行結果です。
Best trial: 4938. Best value: 237010: 100%|██████████| 5000/5000 [52:42<00:00, 1.58it/s]Best trial: Value: 237010.09434071128 Params: carryover_length_TVCM: 5 carryover_peak_TVCM: 1 carryover_rate1_TVCM: 0.9729271018598733 carryover_rate2_TVCM: 0.2966298925357188 carryover_c1_TVCM: 1.2314141219113544 carryover_c2_TVCM: 0.546825967722917 saturation_function_TVCM: exponential curve_param_d_TVCM: 1.9183272984812656 carryover_length_Newspaper: 2 carryover_peak_Newspaper: 1 carryover_rate1_Newspaper: 0.17197042046525332 carryover_rate2_Newspaper: 0.43965062701472585 carryover_c1_Newspaper: 1.0338752928967323 carryover_c2_Newspaper: 0.13346846589535857 saturation_function_Newspaper: exponential curve_param_d_Newspaper: 1.6739264306006938 carryover_length_Web: 7 carryover_peak_Web: 1 carryover_rate1_Web: 0.5503030669166483 carryover_rate2_Web: 0.19277860756776197 carryover_c1_Web: 1.3333660220153671 carryover_c2_Web: 1.751549177057382 saturation_function_Web: logistic curve_param_L_Web: 1.674062430952774 curve_param_k_Web: 2.8891224711920658 curve_param_x0_Web: 0.3463030631752817 alpha: 0.08860413121661548
MMMパイプラインの構築
Optunaで探索し見つけたハイパーパラメータを取得し、Ridge MMMパイプラインを構築します。
そのための関数を定義します。
以下、コードです。
def create_model_from_trial_ridge(trial, X, y, apply_effects_features): # # Optunaの実行結果からハイパーパラメータを取得 # carryover_keys = ['length', 'peak', 'rate1', 'rate2', 'c1', 'c2'] curve_keys_logistic = ['L', 'k', 'x0'] curve_keys_exponential = ['d'] # ハイパーパラメータを抽出しキーと値の辞書を作成 def fetch_params(prefix, feature_name, params, trial_params): return {key: trial_params[f'{prefix}_{key}_{feature_name}'] for key in params} # キャリーオーバー効果関数ハイパーパラメータを抽出 carryover_params = [fetch_params('carryover', feature_name, carryover_keys, trial.params) for feature_name in apply_effects_features] # 飽和関数ハイパーパラメータを抽出 curve_params = [] for feature_name in apply_effects_features: saturation_function = trial.params[f'saturation_function_{feature_name}'] curve_param = {'saturation_function': saturation_function} if saturation_function == 'logistic': curve_param.update(fetch_params('curve_param', feature_name, curve_keys_logistic, trial.params)) elif saturation_function == 'exponential': curve_param.update(fetch_params('curve_param', feature_name, curve_keys_exponential, trial.params)) curve_params.append(curve_param) # 推定器ハイパーパラメータを抽出 alpha = trial.params['alpha'] # # MMMパイプラインの構築&学習 # MMM_pipeline, trained_model, pred = build_MMM_pipeline_ridge( X, y, apply_effects_features, carryover_params, curve_params, alpha) # 最適ハイパーパラメータの集約 model_params = [ carryover_params, curve_params, alpha] return trained_model,model_params
戻り値のtrained_modelが学習済みのRidge MMMパイプラインで、model_paramsがそのハイパーパラメータです。
では、この関数を実行します。
以下、コードです。
trained_model,model_params = create_model_from_trial_ridge( study.best_trial, X, y, apply_effects_features)
以下、実行結果です。
RMSE: 177802.61978860805 MAE: 145419.2861950865 MAPE: 0.07475479898495105 R2: 0.9116853811170141
アドストックの状況をグラフで確認します。
以下、コードです。
# グラフで確認 best_carryover_params = model_params[0] best_curve_params = model_params[1] plot_effects( best_carryover_params, best_curve_params, apply_effects_features)
以下、実行結果です。
売上貢献度とマーケティングROI
このMMMパイプラインから、売上貢献度やマーケティングROIなどを求めていきます。
先ずは、貢献度の推移です。
以下、関数です。
# 貢献度の算出 def calculate_and_plot_contribution(y, X, model, ylim=None, apply_effects_features=None): """ 各媒体の売上貢献度を算定し、結果をプロットする関数。 :param y: ターゲット変数 :param X: 特徴量のデータフレーム :param model: 学習済みモデル :param ylim: y軸の表示範囲 :return: 各媒体の貢献度 """ if apply_effects_features is None: apply_effects_features = X.columns # yの予測 pred = model.predict(X) pred = pd.DataFrame(pred, index=X.index, columns=['y']) # 値がすべて0の説明変数 X_ = X.copy() X_[apply_effects_features] = 0 # Baseの予測 base = model.predict(X_) pred['Base'] = base # 各媒体の予測 for feature in apply_effects_features: X_[apply_effects_features] = 0 X_[feature] = X[feature] pred[feature] = model.predict(X_) - base # 予測値の補正 correction_factor = y.div(pred.y, axis=0) pred_adj = pred.mul(correction_factor, axis=0) contribution = pred_adj.drop(columns=['y']) # apply_effects_features以外をbaseに集約 contribution['Base'] = contribution.drop(columns=apply_effects_features).sum(axis=1) contribution = contribution[['Base'] + list(apply_effects_features)] # エリアプロット if ylim is not None: ax = contribution.plot.area() handles, labels = ax.get_legend_handles_labels() ax.legend(reversed(handles), reversed(labels)) ax.set_ylim(ylim) plt.title('Contributions over time') plt.show() return contribution
この関数を使います。
以下、コードです。
# 貢献度の算出 contribution = calculate_and_plot_contribution( y, X, trained_model, (0, 5e6), apply_effects_features) # 数値を表示 print(contribution)
以下、実行結果です。
Base TVCM Newspaper Web Week 2018-01-07 9.733873e+05 1.158613e+06 0.000000 0.000000 2018-01-14 9.484278e+05 4.575676e+05 572240.872421 617863.665059 2018-01-21 8.437181e+05 2.446132e+05 590972.178279 556896.546019 2018-01-28 8.832017e+05 1.694063e+05 540022.457974 88269.537959 2018-02-04 1.079710e+06 1.453228e+05 242901.141448 687466.000783 ... ... ... ... ... 2021-11-28 6.187977e+05 0.000000e+00 160501.533787 54600.793797 2021-12-05 9.717705e+05 0.000000e+00 614667.094955 478262.412278 2021-12-12 8.355731e+05 0.000000e+00 285893.821197 568033.124942 2021-12-19 2.624556e+06 0.000000e+00 676402.630602 98241.655198 2021-12-26 7.368754e+05 0.000000e+00 303580.715522 420643.921625 [208 rows x 4 columns]
次に、貢献度の構成比を求めます。
以下、コードです。
# 貢献度構成比の算出 contribution_results = summarize_and_plot_contribution(contribution) print(contribution_results)
以下、実行結果です。
contribution ratio Base 1.939308e+08 0.436588 TVCM 8.731737e+07 0.196574 Newspaper 7.037395e+07 0.158430 Web 9.257458e+07 0.208409
売上貢献度とコストから、マーケティングROIを求めます。
以下、コードです。
# マーケティングROIの算出 ROI = calculate_marketing_roi( X[apply_effects_features], contribution[apply_effects_features] ) # 数値を表示 print(ROI)
以下、実行結果です。
TVCM 0.501653 Newspaper 0.530328 Web 1.954888 dtype: float64
売上貢献度(横軸)×マーケティングROI(縦軸)のマップを描きます。円の大きさはコストを表します。
以下、コードです。
# 散布図作成(売上貢献度×マーケティングROI) data_to_plot = plot_scatter_of_contribution_and_roi( X[apply_effects_features], contribution[apply_effects_features] ) # 散布図の数値を表示 print(data_to_plot)
以下、実行結果です。
contribution_percentage ROI cost TVCM 0.348898 0.501653 58147500 Newspaper 0.281197 0.530328 45986200 Web 0.369905 1.954888 31329300
最適投資配分
構築したRidge MMMMパイプラインを最大化する最適投資配分問題を解き、広告・販促のコストの最適投資配分を求めます。
最後に、現状と最適化後の結果を比較し、どのような変化が起こるかを出力します。
最適化する期間の設定
最適化する期間は、直近1年間(52週間)です。
以下、コードです。
# 最適化期間を表す変数、ここでは直近1年間(52週間)を指定 term = 52 # 最適化期間における対象特徴量Xのデータを抽出 X_actual = X[-term:] # 最適化期間における目的変数yのデータを抽出 y_actual = y[-term:]
最適化対象となる特徴量のリストを作ります。
以下、コードです。
# 最適化対象の特徴量のリストを作成 optimized_features = ['TVCM', 'Newspaper', 'Web']
最適化の実行
MMMパイプラインの出力である売上を全体コスト一定(現在のコストを合計した値)のもとで最大化する、非線形最適問題を定義し、最適解を求めます。
以下、コードです。
# # 最適投資配分 # opt_results = optimize_investment( trained_model, X_actual, optimized_features, niter = 100)
最適化の計算が終了したら、その最適解を取得します。
以下、コードです。
# # 最適解の取得 # X_optimized = opt_results['X_optimized'] # 最適化後の特徴量を表示 print(X_optimized)
以下、実行結果です。
TVCM Newspaper Web xmas t sin_1 \ Week 2021-01-03 2.061352e+05 2.644306e+05 310760.065241 0 156 -0.090067 2021-01-10 2.820323e+05 1.117502e+05 230997.697922 0 157 0.030059 2021-01-17 2.949620e+05 2.160800e+05 242852.249510 0 158 0.149750 2021-01-24 3.444725e+05 1.516047e+05 245585.117776 0 159 0.267279 2021-01-31 1.351958e+05 1.238194e+05 265601.756952 0 160 0.380947 2021-02-07 1.343701e+05 2.116608e+05 248820.252306 0 161 0.489113 2021-02-14 1.107157e+05 1.326450e+05 243661.017964 0 162 0.590215 2021-02-21 2.390946e+05 1.204536e+05 274648.400151 0 163 0.682792 2021-02-28 3.677431e+05 1.253575e+05 254981.515806 0 164 0.765507 2021-03-07 1.968231e+05 1.785299e+05 225062.292553 0 165 0.837166 2021-03-14 1.750784e+05 3.667548e+04 236888.864095 0 166 0.896734 2021-03-21 2.848373e+05 2.094940e+05 242672.624940 0 167 0.943350 2021-03-28 1.183038e+05 1.570854e+05 256401.391217 0 168 0.976341 2021-04-04 2.465790e+05 1.830803e+05 245122.335734 0 169 0.995231 2021-04-11 3.441379e+05 1.410329e+05 250977.997496 0 170 0.999746 2021-04-18 2.160167e+05 2.380970e+05 274478.956773 0 171 0.989821 2021-04-25 9.369076e+04 5.558819e+04 227047.171968 0 172 0.965601 2021-05-02 2.576445e+05 2.332610e+05 256482.973451 0 173 0.927434 2021-05-09 2.763834e+05 2.945121e+04 245927.612613 0 174 0.875872 2021-05-16 2.750688e+05 2.015922e+05 275373.440519 0 175 0.811659 2021-05-23 2.728347e+05 2.553521e+05 248861.686229 0 176 0.735724 2021-05-30 2.784224e+05 1.308278e+05 249622.778472 0 177 0.649162 2021-06-06 2.546980e+05 1.921193e+05 248196.469036 0 178 0.553225 2021-06-13 2.297822e+05 3.544338e+04 260218.581156 0 179 0.449297 2021-06-20 9.609488e+04 2.627430e+05 246502.612645 0 180 0.338879 2021-06-27 3.356055e+05 1.732794e+05 246117.691204 0 181 0.223568 2021-07-04 9.761630e+04 1.026479e+05 250492.129535 0 182 0.105027 2021-07-11 3.944771e+05 1.773457e+05 270486.425030 0 183 -0.015031 2021-07-18 1.046504e+05 1.469566e+05 260655.360809 0 184 -0.134872 2021-07-25 1.920452e+05 1.764217e+05 250140.277433 0 185 -0.252764 2021-08-01 1.129333e+05 1.051399e+05 250111.803128 0 186 -0.367006 2021-08-08 1.134429e+05 1.729849e+05 244519.653023 0 187 -0.475947 2021-08-15 3.832926e+05 2.737565e+05 256552.331685 0 188 -0.578014 2021-08-22 2.467442e+05 1.093583e+05 263957.792264 0 189 -0.671733 2021-08-29 1.026868e+05 1.371510e+05 258313.865490 0 190 -0.755750 2021-09-05 3.275985e+05 3.043407e+05 255756.315131 0 191 -0.828851 2021-09-12 2.076493e+05 2.153542e+05 269322.250659 0 192 -0.889981 2021-09-19 2.487868e+05 1.538675e+05 247410.733366 0 193 -0.938256 2021-09-26 1.573702e+05 1.152559e+05 256671.594054 0 194 -0.972981 2021-10-03 1.274701e+05 2.426804e+05 258019.369393 0 195 -0.993652 2021-10-10 3.443403e+05 1.301170e+05 251272.048838 0 196 -0.999972 2021-10-17 2.602334e+05 1.024540e+05 252699.894656 0 197 -0.991849 2021-10-24 1.848510e+05 1.417980e+05 260098.047352 0 198 -0.969400 2021-10-31 3.031779e+05 2.406517e+05 244038.726161 0 199 -0.932951 2021-11-07 9.548359e+04 1.855220e+05 252773.081327 0 200 -0.883026 2021-11-14 3.483452e+05 2.466583e+05 264883.660641 0 201 -0.820348 2021-11-21 1.709500e+05 1.139804e+05 270617.597124 0 202 -0.745821 2021-11-28 4.228808e+05 2.833392e+05 228132.460122 0 203 -0.660522 2021-12-05 1.072361e+05 1.840752e+05 217757.755691 0 204 -0.565683 2021-12-12 3.004597e+05 1.681535e+05 301859.071554 0 205 -0.462674 2021-12-19 9.516851e+03 2.258705e+05 220780.378198 1 206 -0.352983 2021-12-26 1.018596e-16 3.008496e-09 222917.397170 0 207 -0.238193 cos_1 sin_2 cos_2 sin_3 cos_3 sin_4 \ Week 2021-01-03 0.995936 -0.179402 0.983776 -0.267279 0.963619 -0.352983 2021-01-10 0.999548 0.060090 0.998193 0.090067 0.995936 0.119963 2021-01-17 0.988724 0.296123 0.955150 0.435817 0.900035 0.565683 2021-01-24 0.963619 0.515110 0.857124 0.725461 0.688264 0.883026 2021-01-31 0.924597 0.704445 0.709759 0.921708 0.387885 0.999972 2021-02-07 0.872220 0.853229 0.521537 0.999294 0.037570 0.889981 2021-02-14 0.807246 0.952897 0.303293 0.948231 -0.317582 0.578014 2021-02-21 0.730613 0.997713 0.067590 0.775092 -0.631848 0.134872 2021-02-28 0.643427 0.985096 -0.172003 0.502168 -0.864770 -0.338879 2021-03-07 0.546948 0.915773 -0.401695 0.164595 -0.986361 -0.735724 2021-03-14 0.442570 0.793734 -0.608264 -0.194169 -0.980968 -0.965601 2021-03-21 0.331799 0.626005 -0.779819 -0.527935 -0.849285 -0.976341 2021-03-28 0.216236 0.422240 -0.906484 -0.793734 -0.608264 -0.765507 2021-04-04 0.097550 0.194169 -0.980968 -0.957348 -0.288936 -0.380947 2021-04-11 -0.022545 -0.045079 -0.998983 -0.997713 0.067590 0.090067 2021-04-18 -0.142315 -0.281733 -0.959493 -0.909632 0.415415 0.540641 2021-04-25 -0.260029 -0.502168 -0.864770 -0.704445 0.709759 0.868520 2021-05-02 -0.373987 -0.693697 -0.720267 -0.408567 0.912728 0.999294 2021-05-09 -0.482544 -0.845293 -0.534303 -0.060090 0.998193 0.903285 2021-05-16 -0.584131 -0.948231 -0.317582 0.296123 0.955150 0.602282 2021-05-23 -0.677282 -0.996584 -0.082579 0.614213 0.789141 0.164595 2021-05-30 -0.760650 -0.987570 0.157177 0.853229 0.521537 -0.310446 2021-06-06 -0.833032 -0.921708 0.387885 0.982400 0.186791 -0.715033 2021-06-13 -0.893383 -0.802788 0.596265 0.985096 -0.172003 -0.957348 2021-06-20 -0.940830 -0.637656 0.770322 0.860971 -0.508653 -0.982400 2021-06-27 -0.974688 -0.435817 0.900035 0.626005 -0.779819 -0.784502 2021-07-04 -0.994469 -0.208892 0.977939 0.310446 -0.950591 -0.408567 2021-07-11 -0.999887 0.030059 0.999548 -0.045079 -0.998983 0.060090 2021-07-18 -0.990863 0.267279 0.963619 -0.394801 -0.918766 0.515110 2021-07-25 -0.967528 0.489113 0.872220 -0.693697 -0.720267 0.853229 2021-08-01 -0.930218 0.682792 0.730613 -0.903285 -0.429041 0.997713 2021-08-08 -0.879474 0.837166 0.546948 -0.996584 -0.082579 0.915773 2021-08-15 -0.816027 0.943350 0.331799 -0.961583 0.274513 0.626005 2021-08-22 -0.740793 0.995231 0.097550 -0.802788 0.596265 0.194169 2021-08-29 -0.654861 0.989821 -0.142315 -0.540641 0.841254 -0.281733 2021-09-05 -0.559470 0.927434 -0.373987 -0.208892 0.977939 -0.693697 2021-09-12 -0.455998 0.811659 -0.584131 0.149750 0.988724 -0.948231 2021-09-19 -0.345941 0.649162 -0.760650 0.489113 0.872220 -0.987570 2021-09-26 -0.230887 0.449297 -0.893383 0.765507 0.643427 -0.802788 2021-10-03 -0.112498 0.223568 -0.974688 0.943350 0.331799 -0.435817 2021-10-10 0.007516 -0.015031 -0.999887 0.999746 -0.022545 0.030059 2021-10-17 0.127421 -0.252764 -0.967528 0.927434 -0.373987 0.489113 2021-10-24 0.245485 -0.475947 -0.879474 0.735724 -0.677282 0.837166 2021-10-31 0.360005 -0.671733 -0.740793 0.449297 -0.893383 0.995231 2021-11-07 0.469324 -0.828851 -0.559470 0.105027 -0.994469 0.927434 2021-11-14 0.571865 -0.938256 -0.345941 -0.252764 -0.967528 0.649162 2021-11-21 0.666146 -0.993652 -0.112498 -0.578014 -0.816027 0.223568 2021-11-28 0.750806 -0.991849 0.127421 -0.828851 -0.559470 -0.252764 2021-12-05 0.824623 -0.932951 0.360005 -0.972981 -0.230887 -0.671733 2021-12-12 0.886528 -0.820348 0.571865 -0.991849 0.127421 -0.938256 2021-12-19 0.935630 -0.660522 0.750806 -0.883026 0.469324 -0.991849 2021-12-26 0.971218 -0.462674 0.886528 -0.660522 0.750806 -0.820348 cos_4 sin_5 cos_5 Week 2021-01-03 0.935630 -0.435817 0.900035 2021-01-10 0.992778 0.149750 0.988724 2021-01-17 0.824623 0.682792 0.730613 2021-01-24 0.469324 0.976341 0.216236 2021-01-31 0.007516 0.927434 -0.373987 2021-02-07 -0.455998 0.553225 -0.833032 2021-02-14 -0.816027 -0.015031 -0.999887 2021-02-21 -0.990863 -0.578014 -0.816027 2021-02-28 -0.940830 -0.938256 -0.345941 2021-03-07 -0.677282 -0.969400 0.245485 2021-03-14 -0.260029 -0.660522 0.750806 2021-03-21 0.216236 -0.119963 0.992778 2021-03-28 0.643427 0.462674 0.886528 2021-04-04 0.924597 0.883026 0.469324 2021-04-11 0.995936 0.993652 -0.112498 2021-04-18 0.841254 0.755750 -0.654861 2021-04-25 0.495655 0.252764 -0.967528 2021-05-02 0.037570 -0.338879 -0.940830 2021-05-09 -0.429041 -0.811659 -0.584131 2021-05-16 -0.798284 -0.999746 -0.022545 2021-05-23 -0.986361 -0.837166 0.546948 2021-05-30 -0.950591 -0.380947 0.924597 2021-06-06 -0.699090 0.208892 0.977939 2021-06-13 -0.288936 0.725461 0.688264 2021-06-20 0.186791 0.987570 0.157177 2021-06-27 0.620126 0.903285 -0.429041 2021-07-04 0.912728 0.502168 -0.864770 2021-07-11 0.998193 -0.075087 -0.997177 2021-07-18 0.857124 -0.626005 -0.779819 2021-07-25 0.521537 -0.957348 -0.288936 2021-08-01 0.067590 -0.952897 0.303293 2021-08-08 -0.401695 -0.614213 0.789141 2021-08-15 -0.779819 -0.060090 0.998193 2021-08-22 -0.980968 0.515110 0.857124 2021-08-29 -0.959493 0.909632 0.415415 2021-09-05 -0.720267 0.985096 -0.172003 2021-09-12 -0.317582 0.715033 -0.699090 2021-09-19 0.157177 0.194169 -0.980968 2021-09-26 0.596265 -0.394801 -0.918766 2021-10-03 0.900035 -0.845293 -0.534303 2021-10-10 0.999548 -0.999294 0.037570 2021-10-17 0.872220 -0.802788 0.596265 2021-10-24 0.546948 -0.324699 0.945817 2021-10-31 0.097550 0.267279 0.963619 2021-11-07 -0.373987 0.765507 0.643427 2021-11-14 -0.760650 0.995231 0.097550 2021-11-21 -0.974688 0.875872 -0.482544 2021-11-28 -0.967528 0.449297 -0.893383 2021-12-05 -0.740793 -0.134872 -0.990863 2021-12-12 -0.345941 -0.671733 -0.740793 2021-12-19 0.127421 -0.972981 -0.230887 2021-12-26 0.571865 -0.932951 0.360005
最適投資配分の最適化結果の出力
現状と最適化の結果を比較していきます。
先ずは、売上金額とマーケティングROIです。
以下、コードです。
# # 現状と最適配分時の比較(yとマーケティングROI) # result = compare_y_and_marketing_roi( X_optimized, X_actual, y_actual, trained_model, apply_effects_features) # 数値の表示 for key,value in result.items(): print(f"{key}: {value}")
以下、実行結果です。
y_actual_sum: 112534400 y_optimized_sum: 122668392 y_change_percent: +9.01 % roi_actual: 2.39 roi_optimized: 2.70 roi_change_point: +0.31 points
次に、投資配分です。
以下、コードです。
# # 投資配分構成比の比較 # comparison_df = plot_comparative_allocation( X_actual, X_optimized, apply_effects_features) # 数値の表示 print(comparison_df)
以下、実行結果です。
Actual Allocation Optimized Allocation TVCM 0.397076 0.344385 Newspaper 0.339156 0.259919 Web 0.263768 0.395696
まとめ
今回は、「季節性を考慮したRidge MMMパイプラインを構築し最適投資配分を求める方法」のお話しをしました。
ただ季節性は、広告の効果にも影響を及ぼすことでしょう。たとえば、同じ広告コストを投下しても、時期によって売上を伸ばす力が異なるということです。
幾つかやり方があります。
その1つが、季節変数と広告変数の交互作用をモデルに組み込むという方法です。
次回は、その交互作用項を組み込んだRidge MMMパイプラインを構築し最適投資配分を求める方法のお話しをしました。