未来销售额预测--kaggle项目笔记

不同与根据数据特征预测,此项目利用时间序列数据,即根据以往的销售额来预测未来短期内的销售额。
原项目链接
本次项目的任务就是根据提供的数据,包含商品类别、商品名称、商店等信息和商品的历史销售数据来预测接下来一个月的销售额。

导入数据

#安装需要的 statsmodels 包
!pip install statsmodels==0.9.0
#下载数据并解压
!wget -nc "https://labfile.oss.aliyuncs.com/courses/1363/sales.zip"  # 下载数据
!unzip -o sales.zip

在这里插入图片描述
读取5份数据

import pandas as pd
import warnings
warnings.filterwarnings("ignore")

df_train = pd.read_csv('./sales/sales_train_v2.csv')
df_categories = pd.read_csv("./sales/item_categories.csv")
df_items = pd.read_csv("./sales/items.csv")
df_shops = pd.read_csv("./sales/shops.csv")
df_test = pd.read_csv("./sales/test.csv")
#查看训练集
df_train.head()

在这里插入图片描述

df_categories.head()
df_items.head()
df_shops.head()

#将这几份数据集合并为一个数据集
df_train = pd.merge(df_train, df_items, on='item_id', how='inner')
df_train = pd.merge(df_train, df_categories,
                    on='item_category_id', how='inner')
df_train = pd.merge(df_train, df_shops, on='shop_id', how='inner')

df_test = pd.merge(df_test, df_items, on='item_id', how='inner')
df_test = pd.merge(df_test, df_categories, on='item_category_id', how='inner')
df_test = pd.merge(df_test, df_shops, on='shop_id', how='inner')

df_train.head()

数据可视化

销售量分布图

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(14, 4))

# 这里使用了对数函数平滑,所以只选大于 0  的数据据
g = sns.distplot(
    np.log(df_train[df_train['item_cnt_day'] > 0]['item_cnt_day']))
g.set_title("Item Sold Count Distribuition", fontsize=18)
g.set_ylabel("Frequency", fontsize=12)

销售价格分布图

#这里为了更加直观,在绘图时,使用对数函数对商品价格进行平滑。
plt.figure(figsize=(14, 4))

# 这里使用了对数函数平滑,所以只选大于 0  的数据据
g2 = sns.distplot(np.log(df_train[df_train['item_price'] > 0]['item_price']))
g2.set_title("Items Price Log Distribuition", fontsize=18)
g2.set_xlabel("")
g2.set_ylabel("Frequency", fontsize=15)

一天总的营业额

df_train['total_amount'] = df_train['item_price'] * df_train['item_cnt_day']
#查看一下这商品价格、商品销售额、商品销售数据的分布情况,这里使用  四分位数 来描述。
def quantiles(df, columns):
    for name in columns:
        print(name + " quantiles")
        # 打印出四分位数
        print(df[name].quantile([.01, .25, .5, .75, .99]))
        print("")

quantiles(df_train, ['item_cnt_day', 'item_price', 'total_amount'])

对于商店的分布情况,使用 squarify 库提供的树地图

!pip install squarify

import squarify
import random
# 指定颜色
color = ["#"+''.join([random.choice('0123456789ABCDEF')
                      for j in range(6)]) for i in range(30)]
# 获得数量最多的前 20 个商店。
shop = round((df_train["shop_name"].value_counts()[:20]
              / len(df_train["shop_name"]) * 100), 2)

plt.figure(figsize=(20, 10))
# 画出图形
g = squarify.plot(sizes=shop.values, label=shop.index,
                  value=shop.values,
                  alpha=.8, color=color)
g.set_title("'TOP 20 Stores/Shop - % size of total", fontsize=20)
g.set_axis_off()
plt.show()
#在图中,占的空格越大,表示该商店的数量越多,里面的数字表示所占的百分比

对于商店所有商品的价格情况,使用著名的绘图工具 plotly 来绘制

import plotly.offline as offline
import plotly.graph_objs as go
import plotly.offline as py
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=True)
offline.init_notebook_mode()

temp = df_train.groupby('shop_name')['item_price'].sum()
# 画出柱状图
trace = [go.Bar(x=temp.index, y=temp.values,)]
# 设置图的字体颜色等
layout = go.Layout(
    title="TOP 25 Shop Name by Total Amount Sold ",
    yaxis=dict(title='Total Sold')
)
# 画出图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig, filename='schoolStateNames')
#结论:每个商店所销售的商品价格总和都是不一样的,差异非常明显

同样的方法,我来看商店每天的销售情况

temp = df_train.groupby('shop_name')['item_cnt_day'].sum()
# 画出柱状图
trace = [go.Bar(x=temp.index, y=temp.values,)]
# 设置图的字体颜色等
layout = go.Layout(
    title="TOP 25 Shop Name by Total Amount Sold ",
    yaxis=dict(title='Total Sold')
)
# 画出图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig, filename='schoolStateNames')
#商店每天的销售量与上面所绘制的价格分布类似。可能的原因是,前面所绘制的价格分布图是商店所有商品的价格总和,其值越高,意味着商品的数量越多,销量也就越高。

商品的种类信息,为了方便,只取数量最多的前十五个商品类别来进行观察。

# 获得商品类别计数
top_cats = df_train.item_category_name.value_counts()[:15]

plt.figure(figsize=(14, 4))
# 画出统计图
g1 = sns.countplot(x='item_category_name',
                   data=df_train[df_train.item_category_name.isin(top_cats.index)])
g1.set_xticklabels(g1.get_xticklabels(), rotation=70)
g1.set_title("TOP 15 Principal Products Sold", fontsize=22)
g1.set_xlabel("")
g1.set_ylabel("Count", fontsize=18)

最高的三个类别:Кино - DVD:DVD 电影
Игры PC - Стандартные издания:PC 游戏标准版
Музыка - CD локального производства:音乐 CD 本地生产

查看哪种类别的商品卖得最好

plt.figure(figsize=(14, 4))
# 画出箱线图
g2 = sns.boxplot(x='item_category_name', y='item_cnt_day',
                   data=df_train[df_train.item_category_name.isin(top_cats.index)])
g2.set_xticklabels(g2.get_xticklabels(), rotation=70)
g2.set_title("Principal Categories by Item Solds Log", fontsize=22)
g2.set_xlabel("")
g2.set_ylabel("Items Sold Log Distribution", fontsize=18)
#结论:各种类型的商品的销售额基本上是差不多的

数量最多的前二十五个商品的一些信息

# 获得商品名的计数
count_item = df_train.item_name.value_counts()[:25]
plt.figure(figsize=(14, 4))
# 画出统计图
g = sns.countplot(
    x='item_name', data=df_train[df_train.item_name.isin(count_item.index)])
g.set_xticklabels(g.get_xticklabels(), rotation=90)
g.set_title("Count of Most sold Items", fontsize=22)
g.set_xlabel('', fontsize=18)
g.set_ylabel("Total Count of ", fontsize=18)
#结论:大多数商品的数量是一致的,但有一个比较例外,即图中的倒数第二个,翻译如下:
#Фирменный пакет майка 1С Интерес белый (34×42) 45 мкм :迈克1c品牌套装白色(34*42)45微米

这些商品与总销售额的关系

plt.figure(figsize=(14, 4))
# 画出箱线图
g1 = sns.boxplot(x='item_name', y='total_amount',
                   data=df_train[df_train.item_name.isin(count_item.index)])
g1.set_xticklabels(g1.get_xticklabels(), rotation=90)
g1.set_title("Count of Category Name in Top Bills", fontsize=22)
g1.set_xlabel('', fontsize=18)
g1.set_ylabel("Total Count in expensive bills", fontsize=18)
#结论:大多数商品的销售额是一致的。但在图中的最后一列,销售额明显要比其它产品高一点点,翻译如下:
#Sony PlayStation 4 (500 Gb) Black (CUH-1008A/1108A/B01):索尼PS 4(500 GB)黑色(CUH-1008A/1108A/B01)

销售量等信息与时间的关系

#先将数据集中的 date 列转化为时间戳。
def date_process(df):
    # 转化为时间戳
    df["date"] = pd.to_datetime(df["date"], format="%d.%m.%Y")
    df["_weekday"] = df['date'].dt.weekday  # 获取周
    df["_day"] = df['date'].dt.day  # 获取天
    df["_month"] = df['date'].dt.month  # 获取月
    return df


df_train = date_process(df_train)
df_train[["date", "_weekday", "_day", "_month"]].head()
#从上面的结果可以看到,时间序列并不是按照顺序来的,先对其进行处理。
dates_temp = df_train['date'].value_counts().reset_index().sort_values('index')
# 对列名重新命名
dates_temp = dates_temp.rename(
    columns={"date": "Total_Bills"}).rename(columns={"index": "date"})
dates_temp.head()

统计每天的商品价格总和

dates_temp_sum = df_train.groupby('date')['item_price'].sum().reset_index()
dates_temp_sum.head()

统计每天卖出去的商品的数量

dates_temp_count = df_train[df_train['item_cnt_day'] > 0].groupby(
    'date')['item_cnt_day'].sum().reset_index()
dates_temp_count.head()

对上面的统计结果用图形绘制出来

# 定义图形
trace0 = go.Scatter(x=dates_temp.date.astype(str), y=dates_temp.Total_Bills,
                    opacity=0.8, name='Total tickets')
trace1 = go.Scatter(x=dates_temp_sum.date.astype(str), name="Total Amount",
                    y=dates_temp_sum['item_price'], opacity=0.8)
trace2 = go.Scatter(x=dates_temp_count.date.astype(str), name="Total Items Sold",
                    y=dates_temp_count['item_cnt_day'], opacity=0.8)
# 设置标题等参数
layout = dict(
    title="Informations by Date",
    xaxis=dict(rangeselector=dict(buttons=list([
        dict(count=1, label='1m', step='month', stepmode='backward'),
        dict(count=3, label='3m', step='month', stepmode='backward'),
        dict(count=6, label='6m', step='month', stepmode='backward'),
        dict(step='all')])),
        rangeslider=dict(visible=True), type='date'))
# 画出图形
fig = dict(data=[trace0, trace1, trace2], layout=layout)
iplot(fig)
#结论:在 2014 年 1 月的某天和 2015 年 1 月的某天。销售额都达到了顶峰。可能的原因是在这一段时间俄罗斯可能有某个特殊的节日,例如像我们国家的双 11。因此销售额提升了。

每个月的总销售量

temp = df_train.groupby(['_month'])['item_cnt_day'].sum()
# 画出柱状图
trace = [go.Bar(x=temp.index, y=temp.values,)]
# 设置图的字体颜色等
layout = go.Layout(
    title="Total orders by Month",
    xaxis=dict(title='Months'),
    yaxis=dict(title='Total Orders')
)
# 画出图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig, filename='schoolStateNames')
#1 月和 12 月的销售量都相对较高

特征工程

重新读取数据。

# 重新导入数据
df_train = pd.read_csv("./sales/sales_train_v2.csv")
df_test = pd.read_csv("./sales/test.csv")

#查看训练集和测试集的形状
df_train.shape, df_test.shape
df_train.head()
df_test.head()

时间信息进行重新提取,只提取年和月

df_train['date'] = pd.to_datetime(df_train['date'], format='%d.%m.%Y')
df_train['month'] = df_train['date'].dt.month
df_train['year'] = df_train['date'].dt.year

删除掉原来的 data 列,为了方便处理,同时也删除掉 item_price 列。

df_train1 = df_train.drop(['date', 'item_price'], axis=1)

提取每个月的销售总量。

# 求出商品每个月的销售总量
df_train1 = df_train1.groupby([c for c in df_train1.columns if c not in [
                              'item_cnt_day']], as_index=False)[['item_cnt_day']].sum()
df_train2 = df_train1.rename(columns={'item_cnt_day': 'item_cnt_month'})
df_train2.head()

对某个商店的某个商品在每个月的销量取平均值

shop_item_monthly_mean = df_train2[['shop_id', 'item_id', 'item_cnt_month']].groupby(
    ['shop_id', 'item_id'], as_index=False)[['item_cnt_month']].mean()
shop_item_monthly_mean1 = shop_item_monthly_mean.rename(
    columns={'item_cnt_month': 'item_cnt_month_mean'})
shop_item_monthly_mean1.head()

#将上面所提取到的平均值合并到训练数据集中,作为一个新的特征列
df_train3 = pd.merge(df_train2, shop_item_monthly_mean1,
                     how='left', on=['shop_id', 'item_id'])
df_train3.head()
df_test['month'] = 11
df_test['year'] = 2015
df_test['date_block_num'] = 34
df_test.head()

#同样把上面所提取的某个商店的某个商品在每个月的销量取平均值合并到测试集中
df_test1 = pd.merge(df_test, shop_item_monthly_mean1,
                    how='left', on=['shop_id', 'item_id'])
df_test1.head()
#从上面的结果可以看到,数据中存在缺失值,可能是原因是,测试集中的商品在训练集中不存在,所以为缺失值。现在对这些缺失值用 0 来填充
df_test1 = df_test1.fillna(0.)
df_test1.head()

构建模型

在构建模型之前,我们先对训练集进行划分,取一部分来测试一下我们的模型,这样方便我们对模型的调试。当我们把模型调试好时,再用全部的数据进行训练,然后对测试集进行预测。

feature_list = [c for c in df_train3.columns if c not in 'item_cnt_month']
x1 = df_train3[df_train3['date_block_num'] < 33]
y1 = np.log1p(x1['item_cnt_month'].clip(0., 20.))
x1 = x1[feature_list]
x2 = df_train3[df_train3['date_block_num'] == 33]
y2 = np.log1p(x2['item_cnt_month'].clip(0., 20.))
x2 = x2[feature_list]

随机森林构建模型

from sklearn.ensemble import RandomForestRegressor
# 定义模型
model = RandomForestRegressor(n_estimators=20,
                              random_state=42,
                              max_depth=5,
                              n_jobs=-1)
model.fit(x1, y1)  # 训练模型

#模型训练完成之后,使用前面对训练集划分出来的一部分验证集进行测试。看一下我们构建的模型的情况。
plt.figure(figsize=(14, 4))
y_pred = model.predict(x2)
plt.plot(y_pred[:100])
plt.plot(y2.values[:100])
#所构建的随机森林模型预测效果并不是很好,可能的原因是,我们没有对其进行调参。
#对所有的训练数据集进行训练,然后对测试集进行预测
model.fit(df_train3[feature_list], df_train3['item_cnt_month'].clip(0., 20.))

df_test1['item_cnt_month'] = model.predict(
    df_test1[feature_list]).clip(0., 20.)

df_test1.head()

可以将本问题看做是一个回归任务,将其当做回归任务来实现,除了这种实现方式之外,还可以使用时间序列处理的方法来进行预测。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值