基因facebook的时间序列预测框架prophet的实战应用。

在许多商业活动中,季节因素、假期因素等对商家的销售活动会起到很大的影响,如淡季、旺季、十一,五一、春节等假期都会商家的销售额产生很大的影响。如何能让我们的模型从数据的时间序列中捕捉到这种有规律的时间的变化特征,从而为我们提供更加准确的预测,这正是本文所要讨论的主要内容。 这里我们将使用facebook的时间序列预测框架Prophet(https://facebook.github.io/prophet/), 关于安装Prophet方法可以看这篇博客:https://blog.csdn.net/qq_23860475/article/details/81354467

prophet是一种基于附加模型预测时间序列数据的过程,其中非线性趋势与年,周和日的季节性变化以及假日效应相吻合。它最适合具有强烈季节性影响和多个季节历史数据的时间序列。prophet对丢失数据和趋势变化具有鲁棒性,通常可以很好地处理异常值。

在本文中,我们将使用时间序列分析技术来分析国外某商家的历史销售数据,并使用prophet来预测商家未来5周的销售额,你可以在这里下载我们的数据(https://raw.githubusercontent.com/tongzm/ml-python/master/data/Sales.csv)

数据说明

  • Store: 商家编码,这里总共有9个商家(1号-10号,中间缺9号)
  • Product:商品编码,总共有3个商品
  • Is_Holiday:该周是否包含假期的指标:0 =否,1 =是
  • Base Price:日常基准价格(未打折扣)
  • Price:每周的实际价格。 如果是促销时期那就是促销价格,否则就是日常基准价格。
  • Weekly_Units_Sold:每周的销售数量
df = pd.read_csv('./data/Sales.csv')
df.head()

下面我们对数据进行预处理,我们增加一个weekly_sales(每周销售额),year,month,day,week_of_year等字段。

df['Date'] = pd.to_datetime(df['Date'])
df.set_index('Date', inplace=True)

df['weekly_sales'] = df['Price'] * df['Weekly_Units_Sold']
df['year'] = df.index.year
df['month'] = df.index.month
df['day'] = df.index.day
df['week_of_year'] = df.index.weekofyear
df.head()

EDA

为了对数据有一个初步印象,在这里我们将会对数据进行可视化,我们将会画ECDF(经验分布函数)图

sns.set(style = "ticks")
c = '#386B7F' 
figure, axes = plt.subplots(nrows=2, ncols=2)
figure.tight_layout(pad=2.0)
plt.subplot(211)
cdf = ECDF(df['Weekly_Units_Sold'])
plt.plot(cdf.x, cdf.y, label = "statmodels", color = c);
plt.xlabel('Weekly_Units_Sold'); plt.ylabel('ECDF');

plt.subplot(212)
cdf = ECDF(df['weekly_sales'])
plt.plot(cdf.x, cdf.y, label = "statmodels", color = c);
plt.xlabel('Weekly sales');

从上图中我们可以发现:

  • 1.尽管周的销售数量最大查过了2500,但是80%的数据中周销售数量都在500以内。
  • 2.尽管周的销售金额最大超过了25K,但是90%的数据中周销售金额都在5K以内。

下面我们对Store,weekly_sales,Weekly_Units_Sold进行分组统计:

从上面的统计结果中我们可以看到:

  • 1.在所有的商家中商家10的平均销售金额和销售数量都是最大的。
  • 2.在所有的商家中商家5的平均销售金额和销售数量都是最小的。

下面我们按是否节假日来查看商品的销售价格和销售数量的分布:

g = sns.FacetGrid(df, col="Is_Holiday", height=4, aspect=.8)
g.map(sns.barplot, "Product", "Price");

g = sns.FacetGrid(df, col="Is_Holiday", height=4, aspect=.8)
g.map(sns.barplot, "Product", "Weekly_Units_Sold");

从上面的分布图中可以看见:

  • 1.无论是否假期,商品2的销售价格在所有的商品中是最低的,而商品3的价格是最高的。
  • 2.无论是否假期商品2的销售数量是最高的。
  • 3.当假期时商品3的销售量是最低的,而非假期时商品3和商品1的销售量相差无几。

从中我们可以总结出商品的销售价格在假期和非假期时没有太大变化,商品的销售数量在假期和非假期时有略微的差异

下面我们查看密度分布图:

g = sns.FacetGrid(df, row="Is_Holiday",
                  height=1.7, aspect=4,)
g.map(sns.distplot, "Weekly_Units_Sold", hist=False, rug=True);

sns.factorplot(data= df, x= 'Is_Holiday',y= 'Weekly_Units_Sold',hue= 'Store');

sns.factorplot(data= df, x= 'Is_Holiday',y= 'Weekly_Units_Sold',hue= 'Product');

从上图中我们可以看见:

  • 1.大多数商家在节假日的平均销售数量与日常的平均销售量相差不大,而商家10在节假日的销售量有明显的减少
  • 2.商品1在节假日平均销售数量明显增加,而商品2和商品3在节假日的平均销售数量有较为明显的减少

下面我们查看每种商品的销售价格和销售数量的散点图:

g = sns.FacetGrid(df, col="Product", row="Is_Holiday", margin_titles=True, height=3)
g.map(plt.scatter, "Price", "Weekly_Units_Sold", color="#338844", edgecolor="white", s=50, lw=1)
g.set(xlim=(0, 30), ylim=(0, 2600));

从上面的散点图中我们可以看见:

  • 1.每个商品在假期或非假期期间都有至少2个或以上的价格,除商品3以外,其他商品的多个价格直接的差异都比较小,而商品3的价格差异较大(有50%的差异)
  • 2.商品3在非假期期间的销售量最高。

下面我们查看每个商家的销售价格和销售数量的散点图:

g = sns.FacetGrid(df, col="Store", hue="Product", margin_titles=True, col_wrap=3)
g.map(plt.scatter, 'Price', 'Weekly_Units_Sold', alpha=.7)
g.add_legend();

从上面的散点图中我们可以看见:

  • 所有的9个商家都销售商品1、商品2、商品3这三种商品,而商品3在所有商家都存在打折销售的情况,并且在商家10的销售量最大。

下面我们按月份查看每个商家销售数量的点图:

g = sns.FacetGrid(df, col="Store", col_wrap=3, height=3, ylim=(0, 1000))
g.map(sns.pointplot, "month", "Weekly_Units_Sold", color=".3", ci=None, order = [1,2,3,4,5,6,7,8,9,10,11,12]);

从上面的散点图中我们可以看见:

  • 1.每个商家的销售数量都呈现出季节性的波动,7月份销售数量最大,商家10在7月的销售数量是最大的。

下面我们查看商品的点图:

g = sns.FacetGrid(df, col="Product", col_wrap=3, height=3, ylim=(0, 1000))
g.map(sns.pointplot, "month", "Weekly_Units_Sold", color=".3", ci=None, order = [1,2,3,4,5,6,7,8,9,10,11,12]);

从上面的图中我们发现:

  • 1.商品1受季节性影响较小,商品2在4月和7月有两个销售高峰,商品3在7月是销售数量最高峰,其次在11月份也有一个小高峰

下面我们按商家来查看所有的3中商品的销售情况:

g = sns.FacetGrid(df, col="Store", col_wrap=3, height=3, ylim=(0, 2000), hue='Product', palette="Set1")
g.map(sns.pointplot, "month", "Weekly_Units_Sold", ci=None, order = [1,2,3,4,5,6,7,8,9,10,11,12], alpha=.7)
g.add_legend();

从上面的图中我们发现:

  • 1.商品2(蓝色曲线)在大多数时间在所有商家中的销售量是最高的。
  • 2 商家10中在7月份的时候商品3(绿色曲线)的销售量超过了商品2

下面我们查看价格和是否假期的散点回归图:

g = sns.PairGrid(df, y_vars=["Weekly_Units_Sold"], x_vars=["Price", "Is_Holiday"], height=4)
g.map(sns.regplot, color=".3");

从上图上我们发现:

  • 1.商品价格越便宜,销售量越大
  • 2.非假期的销售量要高于假期的销售量

下面我为数据集增加一个promotion字段,用来识别当前的price是否是优惠价。

def f(row):
    if row['Base Price'] == row['Price']:
        val = 0
    elif row['Base Price'] > row['Price']:
        val = 1
    else:
        val = -1
    return val
df['promotion'] = df.apply(f, axis=1)

g = sns.FacetGrid(df, row="promotion",height=1.7, aspect=4,)
g.map(sns.distplot, "Weekly_Units_Sold", hist=False, rug=True);

sns.factorplot(data= df, x= 'promotion',y= 'Weekly_Units_Sold',hue= 'Store');

从上图中我们发现:

  • 所有的商家在优惠期间的销售量都有所上升
sns.factorplot(data= df, x= 'promotion',y= 'Weekly_Units_Sold',hue= 'Product');

从上图中我们发现:

  • 所有的商品在优惠期间的销售量都有所上升

下面我们查看10个商家在优惠和非优惠的情况下的销售量的情况:

g = sns.FacetGrid(df, col="Store", hue="promotion", palette = 'plasma', row='promotion')
g = (g.map(plt.scatter, "Price", "Weekly_Units_Sold")
     .add_legend())

从上图中我们发现:

所有的商家在优惠或非优惠期间都有相似的价格模式,商家10在优惠期间的销售量最大。

def qqplot(x, y, **kwargs):
    _, xr = stats.probplot(x, fit=False)
    _, yr = stats.probplot(y, fit=False)
    plt.scatter(xr, yr, **kwargs)

g = sns.FacetGrid(df, hue="promotion", col="Product", height=4)    
g.map(qqplot, "Price", "Weekly_Units_Sold")
g.add_legend();

从上图中我们发现:

所有的商品在优惠和非优惠期间存在非常明显的价格差异,商品3在非优惠期间销售量最高。

总结

  • 商家10的销售量最大,而商家5的销售量最小
  • 商品2的销售量在每个月机会都要高于其他商品
  • 假期似乎对商家的销售影响不是很大
  • 商品2的价格是最便宜,而商品3的价格最贵。
  • 大多数商家的销售都收季节性影响,一般都会出现2个高峰
  • 2月份商品1的销售量比其他月份略多,4月份产品2的销售量最高,而7月份至9月份的产品3的销售量最高
  • 每个产品都有其正常价格和促销价格。产品1和商品2的正常价格和促销价格之间没有明显的差距,但是,商品3的促销价格可以削减到其原始价格的50%。尽管每个商家都对商品3进行了这种降价,但是商家10是降价期间销量最高的商店。
  • 8.商家10的商品3在7月至9月间是最畅销的产品。

时间序列 Prophet

我们将在商家10建立商品3的时间序列分析,并预测商品3未来50个周的销售额。

store_10_pro_3 = df[(df.Store == 10) & (df.Product == 3)].loc[:, ['Base Price', 'Price', 'Weekly_Units_Sold', 'weekly_sales']]
store_10_pro_3.reset_index(level=0, inplace=True)
fig = px.line(store_10_pro_3, x='Date', y='weekly_sales')
fig.update_layout(title_text='周销售额的时间序列')
fig.show()

商家10的商品3的周销售额受季节性影响非常大,每年的学校暑假7月至9月都是一个销售高峰。

下面我们要实现一个 prophet模型,并用它来预测未来50周的每周销售量。

store_10_pro_3 = store_10_pro_3[['Date', 'weekly_sales']].rename(columns = {'Date': 'ds','weekly_sales': 'y'})

model = Prophet(interval_width = 0.95)
model.fit(store_10_pro_3)
 
future_dates = model.make_future_dataframe(periods = 50, freq='W')

future_dates.tail(7)

 

forecast = model.predict(future_dates)

# 预测最后一周的日期
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(7)

model.plot(forecast);

看来我们的模型能够捕捉到季节性因素,它预测了在2013年7月至9月也有一个销售高峰

model.plot_components(forecast);

评估模型

metric_df = forecast.set_index('ds')[['yhat']].join(store_10_pro_3.set_index('ds').y).reset_index()
metric_df.dropna(inplace=True)
error = mean_squared_error(metric_df.y, metric_df.yhat)
print('The RMSE is {}'. format(sqrt(error)))

季节性因素

对于Prophet来说,我们还可以添加自己的自定义假期和节日。在这里,我们将添加从7月初到9月初的学校假期。

def is_school_holiday_season(ds):    
    date = pd.to_datetime(ds)
    starts = datetime.date(date.year, 7, 1)
    ends = datetime.date(date.year, 9, 9)
    return starts < date.to_pydatetime().date() < ends

store_10_pro_3['school_holiday_season'] = store_10_pro_3['ds'].apply(is_school_holiday_season)
store_10_pro_3['not_school_holiday_season'] = ~store_10_pro_3['ds'].apply(is_school_holiday_season)
model = Prophet(interval_width=0.95)

model.add_seasonality(name='school_holiday_season', period=365, fourier_order=3, condition_name='school_holiday_season')
model.add_seasonality(name='not_school_holiday_season', period=365, fourier_order=3, condition_name='not_school_holiday_season')
model.fit(store_10_pro_3)

forecast = model.make_future_dataframe(periods=50, freq='W')
forecast['school_holiday_season'] = forecast['ds'].apply(is_school_holiday_season)
forecast['not_school_holiday_season'] = ~forecast['ds'].apply(is_school_holiday_season)

forecast = model.predict(forecast)

plt.figure(figsize=(10, 5))
model.plot(forecast, xlabel = 'Date', ylabel = 'Weekly sales')
plt.title('Weekly sales forecast');

评估模型

metric_df = forecast.set_index('ds')[['yhat']].join(store_10_pro_3.set_index('ds').y).reset_index()
metric_df.dropna(inplace=True)
error = mean_squared_error(metric_df.y, metric_df.yhat)
print('The RMSE is {}'. format(sqrt(error)))

model.plot_components(forecast);

我们通过添加的自定义的假期条件,从而改善了模型的预测表现,模型的均方根误差由原来的:1190 下降到了1120。

参考文档:

Prophet:https://facebook.github.io/prophet/docs/quick_start.html#python-api
seaborn:https://seaborn.pydata.org/generated/seaborn.FacetGrid.html

你可以在这里下载本文的完整代码:

https://github.com/tongzm/ml-python/blob/master/Prophet%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97%E9%A2%84%E6%B5%8B.ipynb

猜你喜欢

转载自blog.csdn.net/weixin_42608414/article/details/104679017
今日推荐