エントロピーモデルを応用した、価格による需要調整とPythonによる実装

https://logics-of-blue.com/entropy-model-for-price-setting-with-python/

In [1]:
# ライブラリを読み込む
import scipy as sp
import numpy as np

from scipy.optimize import minimize # 最適化
from scipy.stats import entropy     # エントロピー計算

# 表示桁数の設定
%precision 3
Out[1]:
'%.3f'

情報エントロピーの計算

In [2]:
# エントロピー計算
probs = np.array([0.8, 0.2])

H = -1*probs[0]*sp.log(probs[0]) - probs[1]*sp.log(probs[1])
print('情報エントロピー')
print(H)

print('情報エントロピー(関数使用)')
print(entropy(probs))
情報エントロピー
0.500402423538
情報エントロピー(関数使用)
0.500402423538
In [3]:
# 最も大きなエントロピー
probs = np.array([0.5, 0.5])
entropy(probs)
Out[3]:
0.693

1因子情報路

In [4]:
# 価格の比率
cost_ratio = np.array([1,3])

# 目的関数
def target_func(user_ratio):
    return (float)(-1 * entropy(user_ratio)/(sum(user_ratio * cost_ratio)))

# 制約条件式
# 「0よりも大きい」を表すために、小さな値を足しこんでいる(デフォルトだと0以上の設定)
cons = (
    {'type': 'ineq','fun': lambda p: p[0]},
    {'type': 'ineq','fun': lambda p: p[1]},
    {'type': 'eq',  'fun': lambda p: p[0] + p[1] - 1}
)

# 初期値
p = np.array([0.5,0.5])

# ソルバーを実行
result = minimize(target_func, 
                  x0=p, 
                  constraints=cons, 
                  method="SLSQP",
                  options={'maxiter': 10000, 'ftol': 1e-10})

# 結果の表示
print('エントロピーモデルにより計算された購入比率')
print(result.x)
エントロピーモデルにより計算された購入比率
[ 0.682  0.318]

1因子情報路モデルの関数化

In [5]:
# エントロピーモデルの1因子情報路モデルの推定
def entropy_model_one(cost_ratio):
    '''
    エントロピーモデルの1因子情報路モデルの推定

    Arguments:

    cost_ratio -- 商品コストの比率。金額の比率や消費時間の比率等。
    
    '''

    # ランクの数
    rank_num = len(cost_ratio)

    # 最適化の目的関数
    target_func = lambda user_ratio: (float)(-1 * entropy(user_ratio)/(sum(user_ratio * cost_ratio)))

    # 制約条件の作成
    construction = []

    # すべての確率が0以上という制約
    for i in range(0, rank_num):
        construction.append({'type': 'ineq','fun': lambda p: p[i]})

    # 確率の合計が1であるという制約
    construction.append({'type': 'eq','fun': lambda p: sum(p) - 1})

    # 初期値
    p0 = np.tile(1/rank_num, rank_num)

    # ソルバーを実行
    result = minimize(fun=target_func, 
                      x0=p0, 
                      constraints=construction, 
                      method="SLSQP",
                      options={'maxiter': 10000, 'ftol': 1e-10})

    return result
In [6]:
# 1因子情報路モデルによる理論上の購入比率
print(entropy_model_one(np.array([1,3])).x)
[ 0.682  0.318]
In [7]:
# コスト比率を変えて、商品Bをさらに2倍お得にした時のユーザー比率
print(entropy_model_one(np.array([1,6])).x)
[ 0.778  0.222]
In [8]:
# コスト比率を細かく分けた時
print(entropy_model_one(np.array([1,2,3,4,5])).x)
[ 0.509  0.259  0.132  0.067  0.034]

層別化:固定層がいる場合の購入比率

In [9]:
# 価格の比率
cost_ratio = np.array([3,1])
print('価格の比率')
print(cost_ratio)

# 理論上の選択率
user_ratio_by_cost = entropy_model_one(cost_ratio).x
print('金額の比率から求められる、理論上の商品選択率')
print(user_ratio_by_cost)
価格の比率
[3 1]
金額の比率から求められる、理論上の商品選択率
[ 0.318  0.682]
In [10]:
# 実際の選択率
true_user_ratio = np.array([0.7,0.3])
print('実際の選択率')
print(true_user_ratio)
実際の選択率
[ 0.7  0.3]
In [11]:
# 目的関数(エントロピー最大化)
def target_function(p):
    return (float)(-1*(entropy(p) + p[2]*entropy(user_ratio_by_cost)))


# 制約条件:確率は0以上で合計値は1
# 制約条件:実際の選択率と等しくなる
cons = (
    {'type': 'ineq','fun': lambda p: p[0]},
    {'type': 'ineq','fun': lambda p: p[1]},
    {'type': 'ineq','fun': lambda p: p[2]},
    {'type': 'eq',  'fun': lambda p: p[0] + p[1] + p[2] - 1},
    {'type': 'eq',  'fun': lambda p: p[0] + p[2] * user_ratio_by_cost[0] - true_user_ratio[0]}
)

# 初期値
p = np.array([0.7,0.3,0])

# ソルバーを実行
stable_user_ratio = minimize(target_function, 
                             x0=p, 
                             constraints=cons, 
                             method="SLSQP",
                             options={'maxiter': 10000, 'ftol': 1e-10})

print('商品Aの固定層:商品Bの固定層:価格によって購入するものを変える層')
print(stable_user_ratio.x)
商品Aの固定層:商品Bの固定層:価格によって購入するものを変える層
[ 0.602  0.09   0.308]
In [12]:
# 実際の選択率と一致していることの確認
stable_user_ratio.x[0] + stable_user_ratio.x[2]*user_ratio_by_cost[0]
Out[12]:
0.700
In [13]:
# 実際の選択率と一致していることの確認
stable_user_ratio.x[1] + stable_user_ratio.x[2]*user_ratio_by_cost[1]
Out[13]:
0.300

多因子情報路モデル

In [14]:
# 価格の比率
cost_ratio = np.array([1,2,4])
print('価格の比率')
print(cost_ratio)

# 所要時間の比率
time_ratio = np.array([8,3,1])
print('所要時間の比率')
print(time_ratio)

# 価格の比率に基づく理論上の選択率
user_ratio_by_cost = entropy_model_one(cost_ratio).x
print('価格の比率に基づく理論上の選択率')
print(user_ratio_by_cost)

# 所要時間の比率に基づく理論上の選択率(無理やり並び替えた)
user_ratio_by_time = entropy_model_one(time_ratio).x
print('所要時間の比率に基づく理論上の選択率')
print(user_ratio_by_time)

# 実際の選択率
true_user_ratio = np.array([0.1,0.4,0.5])
print('実際の選択率')
print(true_user_ratio)
価格の比率
[1 2 4]
所要時間の比率
[8 3 1]
価格の比率に基づく理論上の選択率
[ 0.57   0.325  0.105]
所要時間の比率に基づく理論上の選択率
[ 0.039  0.295  0.666]
実際の選択率
[ 0.1  0.4  0.5]
In [15]:
# 目的関数(エントロピー最大化)
def target_function(p):
    return (float)(-1*(entropy(p) + p[3]*entropy(user_ratio_by_cost) + p[4]*entropy(user_ratio_by_time)))

# 制約条件:実際の選択率と等しくなる
cons = (
    {'type': 'ineq','fun': lambda p: p[0]},
    {'type': 'ineq','fun': lambda p: p[1]},
    {'type': 'ineq','fun': lambda p: p[2]},
    {'type': 'ineq','fun': lambda p: p[3]},
    {'type': 'ineq','fun': lambda p: p[4]},
    {'type': 'eq',  'fun': lambda p: p[0] + p[1] + p[2] + p[3] + p[4] - 1},
    {'type': 'eq',  'fun': lambda p: p[0] + p[3]*user_ratio_by_cost[0] + p[4]*user_ratio_by_time[0] - true_user_ratio[0]},
    {'type': 'eq',  'fun': lambda p: p[1] + p[3]*user_ratio_by_cost[1] + p[4]*user_ratio_by_time[1] - true_user_ratio[1]}
)

# 初期値
p0 = np.array([0.5,0.3,0.2,0,0])

# ソルバーを実行
user_pattern_ratio = minimize(target_function, 
                              x0=p0, 
                              constraints=cons, 
                              method="SLSQP",
                              options={'maxiter': 10000, 'ftol': 1e-10})

print('商品Aの固定層:商品Bの固定層:商品Cの固定層:価格によって変える層:消費時間によって変える層')
print(user_pattern_ratio.x)
商品Aの固定層:商品Bの固定層:商品Cの固定層:価格によって変える層:消費時間によって変える層
[ 0.015  0.237  0.209  0.121  0.418]