ダイナミックプライシングとブッキングカーブ:価格戦略の最適化

はじめに

今日のビジネス環境では、データサイエンスが企業の価格戦略を形成する上で重要な役割を果たしています。その一例がダイナミックプライシングです。この記事では、ダイナミックプライシングの基本と、それを実現するための重要なツールであるブッキングカーブについて解説します。

ダイナミックプライシングの基本

ダイナミックプライシングとは、収益の最大化を目的に、需要と供給に合わせて価格を変動させる価格戦略のことです。この戦略は、価格変更が容易で、在庫が希少な商品やサービス、または顧客が価格変動を理解している市場において特に有効です。航空ホテル、家電量販店、アパレルなど多くの業界で利用されています。

ブッキングカーブとは

ブッキングカーブは、予約状況を予約受付開始からの日数経過とともにグラフにしたもので、どのくらいのスピードで予約が入ってきているのかを視覚的に理解するためのツールです。Pythonのpandasライブラリを使って簡単に作成できます。

以下に具体的なコードを示します。なお、booking_data.csvにはある仮想ホテルの予約受付開始日から宿泊日までの30日間の予約件数が日ごとに入っています。またこのホテルの部屋数は20だとします。

実際にコードを試してみたい人は以下のような内容のcsvファイル「booking_data.csv」を作成してください。

予約受付日,宿泊日,予約数
2023-04-15,2023-05-15,1
2023-04-15,2023-05-15,1
2023-04-15,2023-05-15,1
2023-04-16,2023-05-15,1
2023-04-16,2023-05-15,1
2023-04-16,2023-05-15,1
2023-04-17,2023-05-15,1
2023-04-17,2023-05-15,1
2023-04-18,2023-05-15,1
2023-04-19,2023-05-15,1
2023-04-19,2023-05-15,1
2023-04-20,2023-05-15,1
2023-04-20,2023-05-15,1
2023-04-21,2023-05-15,1
2023-04-21,2023-05-15,1
2023-04-22,2023-05-15,1
2023-04-23,2023-05-15,1
2023-04-23,2023-05-15,1
2023-04-24,2023-05-15,1
2023-04-25,2023-05-15,1
2023-05-17,2023-06-15,1
2023-05-26,2023-06-15,1
2023-05-29,2023-06-15,1
2023-05-30,2023-06-15,1
2023-06-02,2023-06-15,1
2023-06-07,2023-06-15,1
2023-06-11,2023-06-15,1
2023-06-12,2023-06-15,1

# 必要なライブラリのインポート
import pandas as pd
import matplotlib.pyplot as plt

# データの読み込み
df = pd.read_csv("booking_data.csv", parse_dates=["予約受付日","宿泊日"])

# 指定日付による絞り込み
df_part = df[df["宿泊日"] == "2023-06-15"]

# 予約受付日毎の予約数の集計
vc = df_part["予約受付日"].value_counts()

# 累計の予約数の計算
vcc = vc.sort_index().cumsum()

# グラフの作成
vcc.plot()

# グラフの表示
plt.show()

これは「宿泊日=2023/6/15」の予約状況です。このように日付ごとに累積予約数が表示されますが、1つの宿泊日の予約だけ見てもよくわからないですよね。

複数日のブッキングカーブの比較

宿泊日による予約の入り方の差異を見るためにもう1日ブッキングカーブを追加して比較してみます。比較のために予約開始日からの経過日数を横軸に設定し、予約がない日でも日付を追加してブッキングカーブを描き出します。

以下はそのコードです。

# 必要なライブラリのインポート
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt

# データの読み込み
df = pd.read_csv("booking_data.csv", parse_dates=["予約受付日","宿泊日"])

# 宿泊日による絞り込み
for stay_date in [pd.Timestamp(2023, 5, 15), pd.Timestamp(2023, 6, 15)]:
    df_part = df[df["宿泊日"] == stay_date]

    # 予約受付開始日と終了日の計算
    start_date = df_part["予約受付日"].min()
    end_date = start_date + pd.Timedelta(days=30)

    # 連続する日付の作成
    new_index = pd.date_range(start=start_date, end=end_date, freq="D")

    # 予約受付日毎の予約数の集計
    vc = df_part["予約受付日"].value_counts()

    # 集計値のインデックスの再構築
    vc = vc.reindex(new_index, fill_value=0)

    # 累計の予約数の計算
    vcc = vc.cumsum()

    # 受付開始何日目の計算
    vcc.index = (vcc.index - vcc.index[0]).days

    # グラフの作成
    vcc.plot(label=f'Stay date: {stay_date.date()}')

# 凡例の表示
plt.legend()

# 横軸の範囲の設定
plt.xlim(0, 30)

# グラフの表示
plt.show()

ここで、2023-05-15の予約は最初の10日で20部屋全部屋が埋まってしまった一方で、先ほどの2023-06-15の方は最終的に予約数を一桁しか獲得できていないことがわかります。前者はもっと価格を上げても予約が入ったのではないかと推察できますし、後者は価格を抑えればもっと予約が入ったかもしれません。

これらを価格調整によって理想的な予約の入り方にすることで利益を最大化しようというのがダイナミックプライシングの考え方になります。

理想的なブッキングカーブ

この仮想ホテルにとっての理想的なブッキングカーブは、予約受付開始日からコンスタントに予約が入り、宿泊日の3日前までに予約が埋まるものと定義します。この理想的なブッキングカーブと、実際のブッキングカーブを比較することで、価格戦略の改善点を見つけることができます。

以下のコードでは、予約開始から宿泊日の3日前までに最大値である20の予約が入る理想的なブッキングカーブを定義しています(最後の2日は20のまま)。

import numpy as np
import matplotlib.pyplot as plt

# 理想のブッキングカーブの作成
target_booking_curve = np.append(np.linspace(0, 20, 28), [20,20])

# グラフの作成
plt.plot(target_booking_curve, label='Ideal Booking Curve')
plt.xlabel('Days since booking start')
plt.ylabel('Number of bookings')
plt.title('Ideal Booking Curve')
plt.legend()
plt.show()

価格調整方針の設計

さて、理想的なブッキングカーブが出来上がったら、いよいよダイナミックプライシングの主要作業である価格調整方針の設計です。ここでは、需要と供給のバランスを取りながら、最大の収益を達成するための価格設定を行います

具体的には先ほどの理想的なブッキングカーブを基に、現在の予約状況に応じて価格を調整する関数を定義します。この関数では、現在の理想的な予約数よりも実際の予約数が少なければ価格を下げ、反対に理想的な予約数よりも実際の予約数が多ければ多くを上げます。ただし、土日は予約が多いので値下げはせず、また極端な価格にならないように価格制限を設けています。

# pandasのインポート
import pandas as pd

# 変動価格決定関数の定義
def dynamic_pricing(current_date, current_price, current_rooms, elapsed_days):
    
    # 現時点での理想の予約数
    target_rooms = target_booking_curve[elapsed_days]

    # 現在の予約数と理想の予約数との差
    diff_rooms = current_rooms - target_rooms
    # 現在の曜日
    current_week = current_date.strftime("%a")
                
    # 予約数が理想よりも2つ下回っている場合
    if diff_rooms <= -2:
        # 土曜・日曜の場合は現状維持
        if current_week == "Sat" or  current_week == "Sun":
            new_price = current_price
        # 土曜・日曜以外の場合は、1000円値下げ
        else:
            new_price = current_price - 1000
            
    # 予約数が理想よりも2つ上回っている場合は1000円値上げ
    elif diff_rooms >= 2: 
        new_price = current_price + 1000
        
    # 理想の予約数との乖離が2未満の場合は現状維持
    else:
        new_price = current_price
        
    # 変更後の宿泊料金が上限の20000円を上回っている場合は、上限値を変更後の価格とする
    if new_price > 20000:
        new_price = 20000
    # 変更後の宿泊料金が下限の5000円を下回っている場合は、下限値を変更後の価格とする
    elif new_price < 5000:
        new_price = 5000
        
    return new_price

この関数を使って、特定の日付に対する新しい価格を計算することができるので、日々設定すべき価格がわかります。

# 関数の実行
print(dynamic_pricing(pd.Timestamp("2023-06-15"), 13000, 5, 10))

このような価格調整方針を元に運用を行い、その結果を確認して運営方針を見直すというサイクルを繰り返すことで収益最大化を目指すことになります。

まとめ

このようにダイナミックプライシングを用いることで、ホテルなどの収益を最大化することが可能です。具体的な価格調整方針は業界の特性や市場状況により異なるため、それらに合わせて最適な方針を設計することが重要です。もし在庫が限られる商品の価格を自由に設定できるような機会があればぜひ試してみてください。

最新情報をチェックしよう!