导读:在日常生活中,我们经常会遇见线上/线下商家推出各类打折、满减、赠品、新人价、优惠券、捆绑销售等促销活动。一次成功的促销对于消费者和商家来说是双赢的。一方面,促销活动能让消费者买到低价的商品;另一方面,促销活动也能为商家带来可观的利润。因此,对于商家来说,如何科学合理地制定促销策略是必须仔细思考的问题。
作者2:向杜兵,算法专家,某制造业龙头
大家好!我们是IndustryOR团队,致力于分享业界落地的OR+AI技术。欢迎关注微信公众号/知乎【运筹匠心】 。本期我们来谈一谈《促销定价背后的算法技术》。促销活动五花八门、玩法多变,但其底层的核心商业逻辑是“价格”。因此,本期案例将选择某零售商超“促销定价”场景,共分5篇文章依次讲解:(1)业务问题拆解;(2)数据预处理生成;(3)数据挖掘分析;(4)模型算法实现-价格弹性模型;(5)模型算法实现-运筹决策模型。
本篇文章讲解(4)模型算法实现-价格弹性模型。
共分为4个部分,依次为:
01 问题聚焦
02 理论介绍
03 代码实现
04 效果验证
注:本案例数据改编自【2019年全国大学生数学建模E题】公开数据集。
01 问题聚焦
经过(1)业务问题拆解的拆解分析可知,促销定价用数学语言可表示为:
max
∑
s
∈
S
(
p
s
−
c
s
)
×
q
s
(
1
)
\max \sum_{s \in S} (p_{s}-c_{s})\times q_{s} \space\space\space (1)
maxs∈S∑(ps−cs)×qs (1)
q
s
=
f
(
p
s
,
θ
s
)
∀
s
∈
S
(
2
)
q_{s}=f(p_{s},\theta_{s}) \space\space\space \forall s \in S \space\space\space (2)
qs=f(ps,θs) ∀s∈S (2)
l
s
≤
p
s
≤
h
s
∀
s
∈
S
(
3
)
l_{s} \leq p_{s} \leq h_{s} \space\space\space \forall s \in S \space\space\space (3)
ls≤ps≤hs ∀s∈S (3)
上述公式中, S S S表示商家售卖商品集合; c s c_{s} cs表示商品 s s s的成本价; p s p_{s} ps表示商品 s s s的售价; q s q_{s} qs表示商品 s s s的销量; f ( p s , θ s ) f(p_{s},\theta_{s}) f(ps,θs)表示商品 s s s销量与商品售价之间的量化函数; l s l_{s} ls和 h s h_{s} hs分别商品合适的价格区间的左右边界价格。式 ( 1 ) (1) (1)表示商家利润最大化,即所有种类商品利润加和最大化;式 ( 2 ) (2) (2)表示每种商品的销量与售价之间的关系;式 ( 3 ) (3) (3)表示每种商品的价格均要在合适的区间。进而,我们明确了实现促销定价需要解决的三个问题:(1)量化出商品价格与销量的关系,即确定 f ( p s , θ s ) f(p_{s},\theta_{s}) f(ps,θs)的具体形式;(2)找到商品合适的价格区间,即确定 l s l_{s} ls和 h s h_{s} hs的取值。(3)选择出能使总利润最大化的不同商品的最佳价格,即求解出能使 ∑ s ∈ S ( p s − c s ) × q s \sum_{s \in S} (p_{s}-c_{s})\times q_{s} ∑s∈S(ps−cs)×qs最大化的 p s p_{s} ps。
经过(3)数据挖掘分析的数据挖掘分析可知:不同sku的价格与销量之间的关系不同,促销定价做的越精细,效果越好。
因此,接下来我们需要解决的问题是:
- 如何精细化地确定价格与销量的关系模型 f f f的形式和模型参数 θ s \theta_{s} θs的取值?
针对以上问题,目前业界落地方案可分为3大类:
类别 | 方法 | 优劣势 |
---|---|---|
白盒方案 | 价格弹性模型 | 可解释性强,量化效果一般 |
黑盒方案 | 机器/深度学习模型 | 量化效果好,可解释性弱 |
融合方案 | 机器/深度学习模型+因果推断 | 量化效果较好,可解释性较强 |
- 白盒方案:可表示为 Q = E ( P , Θ ) Q=E(P,\Theta) Q=E(P,Θ),一般采用价格弹性模型, E E E, P P P为待定价格, Q Q Q为定价 P P P下产生的销量, Θ \Theta Θ为学习参数。此类模型通常可解释性强,但销量 Q Q Q量化效果一般。
- 黑盒方案:可表示为 Q = F ( X , P , Θ ) Q=F(X,P,\Theta) Q=F(X,P,Θ),一般采用机器/深度学习模型,除待定价格 P P P外,还可以引入其他的特征 X X X量化销量 Q Q Q。因此,此类模型量化效果好,但可解释性弱。
- 融合方案:可表示为 Q = Q b a s e + Δ Q Q=Q_{base}+\Delta Q Q=Qbase+ΔQ,通常利用机器/深度学习模型预测基准价 P b a s e P_{base} Pbase下的销量 Q b a s e Q_{base} Qbase,采用因果推断技术无偏估计出定价 P P P与基准价 P b a s e P_{base} Pbase间的销量波动 Δ Q \Delta Q ΔQ,如利用价格弹性计算: Δ Q = E ( P , Θ ) − E ( P b a s e , Θ ) \Delta Q=E(P,\Theta)-E(P_{base},\Theta) ΔQ=E(P,Θ)−E(Pbase,Θ),最终将 Q b a s e Q_{base} Qbase与 Δ Q \Delta Q ΔQ相加得到最终的销量 Q Q Q。该方法既保证了量化效果,又兼顾可解释性。
一般情况下,业界应用会先选择白盒方案的价格弹性模型作为baseline版本,然后逐步向因果推断方案过渡。因此,本文将重点介绍价格弹性模型是如何量化sku的价格与销量之间的关系的。
02 理论介绍
1)价格弹性定义
价格弹性(price elasticity)是经济学领域的重要概念,可分为需求价格弹性、供给的价格弹性、交叉价格弹性、预期价格弹性等各种类型。由于我们本文主要研究价格与销量之间的关系,因为我们接下来将重点研究需求价格弹性。
需求价格弹性(以下简称“价格弹性”)可定义为需求(销量)变动比率与引起其变动的价格变动比率的比率,反映商品价格与市场消费容量的关系,表明价格升降时需求量的增减程度。用数学公式可表示为:
e
=
(
Δ
Q
/
Q
)
/
(
Δ
P
/
P
)
(
1
)
e = (\Delta Q / Q)/(\Delta P / P) \space\space\space (1)
e=(ΔQ/Q)/(ΔP/P) (1)
公式(1)中,
e
e
e代表价格弹性;
Q
Q
Q表示销量,
Δ
Q
\Delta Q
ΔQ表示销量的变化量,因此
Δ
Q
/
Q
\Delta Q / Q
ΔQ/Q则表示销量变动比率;
P
P
P表示价格,
Δ
P
\Delta P
ΔP表示价格的变化量,因此
Δ
P
/
P
\Delta P / P
ΔP/P则表示价格变动比率。
通过观察以上公式,我们可用通俗的语言描述价格弹性:价格 P P P每增加(减少)1%所能带来的销量 Q Q Q增加(减少)的比例。
2) l n P lnP lnP - l n Q lnQ lnQ线性回归
我们已经知道了价格弹性的定义,但是如何求解价格弹性呢?答案就是: l n P lnP lnP - l n Q lnQ lnQ线性回归。接下来我们逐步推导证明。
首先回顾一下导数的定义:
d
y
/
d
x
=
lim
Δ
x
−
>
0
(
Δ
y
/
Δ
x
)
(
1
)
dy / dx = \lim_{\Delta x->0}(\Delta y / \Delta x ) \space\space\space (1)
dy/dx=Δx−>0lim(Δy/Δx) (1)
l
n
P
lnP
lnP -
l
n
Q
lnQ
lnQ线性回归可表示为:
l
n
Q
=
e
∗
l
n
P
+
b
(
2
)
lnQ = e * lnP + b \space\space\space (2)
lnQ=e∗lnP+b (2)
等式两边求导可得:
(
1
/
Q
)
∗
(
d
Q
/
d
P
)
=
e
∗
(
1
/
P
)
(
3
)
(1/Q) * (dQ /dP) = e * (1 / P) \space\space\space (3)
(1/Q)∗(dQ/dP)=e∗(1/P) (3)
当
Δ
P
\Delta P
ΔP较小时,可近似写为:
(
1
/
Q
)
∗
(
Δ
Q
/
Δ
P
)
=
e
∗
(
1
/
P
)
(
4
)
(1/Q) * (\Delta Q / \Delta P) = e * (1 / P) \space\space\space (4)
(1/Q)∗(ΔQ/ΔP)=e∗(1/P) (4)
等号两侧交换并整理可得:
e
/
P
=
Δ
Q
/
(
Δ
P
∗
Q
)
(
5
)
e / P = \Delta Q /(\Delta P* Q) \space\space\space (5)
e/P=ΔQ/(ΔP∗Q) (5)
将
P
P
P移至等式右侧可得:
e
=
(
Δ
Q
/
Q
)
/
(
Δ
P
/
P
)
(
6
)
e = (\Delta Q / Q)/(\Delta P / P) \space\space\space (6)
e=(ΔQ/Q)/(ΔP/P) (6)
而式(6)正是价格弹性的定义。
至此,我们可以看出: l n P lnP lnP - l n Q lnQ lnQ的线性回归的回归参数 e e e就是我们想求解的价格弹性。
接下来我们将用代码实现这一过程。
- 注:除了 l n P lnP lnP - l n Q lnQ lnQ线性回归,价格弹性模型还有多种其他的实现形式,大家如果有兴趣可以深入研究。
03 代码实现
1)特征工程
将销量和价格取 l n ln ln。
# 特征工程,将销量和价格取ln
elasticity_data_df['sale_prc_lg'] = np.log(elasticity_data_df['sale_prc'])
elasticity_data_df['sale_cnt_lg'] = np.log(elasticity_data_df['sale_cnt'])
2)切分训练集和测试集
训练集:2018-03-01 至 2018-08-31
测试集:2018-09-01 至 2018-09-30
all_train_df = elasticity_data_df[(elasticity_data_df['sale_dt'] < '2018-09-01')].reset_index()
all_test_df = elasticity_data_df[elasticity_data_df['sale_dt'] >= '2018-09-01'].reset_index()
3)训练集分组
将训练集按照(3)数据挖掘分析中聚类得出的聚类簇维度、3级品类维度和sku维度分组。方便之后的对比分析。
# cluster/c3/sku分组
cluster_to_train_df = {}
for cluster in set(elasticity_data_df['label'].values.tolist()):
train_df = all_train_df[all_train_df['label'] == cluster]
if len(train_df) > 0:
cluster_to_train_df[cluster] = train_df
c3_to_train_df = {}
for c3 in set(elasticity_data_df['cate3_id'].values.tolist()):
train_df = all_train_df[all_train_df['cate3_id'] == c3]
if len(train_df) > 0:
c3_to_train_df[c3] = train_df
sku_to_train_df = {}
for sku_id in set(elasticity_data_df['sku_id'].values.tolist()):
train_df = all_train_df[all_train_df['sku_id'] == sku_id]
if len(train_df) > 0 :
sku_to_train_df[sku_id] = train_df
分组后,训练集可分为:4个聚类簇分组、547个3级品类分组、2628个sku分组
3)回归价格弹性
选择岭回归(Ridge Regression,L2正则化的线性回归)回归价格弹性。
# 回归价格弹性,新加特征必须与价格正交
# 回归函数
def train_ridge_reg_model(key_to_train_df):
key_to_model = {}
for key,train_df in key_to_train_df.items():
train_df = train_df[['sale_prc_lg', 'sale_cnt_lg']]
# 定义特征和目标变量
X_train = train_df.drop('sale_cnt_lg', axis=1)
y_train = train_df['sale_cnt_lg']
# 创建岭回归模型并拟合数据
ridge_reg = Ridge(alpha=0.1)
ridge_reg.fit(X_train, y_train)
key_to_model[key] = ridge_reg
return key_to_model
# 分组回归价格弹性模型
cluster_to_model = train_ridge_reg_model(cluster_to_train_df) # cluster 模型
c3_to_model = train_ridge_reg_model(c3_to_train_df) # c3 模型
sku_to_model = train_ridge_reg_model(sku_to_train_df) # sku 模型
04 效果分析
1)模型预测
# 预测函数
# 聚类簇分组预测
def predict_cluster(row):
cluster = row['label']
model = cluster_to_model.get(cluster, None) # label 兜底
ret = model.predict([[row['sale_prc_lg']]])[0]
ret = np.exp(ret)
return ret
# 3级品类分组预测
def predict_c3(row):
c3 = row['cate3_id']
cluster = row['label']
model = c3_to_model.get(c3, None) # c3兜底
if model is None:
model = cluster_to_model.get(cluster, None) # label 兜底
ret = model.predict([[row['sale_prc_lg']]])[0]
ret = np.exp(ret)
return ret
# sku分组预测
def predict_sku(row):
sku = row['sku_id']
c3 = row['cate3_id']
cluster = row['label']
model = sku_to_model.get(sku, None)
if model is None:
model = c3_to_model.get(c3, None) # c3兜底
if model is None:
model = cluster_to_model.get(cluster, None) # cluster 兜底
ret = model.predict([[row['sale_prc_lg']]])[0]
ret = np.exp(ret)
return ret
# 预测
all_test_df['cluster_y_hat'] = all_test_df.apply(func=predict_cluster, axis=1)
all_test_df['c3_y_hat'] = all_test_df.apply(func=predict_c3, axis=1)
all_test_df['sku_y_hat'] = all_test_df.apply(func=predict_sku, axis=1)
# 四舍五入取整
all_test_df['cluster_y_hat'] = all_test_df['cluster_y_hat'].round(0)
all_test_df['c3_y_hat'] = all_test_df['c3_y_hat'].round(0)
all_test_df['sku_y_hat'] = all_test_df['sku_y_hat'].round(0)
2)计算 W M A P E WMAPE WMAPE
W
M
A
P
E
WMAPE
WMAPE(Weighted Mean Absolute Percentage Error)是指带权重的平均绝对百分比误差,是用来做销量预测最常用的指标,越小越好。公式如下:
W
M
A
P
E
=
∑
n
∣
y
′
−
y
∣
/
∑
n
y
WMAPE=\sum_{n} |y^{'} - y| / \sum_{n} y
WMAPE=n∑∣y′−y∣/n∑y
分别计算聚类簇维度、3级品类维度和sku维度的
W
M
A
P
E
WMAPE
WMAPE值。
# wmape计算公式
def call_wmape(y_true, y_pred):
y_true, y_pred = np.array(y_true), np.array(y_pred)
return np.sum(np.abs(y_true - y_pred)) / np.sum(y_true)
# 评测,计算wmape
cluster_y_pred = all_test_df['cluster_y_hat']
c3_y_pred = all_test_df['c3_y_hat']
sku_y_pred = all_test_df['sku_y_hat']
y_test = all_test_df['sale_cnt']
cluster_wmape = call_wmape(y_test, cluster_y_pred)
c3_wmape = call_wmape(y_test, c3_y_pred)
sku_wmape = call_wmape(y_test, sku_y_pred)
结果如下:
分组维度 | W M A P E WMAPE WMAPE |
---|---|
聚类簇 | 0.7379 |
3级品类 | 0.6772 |
sku | 0.6198 |
我们发现:对于 W M A P E WMAPE WMAPE值,聚类簇维度 < 3级品类维度 < sku维度,这也验证了(3)数据挖掘分析的数据挖掘的结论:不同sku的价格与销量之间的关系不同,促销定价做的越精细,效果越好。 sku维度粒度最细,效果也最好。
05 小结
第一篇(业务问题拆解):我们把一个实际的促销定价问题拆解成了一系列的数学问题。
第二篇(数据预处理生成):我们选择了一份公开的促销定价数据集,将其加工成了可分析求解的数据。
第三篇(数据挖掘分析):我们对数据进行了全方位的挖掘和分析,介绍了数据挖掘分析和可视化方法。
本篇(价格弹性模型):关于如何量化商品价格与销量的关系,我们详细介绍了baseline方案-价格弹性模型。
下一篇(数据挖掘分析):我们将介绍如何用运筹模型对量化结果进行最终的定价决策。敬请期待~~~
06 代码获取方式
大家将推文转发朋友圈获X赞,截图发后台,管理员会定期回复数据代码连接哈~~~
获20赞 :获取 本篇代码地址
我们是**【运筹匠心】** ,咱们下期见~~~
07 加粉丝群方式
粉丝1群二维码:
加不了群,请加管理员微信:IndustryOR
参考文献
- Hua J, Yan L, Xu H,et al. Markdowns in E-Commerce Fresh Retail: A Counterfactual Prediction and Multi-Period Optimization Approach[J]. arxiv, 2021.(https://arxiv.org/pdf/2105.08313.pdf)
- Kui Zhao, Junhao Hua, Ling Yan, et al. A Unified Framework for Marketing Budget Allocation[J]. arxiv, 20.(https://arxiv.org/pdf/1902.01128.pdf)
- 用相关系数进行Kmeans聚类,利用利润率、打折率、销售额、毛利润得到商品价格弹性标签,建立价格折扣力度模型(https://blog.csdn.net/weixin_45934622/article/details/114382037)
- 2019全国大学生数学建模竞赛讲评:“薄利多销”分析(https://dxs.moe.gov.cn/zx/a/hd_sxjm_sxjmstjp_2019sxjmstjp/210604/1699445.shtml)
- 策略算法工程师之路-基于线性规划的简单价格优化模型(https://zhuanlan.zhihu.com/p/145192690)