【促销定价】背后的算法技术4-价格弹性模型

导读:在日常生活中,我们经常会遇见线上/线下商家推出各类打折、满减、赠品、新人价、优惠券、捆绑销售等促销活动。一次成功的促销对于消费者和商家来说是双赢的。一方面,促销活动能让消费者买到低价的商品;另一方面,促销活动也能为商家带来可观的利润。因此,对于商家来说,如何科学合理地制定促销策略是必须仔细思考的问题。


作者1:张哲铭,算法专家,某互联网大厂
作者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) maxsS(pscs)×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)   sS   (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) lspshs   sS   (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} sS(pscs)×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(Δyx)   (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=elnP+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)(ΔQP)=e(1/P)   (4)

等号两侧交换并整理可得:
e / P = Δ Q / ( Δ P ∗ Q )     ( 5 ) e / P = \Delta Q /(\Delta P* Q) \space\space\space (5) e/P=ΔQ/(ΔPQ)   (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 WMAPEWeighted Mean Absolute Percentage Error)是指带权重的平均绝对百分比误差,是用来做销量预测最常用的指标,越小越好。公式如下:
W M A P E = ∑ n ∣ y ′ − y ∣ / ∑ n y WMAPE=\sum_{n} |y^{'} - y| / \sum_{n} y WMAPE=nyy∣/ny
分别计算聚类簇维度、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
sku0.6198

我们发现:对于 W M A P E WMAPE WMAPE值,聚类簇维度 < 3级品类维度 < sku维度,这也验证了(3)数据挖掘分析的数据挖掘的结论:不同sku的价格与销量之间的关系不同,促销定价做的越精细,效果越好。 sku维度粒度最细,效果也最好。


05 小结

第一篇(业务问题拆解):我们把一个实际的促销定价问题拆解成了一系列的数学问题。

第二篇(数据预处理生成):我们选择了一份公开的促销定价数据集,将其加工成了可分析求解的数据。

第三篇(数据挖掘分析):我们对数据进行了全方位的挖掘和分析,介绍了数据挖掘分析和可视化方法。

本篇(价格弹性模型):关于如何量化商品价格与销量的关系,我们详细介绍了baseline方案-价格弹性模型。

下一篇(数据挖掘分析):我们将介绍如何用运筹模型对量化结果进行最终的定价决策。敬请期待~~~


06 代码获取方式

大家将推文转发朋友圈获X赞,截图发后台,管理员会定期回复数据代码连接哈~~~

获20赞 :获取 本篇代码地址

我们是**【运筹匠心】** ,咱们下期见~~~


07 加粉丝群方式

粉丝1群二维码

加不了群,请加管理员微信:IndustryOR


参考文献
  1. 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)
  2. 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)
  3. 用相关系数进行Kmeans聚类,利用利润率、打折率、销售额、毛利润得到商品价格弹性标签,建立价格折扣力度模型(https://blog.csdn.net/weixin_45934622/article/details/114382037)
  4. 2019全国大学生数学建模竞赛讲评:“薄利多销”分析(https://dxs.moe.gov.cn/zx/a/hd_sxjm_sxjmstjp_2019sxjmstjp/210604/1699445.shtml)
  5. 策略算法工程师之路-基于线性规划的简单价格优化模型(https://zhuanlan.zhihu.com/p/145192690)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值