Press "Enter" to skip to content

掌握定价优化的艺术-数据科学解决方案

揭开零售业定价优化的真实数据科学解决方案的秘密

作者提供的图片

目录:

1. 概述2. 弹性建模3. 优化

1. 概述

定价在商业世界中起着非常关键的作用。在任何企业的成功中,平衡销售和利润率非常重要。我们如何用数据科学的方式来做到这一点呢?在本部分中,我们将建立对定价优化有效数据科学解决方案的直觉,然后我们将详细介绍每个组件的细节和代码。

注意-有不同类型的定价策略,但在本文中,我们将重点讨论为具有足够的价格变动历史数据的传统企业/知名品牌构建定价策略。在进入细节之前,让我们先看一下我们试图遵循的基本方法-

作者提供的图片

我们已经绘制了项目1的销售和价格。在过去的9个月里,价格已经变动了2次,很明显,我们可以看到对销售的影响。当价格较低时,销售量较高。现在的问题是如何量化过去价格变动对销售的影响,并预测未来项目的最佳价格。

一个有趣的观察结果是,从一月到四月,价格已固定为5美元,但我们仍然观察到销售波动。这是非常正常的,因为在实际世界中,有许多外部因素会影响销售,如季节性、节假日、促销活动、市场支出等。因此,我们不模拟实际销售,而是使用不同的模型来推导出基线销售。

作者提供的图片

您可以观察到我们正在查看基线销售系列中更平滑的销售趋势。这是100%准确的吗?肯定不是!数据科学就是关于我们能够接近现实的程度。现在让我们继续流程-

假设我们被雇佣并要求为Retailmart集团的各个商店提供数千种商品的价格。同一商品在不同商店的价格可能会有所不同。公司已经为过去5年提供了我们的数据。解决这个问题的方法应该是什么?

让我们用一个价格表的例子来理解这个问题。假设我们有一个价格表,我们已经设定了最低值和最高值,表针可以在这两个极端之间移动。目前,表针指向当前价格。我们的目标是将表针停在一个可以最大化利润的位置。

现在,当我们将表针向右移动时(即增加价格),销售量将开始下降,利润率将增加,但是

  1. 我们可以量化这种下降吗?是的,我们可以,这被称为商品的价格弹性。简单来说,商品的价格弹性是指价格变动1%对销售的百分比变动。
  2. 在现实世界中,销售通常受促销活动、节假日、额外折扣等驱动,但为了优化价格,我们需要排除所有这些外部因素的影响并计算基线销售。
  3. 一旦我们量化了销售与价格变动的关系,我们需要找到答案,我在哪里停下表针?为此,我们需要一个目标,大多数情况下是最大化利润。利润=销售量*利润率,因此我们需要在能够实现最大利润的位置停下。从数学上讲,这是一种非线性优化的概念,值可以在界限内移动。
  4. 业务规则很重要,我们必须确保最终推荐的价格符合这些规则。

所以这些是我们将遵循的主要步骤,以确定给定商店中每个商品的正确价格。让我们更详细地看一下这些步骤:

1. 基线销售/基本单位

这一步是后续步骤的预备步骤。正如所述,我们希望模拟价格变动对销售的影响。理想情况是仅价格影响销售,但在实际世界中,情况从来没有这样。

所以我们想要模拟我们理想情况下的销售,我们使用以下方程式的时间序列模型来完成:

销售 ~ 函数[基线销售 + (促销效应) + (节假日效应) + (其他效应)]

请注意,有时我们没有关于影响销售的外部因素的实际数据。在这种情况下,我们可以使用虚拟变量来考虑所有这些因素。一个简单的例子是,如果在某个月份,我们看到销售额突然增加,但价格保持不变,我们可以引入一个简单的虚拟变量,该变量在该月份为1,其他月份为0。

2. 价格弹性

价格弹性是指对于给定商店中商品价格的变动,销售额相对于价格变动的百分比变化。

举个例子,考虑牛奶和ABC绿茶这两个产品。你认为哪个产品的价格弹性较高?

牛奶是一种日常必需品,竞争激烈,显示出较高的价格弹性。即使价格稍微变动,也能对销售产生显著影响,因为需求广泛。另一方面,ABC绿茶可能只在少数几家商店出售,价格弹性较低。对于ABC绿茶来说,价格的小幅变动不太可能对销售产生重大影响,因为它的市场定位比较独特。

我们将如何建模呢?

基线销售 ~ 函数[价格 + 趋势]

价格变量的系数将用作价格弹性。趋势变量用于解释销售额增长的长期趋势,而不仅仅是由于价格变动。我们将在下面的价格弹性部分中详细讨论计算弹性的更多细节。

3. 边界内的非线性优化

在这一步中,我们将找到应该停在哪个价格点上的答案。

我们首先定义我们的目标函数 — 最大化利润

然后我们定义价格计量器的起点和终点,定义价格的下限和上限

我们已经计算了基线销售和价格弹性,量化了销售对价格的敏感性。我们将把所有这些输入放入我们的非线性优化函数中,然后得到优化后的价格。

简单来说,算法将在边界内尝试不同的价格点,并检查目标函数的值,而在我们的情况下,目标函数是利润。它将返回使目标函数取得最大值的价格点。(在线性优化中,可以想象一下梯度下降的工作原理)。我们将在下面的优化部分中详细讨论计算优化价格的更多细节。

4. 商业规则

那么我们可以直接在我们的商店中实施优化后的价格吗?

不可以,但现在还剩下什么?遵守商业规则是任何企业的最重要要求之一。

但我们所说的定价规则是什么样的呢?

  1. 末位数字规则 —— 通常将产品定价为$999或$995,而不是$1000。这样做有几个心理原因,因此如果适用,我们需要确保我们最终推荐的价格符合任何此类规则。
  2. 产品差距规则 —— 你能以每个单位比四包装的Maggi更贵的价格销售一包装的Maggi吗?不行,对吧。通常,如果包装的大小增加,每单位成本应该降低,或者至少保持不变。

所以,这些是商业希望应用的一些规则示例。我们将对优化后的价格进行一些后处理步骤,以得到最终的推荐价格。

现在您已经了解了整个流程的概述,现在是时候深入了解更多细节和编码了。

2. 弹性建模

在这一部分中,我们将了解如何使用这个概念来为多个商店的数千种商品推导出优化价格。假设我们需要确定过去3年在加利福尼亚州一家商店销售的零食Yochips的价格弹性。首先让我们看一下价格弹性的定义:

价格弹性定义为价格变动1%时销售额的百分比变动。

现在你可能会想,我可以使用哪个算法来计算像Yochips这样的商品的价格弹性呢?

让我们从经济书中了解一些关于恒定价格弹性模型的细节,看看我们是否可以将其与某个数据科学算法相关联。

需求函数的乘法形式为:

Yi = α*Xi(其中y表示销售/需求,x表示价格)

双方取对数

log(Yᵢ) = log(α*Xᵢ^β)

log(Yᵢ) = log(α) + β*log(Xᵢ) ……….等式(1)

log(α)可以被视为截距β₀

log(Yᵢ) = β₀ + β₁*log(Xᵢ) ………….等式(2)

现在双方进行微分,我们将得到

δY/Y = β₁*δX/X

左侧的项表示Y的百分比变化,即销售额的百分比变化,而右侧的项表示价格的百分比变化。现在当

价格变化率 = 1%;那么δX/X = 1

δY/Y = β₁

这意味着销售额的百分比变化将是β₁,即为我们的弹性值

现在,如果你注意到,等式2是一个回归方程,其中log(销售额)被回归到log(价格),而log(价格)的系数将是我们的价格弹性。

太棒了!现在我们知道计算弹性就像训练一个回归模型一样简单。

但还有一个问题。需求函数方程有一些假设,其中一个假设是销售只受价格影响,但在现实世界中通常不是这样,因为销售通常受到多种因素的影响,如促销、假日、活动等。那么解决办法是什么呢?我们需要计算销售组成部分,可以在其中消除所有这些额外事件的影响。

需要澄清的一点是,基本销售是指单位销售量而不是货币销售额。所以在等式2中,我们需要将价格回归到基本单位而不是实际单位销售量。现在的问题是,我们如何从实际销售单位中推导出基本单位?

让我们使用一个例子来理解。下面你可以看到销售单位和销售价格的时间序列的周图:

作者提供的图片

你能在上面的图中看出任何模式吗?很难说,因为销售单位系列中有太多的波动。现在这些波动可能是由于假期、促销、活动、国际足联世界杯等多种因素引起的。为了分离出价格变化的影响,我们需要计算排除这些额外因素影响的销售额。

使用prophet模型,我们可以对时间序列进行分解,并提取代表基本销售的趋势成分。通过应用这种技术,我们将长期的价格变化影响与其他短期影响分离开来。让我们看看我们在谈论什么:

作者提供的图片

在上面的图中,我们将原始对数销售单位(灰色)分解为对数基本单位(黄色线图)

下面是一段代码,可以用来对时间序列进行分解,并提取趋势成分,这将成为基本销售:

# 定义输入
timestamp_var = "week_ending_sunday"
baseline_dep_var = "ln_sales"
changepoint_prior_scale_value = 0.3
list_ind_vars_baseline = ['event_type_1_Cultural', 'event_type_1_National', 'event_type_1_Religious', 'event_type_1_Sporting']

# 准备数据集
df_item_store = df_item_store.rename(columns={timestamp_var: 'ds', baseline_dep_var: 'y'})
df_item_store['ds'] = pd.to_datetime(df_item_store['ds'])
# 初始化并拟合模型
model = Prophet(changepoint_prior_scale=changepoint_prior_scale_value) # 默认 changepoint_prior_scale = 0.05
# 将回归变量添加到模型中
for regressor in list_ind_vars_baseline:
    model.add_regressor(regressor)
model.fit(df_item_store)
# 由于我们只对当前时间序列进行分解,因此在预测时使用的数据与建模时使用的数据相同
# 进行预测并提取趋势成分
forecast = model.predict(df_item_store)
level_component = forecast['trend']

下面是我们需要定义的输入:

  • changepoint_prior_scale_value — 这控制趋势的平滑度。您可以在Prophet模型文档中了解更多信息。
  • list_ind_vars_baseline — 这些包括所有对销售产生影响的其他事件,例如一些节日、体育赛事、文化活动等。

下面是changepoint_prior_scale_value对趋势的影响。当值较小时,趋势接近直线,而当值较大时,趋势较不平滑。

掌握定价优化的艺术-数据科学解决方案 四海 第6张

代码很简单。首先,我们将“ln_sales”变量重命名为“y”,将“week”变量重命名为“ds”,以符合使用Prophet模型的先决条件。接下来,我们初始化Prophet模型,指定“changepoint_prior_scale”参数。然后,我们将其他事件和假日变量加入模型。最后,我们使用训练模型和提取的趋势成分生成预测。

太棒了。我们现在有了基本单位序列,可以在基本单位(已经处于对数尺度,因为我们对log_base_units序列进行了分解)和对数价格之间拟合线性回归模型。下面是方程:

log(基本单位) = 截距 + 弹性*log(价格)

根据上述方程,我们可以计算弹性值。实际上,并不是所有的序列都适合建模,因此您可能会对各种物品的弹性值得到一些意外的值。那么解决方案是什么呢?如果我们可以在弹性值上实施一些约束的回归。但是如何实施呢?使用优化函数。

对于任何优化,以下是基本要求:

  1. 目标函数 — 这是我们试图最小化/最大化的方程。在我们的情况下,它将是线性回归MSE中使用的损失函数(预测值-实际值 => [截距 + 弹性*ln_price – 实际值]²)
  2. 要优化的参数的初始值,在我们的情况下是截距和弹性。这些可以是任意初始值。
  3. 参数的边界,这是截距和弹性的最小和最大边界
  4. 优化算法 — 这取决于库,但您可以使用默认值,应该可以得到正确的结果

现在让我们看一下代码:

# 准备矩阵用于传入优化算法
x = df_item_store_model
x["intercept"] = 1
x = x[["intercept", "ln_sell_price", "ln_base_sales"]].values.T
# x_t = x.T
actuals = x[2]

from scipy.optimize import minimize
# 定义要最小化的目标函数
def objective(x0):
    return sum(((x[0]*x0[0] + x[1]*x0[1]) - actuals)**2) # (intercept*1 + elasticity*(ln_sell_price) -ln_base_sales)^2

# 定义初始猜测值
x0 = [1, -1]
# 定义变量的边界
bounds = ((None, None), (-3, -0.5))
# 使用SLSQP优化算法最小化目标函数
result = minimize(objective, x0, bounds=bounds, method='L-BFGS-B')
# 打印优化结果
print(result)
# 将物品的价格弹性保存在数据帧中
price_elasticity = result.x[1]
df_item_store_model["price_elasticity"] = result.x[1]

请注意,我们已经将截距的初始参数值定义为1,弹性为-1。截距没有定义边界,而弹性的边界为(-3,-0.5)。这就是为什么我们通过优化函数来进行回归的主要原因。在运行优化之后,我们保存了价格弹性的优化参数值。太棒了!我们已经计算出了价格弹性!

因此,我们在加利福尼亚州的Yochips产品的价格弹性为-1.28。

让我们也来看看其他一些系列的价格弹性:

低价格弹性:价格增加时销售几乎没有明显变化。下面是一个价格弹性为-0.5的物品的图表:

作者提供的图片

VoAGI价格弹性:价格增加时销售略有下降。下面是一个价格弹性为-1.28的物品的图表:

作者提供的图片

高价格弹性:价格增加时销售大幅下降。下面是一个价格弹性为-2.5的物品的图表:

作者提供的图片

使用相同的方法,我们可以计算所有物品的价格弹性。在下一篇文章中,我们将探讨如何利用这些弹性值确定每个物品的优化价格。

3. 优化

在前一节中,我们已经确定了我们加利福尼亚州Yochips产品的价格弹性。但是这对店长并没有帮助,他想知道如何调整Yochips的价格以最大化收入。在本文中,我们将了解优化价格的方法论。

但在此之前,我们必须向店长提出几个问题。

问:在优化价格时,是否应考虑价格更改的最小和最大限制?

根据与店长的讨论,我们确定价格降低不应超过20%,价格上涨也应限制在20%以内。

Yochips目前的价格是3.23美元,现在我们知道优化价格必须在2.58美元至3.876美元之间。但是我们如何得出一个优化价格呢?

但是我们如何得出一个最大化收入的优化价格?让我们进行一些数学计算:

优化收入 = 总销售数量 * (优化价格)

我们需要优化价格以最大化收入。但是总销售数量也会随着价格的变化而变化。让我们重新编写上述方程,并且我们可以将优化价格下的总销售数量称为优化数量:

优化收入 = 优化数量 * (优化价格)……………(公式1)

我们已经知道 —

弹性 = 单位销售量的百分比变化 / 价格的百分比变化

因此:

优化数量 = 基础数量 + 优化价格下的单位变化量

这里,基础数量指的是当前价格2.58美元下的总销售数量

优化数量 = (基础数量 + (基础数量 * 价格弹性 * (优化价格与常规价格的百分比变化)……….(公式2)

让我们将公式2代入公式1

优化收入 = (基础数量 + (基础数量 * 价格弹性 * (优化价格与常规价格的百分比变化) * (优化价格)………公式3)

优化收入 = (基础数量 + (基础数量 * 价格弹性 * [(优化价格 — 当前价格)/ 当前价格] * (优化价格)…………..公式4)

以下是优化方程(eq4)中的关键参数:

基本单位 = 当前价格下的平均销售单位。

价格弹性 = 该商品价格弹性的计算值

当前价格 = 最新售价

太棒了!在我们的方程中,除了优化价格,我们还有所有其他变量的数据。那么我们可以使用哪种算法来计算最大化收入的优化价格呢?我们可以简单地使用优化算法。

优化所需的关键组件是什么:

  1. 需要最小化/最大化的目标函数:我们已经定义了目标函数,即最大化eq(4)中定义的优化收入。
  2. 边界:根据店长的要求,我们需要将优化价格限制在不变动超过20%的范围内。所以下界 = 当前价格(1–0.2),上界 = 当前价格(1+0.2)
  3. 优化算法:我们将使用Python的Scipy.optimize库来实现优化。

让我们来看一下代码:

# 获取最近6周基础销售的平均值#--------------------------------------------------# 对日期列进行排序df_item_store_optimization["rank"] = df_item_store_optimization["ds"].rank(ascending=False)# 按id分组,获取最近6周的数据sales_df = df_item_store_optimization.loc[df_item_store_optimization["rank"] <= 6].groupby("id")["base_sales"].mean().reset_index()df_item_store_optimization_input.rename(columns = {"base_sales":"base_units"}, inplace=True)# 计算销售价格的最小值和最大值#--------------------------------------------------# 创建下界和上界,范围为20%df_item_store_optimization_input["LB_price"] = df_item_store_optimization_input["sell_price"] - (0.2*df_item_store_optimization_input["sell_price"])df_item_store_optimization_input["UB_price"] = df_item_store_optimization_input["sell_price"] + (0.2*df_item_store_optimization_input["sell_price"])

上述代码帮助我们进行优化的数据准备工作。首先,我们计算基本单位,即最近6周的基础销售的平均值(分解系列的趋势分量)。我们已经在上面的部分讨论了计算基础销售的方法。

接下来,我们通过将当前售价减少和增加20%来定义下界(LB_price)和上界(UB_price)。

让我们定义执行优化的代码。

from scipy.optimize import minimize# 定义要最小化的目标函数def objective(opti_price):    df_item_store_optimization_input["opti_price"] = opti_price    df_item_store_optimization_input["optimized_units"] = df_item_store_optimization_input["base_units"] + (df_item_store_optimization_input["base_units"]*\                                                                                                        ((df_item_store_optimization_input["opti_price"]/df_item_store_optimization_input["sell_price"]) - 1)*\                                                                                                       (df_item_store_optimization_input["price_elasticity"]))        df_item_store_optimization_input["optimized_revenue"] = df_item_store_optimization_input["optimized_units"]*df_item_store_optimization_input["opti_price"]        return -sum(df_item_store_optimization_input["optimized_revenue"])# 定义初始猜测值opti_price = df_item_store_optimization_input["sell_price"][0]# 定义变量的边界bounds = ((df_item_store_optimization_input["LB_price"][0], df_item_store_optimization_input["UB_price"][0]),)# 使用优化算法最小化目标函数result = minimize(objective, opti_price, bounds=bounds)# 打印优化结果print(result)

上述代码将给出我们的优化价格。你能猜到在目标函数中为什么我们定义了负的优化收入吗?-(-1)等于1。我们在最小化目标函数,并且使用负号来最大化优化收入。

此外,我们可以用任意随机值初始化opti_price变量,只是为了快速收敛,我们将其初始化为当前售价。在边界中,我们定义了在上面的代码中创建的下界(LB_price)和上界(UB_price)。

太棒了!我们已经找到了Yochips的优化价格,现在可以向加州店长提议了。

作者提供的图片

我们的建议是将Yochips的价格下调10.2%,降至2.9美元。这将带来最大收入。

这是价格优化方法的最后一步,整体方法非常强大,可以帮助我们为每个商店的每个项目返回优化后的价格。

上述方法的一个限制是对于我们没有足够的价格变动历史的项目。在那种情况下,我们使用其他技术,但如果这类项目的比例较小,则可以使用类别级别的平均价格弹性。

希望您喜欢这篇文章!

Leave a Reply

Your email address will not be published. Required fields are marked *