使用黄氏曲线评估零售店促销活动效果
1. 关于项目
这是一个线上电玩产品零售店在六一促销活动(2020年5.28-6.3)的复盘工作中的一个环节。
这一小节里,将使用黄氏曲线分析工具,从整体上对促销活动的效果进行评估。
黄氏曲线是零售业数据化管理工具之一,本质上是一种加权曲线。
一个完整的促销活动复盘工作还有很多繁杂的工作,包括目标回顾、评估结果、分析原因以及总结经验四个方面。
活动复盘是促进活动迭代的一个重要原因。通过交易数据、系统数据、用户反馈等来确定用户喜好、流程思考、活动设计。
2. 数据预处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
%matplotlib inline
sales = pd.read_excel('sales.xlsx')
sales
年月 | 日 | 销售额 | |
---|---|---|---|
0 | 2019年3月 | 31 | 1256.80 |
1 | 2019年3月 | 30 | 1130.00 |
2 | 2019年3月 | 29 | 1214.80 |
3 | 2019年3月 | 28 | 1129.00 |
4 | 2019年3月 | 27 | 595.00 |
... | ... | ... | ... |
463 | 2020年6月 | 5 | 1939.42 |
464 | 2020年6月 | 4 | 1985.00 |
465 | 2020年6月 | 3 | 1668.29 |
466 | 2020年6月 | 2 | 2070.05 |
467 | 2020年6月 | 1 | 3572.06 |
468 rows × 3 columns
sales.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 468 entries, 0 to 467
Data columns (total 3 columns):
年月 468 non-null object
日 468 non-null int64
销售额 468 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 11.1+ KB
sales['日'] = sales['日'].astype(str)
sales['日期'] = pd.to_datetime(sales['年月']+sales['日'], format='%Y年%m月%d')
sales.drop(columns=['年月','日'], inplace=True)
sales.sort_values(by='日期', inplace=True)
sales
销售额 | 日期 | |
---|---|---|
30 | 187.00 | 2019-03-01 |
29 | 272.40 | 2019-03-02 |
28 | 255.60 | 2019-03-03 |
27 | 367.80 | 2019-03-04 |
26 | 605.40 | 2019-03-05 |
... | ... | ... |
462 | 1880.35 | 2020-06-06 |
461 | 1221.48 | 2020-06-07 |
460 | 1413.28 | 2020-06-08 |
459 | 1737.08 | 2020-06-09 |
458 | 1794.69 | 2020-06-10 |
468 rows × 2 columns
sales.sort_values(by='日期', inplace=True)
sales
销售额 | 日期 | |
---|---|---|
30 | 187.00 | 2019-03-01 |
29 | 272.40 | 2019-03-02 |
28 | 255.60 | 2019-03-03 |
27 | 367.80 | 2019-03-04 |
26 | 605.40 | 2019-03-05 |
... | ... | ... |
462 | 1880.35 | 2020-06-06 |
461 | 1221.48 | 2020-06-07 |
460 | 1413.28 | 2020-06-08 |
459 | 1737.08 | 2020-06-09 |
458 | 1794.69 | 2020-06-10 |
468 rows × 2 columns
3. 促销分析与评估
促销活动是零售业最常见的销售模式,随着促销的逐渐日常化,促销的分析和评估也变成一种固定的工作。
然而不管是线上或者线下的促销活动分析,往往会存在一些误区:
1.只关注促销的目标,完成目标就是成功了。
2.只关注促销活动的同比,且对比的误差比较大。
3.只关注促销前和促销中的数据,却从来不关注促销后的数据。
而黄氏曲线,是在对每日销售额进行权重处理的基础上,将促销活动以及前后相邻时间段的权重值进行对比,不仅关注促销本身的效果,同时关注促销对后续销售的影响,是一个对促销效果进行量化评估的有效工具。
3.1 企业周权重指数
周权重指数是以某段销售周期内的历史日销售额数据为基础,以周为单位,进行权重分析处理的一种管理工具。
周权重指数是一个相对概念,每个企业不尽相同,一般介于7.0~14.0之间。值越大表示该企业或者店铺的日销售额波动幅度越大。
周权重指数是零售店铺用来量化处理各种销售状况、销售事件的管理工具,非常强大。
plt.rcParams['font.sans-serif'] = ['SimHei']
sales.plot('日期','销售额', figsize=(18, 6))
<matplotlib.axes._subplots.AxesSubplot at 0xd5a3048>
周权重指数等于周一到周日每天的日权重指数相加。
所以在计算周权重指数前,需要先计算每日的权重指数。
计算日权重指数的步骤:
1.先选取最近完整一年的销售数据,可以是2019-01-01到2019-12-31,也可以是2019-03-01到2020-02-29这一整年的销售数据;
2.剔除数据集中的异常值,如节假日、促销活动非常规销售日引起的销售额变化等记录;
3.将剩下的数据以周为单位进行整理,并计算出平均日销售;
# datetime.weedak()方法的返回值为[0,6]之间的整数,分别代表周一到周日。在别的时区可能代表的是周日到周六。
sales['星期'] = sales['日期'].apply(lambda x:x.weekday()) + 1
sales = sales.reindex(columns=['日期','星期','销售额'])
sales
日期 | 星期 | 销售额 | |
---|---|---|---|
30 | 2019-03-01 | 5 | 187.00 |
29 | 2019-03-02 | 6 | 272.40 |
28 | 2019-03-03 | 7 | 255.60 |
27 | 2019-03-04 | 1 | 367.80 |
26 | 2019-03-05 | 2 | 605.40 |
... | ... | ... | ... |
462 | 2020-06-06 | 6 | 1880.35 |
461 | 2020-06-07 | 7 | 1221.48 |
460 | 2020-06-08 | 1 | 1413.28 |
459 | 2020-06-09 | 2 | 1737.08 |
458 | 2020-06-10 | 3 | 1794.69 |
468 rows × 3 columns
# 选取2019年6月份到2020年5月份这一整年的数据,并剔除了2019年11月中7000多销售额的那个异常值。
subset = sales.query('"2019-06-01" <= 日期 < "2020-06-01" & 销售额 < 6000').copy()
mean_weekday = subset.groupby('星期')['销售额'].mean()
mean_weekday
星期
1 1612.590962
2 1421.880962
3 1494.894808
4 1487.894902
5 1356.369038
6 1383.588302
7 1429.729245
Name: 销售额, dtype: float64
4. 找出平均日销售额最低的一天,设定它的日权重指数为1.0,然后分别用其余六天的平均日销售额除以这个最低值,就分别得到每天的日权重指数。
weight_index = mean_weekday.div(mean_weekday.min()).round(1)
weight_index.name = '日权重指数'
weight_index
星期
1 1.2
2 1.0
3 1.1
4 1.1
5 1.0
6 1.0
7 1.1
Name: 日权重指数, dtype: float64
5. 最后将周一到周日的日权重指数求和,就得到企业标准周权重指数。
weight_index.sum()
7.5
注意:这个计算结果是整个企业使用的周权重指数和日权重指数。
而具体到企业下的某个分店,则需要根据分店的销售规律分别计算。
分店的周权重指数计算方法:
1.选取最近2个月,和上一年同期月份的数据。
2.剔除数据集中的异常值,如节假日、促销活动非常规销售日引起的销售额变化等记录;
3.将剩下的数据以周为单位进行整理,并计算出平均日销售额,以及平均周销售额;
4.计算分店日权重指数。公式为:(其中N为1~ 7,代指周一到周日)
分店星期N的日权重指数 = (星期N的平均日销售额 ÷ 平均周销售额)× 企业周权重指数
在本次项目里计算的是企业总体情况,所以用的是企业标准的周权重指数。
3.2 权重曲线和黄氏曲线
为了评估的时效性和可比性,这里选取活动日以及其前后一个星期的记录,进行对比分析活动前后的差距。
promote_set = sales.query('"2020-05-21" <= 日期 <= "2020-06-10"').copy()
promote_set
日期 | 星期 | 销售额 | |
---|---|---|---|
437 | 2020-05-21 | 4 | 1347.24 |
436 | 2020-05-22 | 5 | 1516.32 |
435 | 2020-05-23 | 6 | 1756.28 |
434 | 2020-05-24 | 7 | 1724.58 |
433 | 2020-05-25 | 1 | 2134.84 |
432 | 2020-05-26 | 2 | 1844.07 |
431 | 2020-05-27 | 3 | 1574.27 |
430 | 2020-05-28 | 4 | 2272.15 |
429 | 2020-05-29 | 5 | 1847.67 |
428 | 2020-05-30 | 6 | 1793.38 |
427 | 2020-05-31 | 7 | 1693.99 |
467 | 2020-06-01 | 1 | 3572.06 |
466 | 2020-06-02 | 2 | 2070.05 |
465 | 2020-06-03 | 3 | 1668.29 |
464 | 2020-06-04 | 4 | 1985.00 |
463 | 2020-06-05 | 5 | 1939.42 |
462 | 2020-06-06 | 6 | 1880.35 |
461 | 2020-06-07 | 7 | 1221.48 |
460 | 2020-06-08 | 1 | 1413.28 |
459 | 2020-06-09 | 2 | 1737.08 |
458 | 2020-06-10 | 3 | 1794.69 |
promote_set = promote_set.merge(weight_index.reset_index(), on='星期', how='left')
promote_set
日期 | 星期 | 销售额 | 日权重指数 | |
---|---|---|---|---|
0 | 2020-05-21 | 4 | 1347.24 | 1.1 |
1 | 2020-05-22 | 5 | 1516.32 | 1.0 |
2 | 2020-05-23 | 6 | 1756.28 | 1.0 |
3 | 2020-05-24 | 7 | 1724.58 | 1.1 |
4 | 2020-05-25 | 1 | 2134.84 | 1.2 |
5 | 2020-05-26 | 2 | 1844.07 | 1.0 |
6 | 2020-05-27 | 3 | 1574.27 | 1.1 |
7 | 2020-05-28 | 4 | 2272.15 | 1.1 |
8 | 2020-05-29 | 5 | 1847.67 | 1.0 |
9 | 2020-05-30 | 6 | 1793.38 | 1.0 |
10 | 2020-05-31 | 7 | 1693.99 | 1.1 |
11 | 2020-06-01 | 1 | 3572.06 | 1.2 |
12 | 2020-06-02 | 2 | 2070.05 | 1.0 |
13 | 2020-06-03 | 3 | 1668.29 | 1.1 |
14 | 2020-06-04 | 4 | 1985.00 | 1.1 |
15 | 2020-06-05 | 5 | 1939.42 | 1.0 |
16 | 2020-06-06 | 6 | 1880.35 | 1.0 |
17 | 2020-06-07 | 7 | 1221.48 | 1.1 |
18 | 2020-06-08 | 1 | 1413.28 | 1.2 |
19 | 2020-06-09 | 2 | 1737.08 | 1.0 |
20 | 2020-06-10 | 3 | 1794.69 | 1.1 |
3.2.1 权重曲线
单位权重(销售)值 = ∑日销售额 / ∑日权重指数
这个公式的含义是:计算在某个销售时期内平均单位权重指数的销售额,这就解决了“时间标准”有时没有可对比性的原则。
将销售时期的时间段定为天,把每天的销售额分别除以当日的权重指数,得到的便是单位权重(销售)值曲线,简称权重曲线。
promote_set.eval('权重曲线 = 销售额 / 日权重指数', inplace=True)
promote_set
日期 | 星期 | 销售额 | 日权重指数 | 权重曲线 | |
---|---|---|---|---|---|
0 | 2020-05-21 | 4 | 1347.24 | 1.1 | 1224.763636 |
1 | 2020-05-22 | 5 | 1516.32 | 1.0 | 1516.320000 |
2 | 2020-05-23 | 6 | 1756.28 | 1.0 | 1756.280000 |
3 | 2020-05-24 | 7 | 1724.58 | 1.1 | 1567.800000 |
4 | 2020-05-25 | 1 | 2134.84 | 1.2 | 1779.033333 |
5 | 2020-05-26 | 2 | 1844.07 | 1.0 | 1844.070000 |
6 | 2020-05-27 | 3 | 1574.27 | 1.1 | 1431.154545 |
7 | 2020-05-28 | 4 | 2272.15 | 1.1 | 2065.590909 |
8 | 2020-05-29 | 5 | 1847.67 | 1.0 | 1847.670000 |
9 | 2020-05-30 | 6 | 1793.38 | 1.0 | 1793.380000 |
10 | 2020-05-31 | 7 | 1693.99 | 1.1 | 1539.990909 |
11 | 2020-06-01 | 1 | 3572.06 | 1.2 | 2976.716667 |
12 | 2020-06-02 | 2 | 2070.05 | 1.0 | 2070.050000 |
13 | 2020-06-03 | 3 | 1668.29 | 1.1 | 1516.627273 |
14 | 2020-06-04 | 4 | 1985.00 | 1.1 | 1804.545455 |
15 | 2020-06-05 | 5 | 1939.42 | 1.0 | 1939.420000 |
16 | 2020-06-06 | 6 | 1880.35 | 1.0 | 1880.350000 |
17 | 2020-06-07 | 7 | 1221.48 | 1.1 | 1110.436364 |
18 | 2020-06-08 | 1 | 1413.28 | 1.2 | 1177.733333 |
19 | 2020-06-09 | 2 | 1737.08 | 1.0 | 1737.080000 |
20 | 2020-06-10 | 3 | 1794.69 | 1.1 | 1631.536364 |
plt.rcParams['font.sans-serif'] = ['SimHei']
promote_set.plot('日期',['销售额', '权重曲线'], figsize=(18, 6))
<matplotlib.axes._subplots.AxesSubplot at 0xd94c550>
从权重曲线上可以看出,促销活动是有一定效果的,但是还不够直观,不能看出活动中和活动前后平均销售水平的变化幅度,进而量化地评估活动效果的好坏程度。
3.2.2 黄氏曲线
取权重曲线中的某个时间段,以权重异动点为起点,以最后一个异动点为终点,把起点和终点之间的权重数据做平均,所得数据值为起点和终点之间的数据纵轴,呈现的数据曲线为黄氏曲线。
黄氏曲线为权重曲线同一时间段的平均值。
将相邻时间段的权重值进行对比,相邻的时间段能保证对比状态的一致性,有可对比性。同时销售额与当日权重指数的比值降低了周一到周日销售额不均衡的影响。因此在黄氏曲线的基础上,可以更好地对促销效果进行量化评估。
除此外,黄氏曲线也常常用于店铺受某个事件影响评估、新品上市评估、缺货影响评估、网站改版评估、新人到任评估、竞争对手活动造成的影响评估。
def HuangshiCurve(data,point_of_date):
"""
data:含有单位权重值(权重曲线)字段的数据帧
point_of_date:促销活动的开始和结束日期
"""
before = data.query('"%s" > 日期' % point_of_date[0])['权重曲线'].mean()
during = data.query('"%s" <= 日期 <= "%s"' % point_of_date)['权重曲线'].mean()
after = data.query('"%s" < 日期' % point_of_date[1])['权重曲线'].mean()
data['黄氏曲线'] = np.nan
data.loc[data['日期'] < point_of_date[0],'黄氏曲线'] = before
data.loc[data['日期'] > point_of_date[1],'黄氏曲线'] = after
data.loc[data['黄氏曲线'].isna(),'黄氏曲线'] = during
return data
point_of_date = ('2020-05-28', '2020-06-03')
promote_set = HuangshiCurve(promote_set, point_of_date)
promote_set
日期 | 星期 | 销售额 | 日权重指数 | 权重曲线 | 黄氏曲线 | |
---|---|---|---|---|---|---|
0 | 2020-05-21 | 4 | 1347.24 | 1.1 | 1224.763636 | 1588.488788 |
1 | 2020-05-22 | 5 | 1516.32 | 1.0 | 1516.320000 | 1588.488788 |
2 | 2020-05-23 | 6 | 1756.28 | 1.0 | 1756.280000 | 1588.488788 |
3 | 2020-05-24 | 7 | 1724.58 | 1.1 | 1567.800000 | 1588.488788 |
4 | 2020-05-25 | 1 | 2134.84 | 1.2 | 1779.033333 | 1588.488788 |
5 | 2020-05-26 | 2 | 1844.07 | 1.0 | 1844.070000 | 1588.488788 |
6 | 2020-05-27 | 3 | 1574.27 | 1.1 | 1431.154545 | 1588.488788 |
7 | 2020-05-28 | 4 | 2272.15 | 1.1 | 2065.590909 | 1972.860823 |
8 | 2020-05-29 | 5 | 1847.67 | 1.0 | 1847.670000 | 1972.860823 |
9 | 2020-05-30 | 6 | 1793.38 | 1.0 | 1793.380000 | 1972.860823 |
10 | 2020-05-31 | 7 | 1693.99 | 1.1 | 1539.990909 | 1972.860823 |
11 | 2020-06-01 | 1 | 3572.06 | 1.2 | 2976.716667 | 1972.860823 |
12 | 2020-06-02 | 2 | 2070.05 | 1.0 | 2070.050000 | 1972.860823 |
13 | 2020-06-03 | 3 | 1668.29 | 1.1 | 1516.627273 | 1972.860823 |
14 | 2020-06-04 | 4 | 1985.00 | 1.1 | 1804.545455 | 1611.585931 |
15 | 2020-06-05 | 5 | 1939.42 | 1.0 | 1939.420000 | 1611.585931 |
16 | 2020-06-06 | 6 | 1880.35 | 1.0 | 1880.350000 | 1611.585931 |
17 | 2020-06-07 | 7 | 1221.48 | 1.1 | 1110.436364 | 1611.585931 |
18 | 2020-06-08 | 1 | 1413.28 | 1.2 | 1177.733333 | 1611.585931 |
19 | 2020-06-09 | 2 | 1737.08 | 1.0 | 1737.080000 | 1611.585931 |
20 | 2020-06-10 | 3 | 1794.69 | 1.1 | 1631.536364 | 1611.585931 |
plt.rcParams['font.sans-serif'] = ['SimHei']
promote_set.plot('日期',['权重曲线', '黄氏曲线'], figsize=(12,6))
<matplotlib.axes._subplots.AxesSubplot at 0xd9babe0>
促销的 爆发度 和 衰减度
促销爆发度体现了促销活动立杆见影的程度,这和促销活动的方案、宣传力度、卖场等息息相关,是一个综合指标。
而促销衰减度是用来判断促销活动是否有透支销售的情况发生。如果衰减度大于爆发度则有销售透支的现象发生,如果衰减度大于两倍的爆发度,那这个促销活动就是彻底失败了。
爆发度 = (促销期间单位权重值的平均值 - 促销前单位权重值的平均值) / 促销前单位权重值的平均值 * 100%
衰减度 = (促销期间单位权重值的平均值 - 促销后单位权重值的平均值) / 促销前单位权重值的平均值 * 100%
注意:这里的除数都是促销前的,把增量和减量都除以同一个数,得到的是相较于促销前的变化率,保证了对比的一致性。
把前面计算黄氏曲线的函数修改一下,就可以得到爆发度和衰减度。
def HuangshiCurve(data,point_of_date):
"""
data:含有单位权重值(权重曲线)字段的数据帧
point_of_date:促销活动(事件)的开始和结束日期
"""
before = data.query('"%s" > 日期' % point_of_date[0])['权重曲线'].mean()
during = data.query('"%s" <= 日期 <= "%s"' % point_of_date)['权重曲线'].mean()
after = data.query('"%s" < 日期' % point_of_date[1])['权重曲线'].mean()
data['黄氏曲线'] = np.nan
data.loc[data['日期'] < point_of_date[0],'黄氏曲线'] = before
data.loc[data['日期'] > point_of_date[1],'黄氏曲线'] = after
data.loc[data['黄氏曲线'].isna(),'黄氏曲线'] = during
explosive_range = (during - before) / before * 100 # 爆发度
attenuation_range = (during - after) / before * 100 # 衰减度
return (data,(explosive_range, attenuation_range))
promote_set, trends = HuangshiCurve(promote_set, point_of_date)
print('爆发度: %.2f%% \n衰减度:%.2f%% ' % trends )
爆发度: 24.20%
衰减度:22.74%
3.3 促销效果评估
本次促销活动的爆发度是24.20%,衰减度是22.74%。促销结束后一周的平均销售额比促销前一周平均销售额上涨1.46%。(定量分析)
促销活动的爆发度高于衰减度,促销活动并未透支促销之后一周的销售额,本次促销活动在总体上是成功的(在销售目标完成的前提下)。(定性分析)
4. 项目总结
- 使用权重指数和黄氏曲线来评估和分析零售活动的指标时,计算的地方很多,看起来很麻烦。
而实际上这些计算过程都可以通过函数或者模板自动计算,实际上难的是业务逻辑,而不是计算过程。
特别是书本上的知识,对相关概念的定义,由于本人水平有限,都需要反复咀嚼斟酌良久才能明白其意。本次项目也算是对这部分知识的一次梳理和总结。
本次项目也只是黄氏曲线应用的一个小的方面,这个工具还有很多应用场景,需要在后续的工作中再深入剖析。
- 数据分析是通过各种分析手段和业务指标对数据进行定量和定性分析。定性分析让我们了解数据的各种状态,定量分析让我们知道这些状态的程度或所处的位置。而这些定量和定性分析的结果是作为业务评估依据和业务决策依据的重要指标之一。