50Kaggle 数据分析项目入门实战--分销商产品未来销售情况预测

分销商产品未来销售情况预测

未来销售额预测介绍

对于一个产品来说,其未来销售额的预测是一个重要的指标,也是一项重要的任务。例如,对于一部苹果手机来说。在上市之前,得先对销售额进行预测,才能确定出货量的大小。
本次实验来源于 Kaggle 上的一个挑战,即:未来销售额预测,由俄罗斯的 1C-Company 软件分销公司发起,并提供数据。
而本次实验的任务就是根据提供的数据,包含商品类别、商品名称、商店等信息和商品的历史销售数据来预测接下来一个月的销售额。

导入数据并预览

首先安装实验需要的 statsmodels 包。

!pip install statsmodels==0.9.0

现在下载数据,并进行解压。

!wget -nc "https://labfile.oss.aliyuncs.com/courses/1363/sales.zip"  # 下载数据
!unzip -o sales.zip

在 Kaggle 提供的数据集 sales 中,公共含有 5 份数据集,分别如下:
sales_train_v2.csv:训练数据集,包含 2013 年到 2015 年的历史销售数据;
item_categories.csv:销售商品的类别信息;
data/sales/items.csv:销售商品名称;
data/sales/shops.csv:销售商店的名称;
data/sales/test.csv:测试数据集,包含商品 ID 和商店 ID。

现在依次读取这五份数据集。

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()

在训练集中,数据总的列名表示含义如下:
data:时间信息;
date_block_num:月数,例如:将 2013.2 看做是 1 月,2014.2 看作是 13 月;
shop_id:商店的 ID;
item_id:商品的 ID;
item_price:商品的价格;
item_cnt_day:上面每天的销售量。

现在来看一下,销售商品的类别信息。

df_categories.head()

item_category_name 表示类别名称,因为该数据集是俄罗斯的一家科技公司提供的,所以。数据集中的信息都是俄文。现在看一下商品的信息数据集。

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\times×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()

训练集和测试集有两列是相同的,即商店(shop_id)和商品(item_id)。
现在对时间信息进行重新提取,只提取年和月。

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()

在我们的训练数据集中,为 2013 年 1 月到 2015 年 10 月的历史数据,而现在我们要预测的是 2015 年 11 月的数据。因此在测试集中创建相关的特征列。

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()

西班牙高速列车票价预测分析

数据集预处理

本次挑战的数据集为 Kaggle 提供的西班牙高铁运营情况数据。如果你想了解数据的相关信息,可以自行去 Kaggle 查阅相关的 数据描述 。现在先来加载数据,通过下面代码下载数据。

!wget -nc "https://labfile.oss.aliyuncs.com/courses/1363/Spanish.csv"

加载并预览数据集前 5 行。

import pandas as pd

df = pd.read_csv('./Spanish.csv')
df.head()

从上面的输出结果我们可以看到,数据中主要为高铁的一些基本运营信息,例如出发时间,出发地点等,每个列的具体含义如下:
insert_date:收集价格并写入数据库的日期和时间;
origin:出发城市;
destination:目的地城市;
start_date:火车出发时间(欧洲中部时间);
end_date:火车到达时间(欧洲中部时间);
train_type:列车服务名称;
price:价格(欧元);
train_class:票舱等级,如商务座等;
fare:票价,往返等。

我们可以将关于时间的列都拆分开来,拆分成为年、月、日等数值信息。

for col in ['insert_date', 'start_date', 'end_date']:
    date_col = pd.to_datetime(df[col])
    df[col + '_hour'] = date_col.dt.hour
    df[col + '_minute'] = date_col.dt.minute
    df[col + '_second'] = date_col.dt.second
    df[col + '_weekday'] = date_col.dt.weekday
    df[col + '_day'] = date_col.dt.day
    df[col + '_month'] = date_col.dt.month
    df[col + '_year'] = date_col.dt.year
    del df[col]
df.head()

现在我们已经得到一个相对完整的数据,我们看一一下数据找那个是否存在缺失值。
查看数据是否存在缺失值,将每列的缺失值进行求和。然后按降序进行排序,取前五个值输出。

df.isnull().sum().sort_values(ascending=False)[:5]

从上面的结果可以看到,price 、train_class 、 fare 都存在缺失值。现在直接对含缺失值的行进行删除。
删除所有存在缺失值的行。再重新查看数据是否存在缺失值,将每列的缺失值进行求和。然后按降序进行排序,取前五个值输出。

df_drop = df.dropna()
df_drop.isnull().sum().sort_values(ascending=False)[:5]

由于数据集中存在一些字符型特征,如 train_class 列中的取值 Turista 、Preferente 等,现在要将这些字符型特征转化为相应的数字,以便后续的算法能够运行。
对字符型特征进行编码。并打印出前 5 行。

from sklearn import preprocessing

# 取出非数值的列
categorical_feats = [
    f for f in df_drop.columns if df_drop[f].dtype == 'object']
# 对非数值的列进行编码
for col in categorical_feats:
    lb = preprocessing.LabelEncoder()
    lb.fit(list(df_drop[col].values.astype('str')))
    df_drop[col] = lb.transform(list(df_drop[col].values.astype('str')))
df_drop = df_drop.drop(['Unnamed: 0'], axis=1)

df_drop.head()

我们要预测的列为 price ,即车票的价格。因此,现在来取出特征集和目标集。

X = df_drop.drop(columns='price')
y = df_drop['price'].values

划分训练集和测试集。
按 1:1 的比例划分训练集和测试集。

from sklearn.model_selection import train_test_split

train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.5, random_state=2020)

打印数据集的形状。

train_X.shape, train_y.shape, test_X.shape, test_y.shape

构建预测模型,完成训练和测试。
构建预测模型,并训练。然后使用训练好的模型对测试集进行预测。

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

model = LinearRegression()
model.fit(train_X, train_y)
y_pred = model.predict(test_X)
mean_squared_error(y_pred, test_y)

为了直观的观测模型的拟合预测情况,我们可分别画出预测出的价格和真实的价格。
使用 Matplotlib 工具分别画出预测出的价格和真实的价格。图的尺寸设置为(16,5)。真实价格用红线表示,预测价格用绿线表示。且真实价格和预测价格都只画出前 100 个值。

from matplotlib import pyplot as plt
%matplotlib inline

fig1, ax1 = plt.subplots(figsize=(16, 5))
plt.plot(test_y[:100], color='r')
plt.plot(y_pred[:100], color='g')
image.png

Kaggle 数据分析独立项目挑战

挑战选题

选题一:心脏病诱发原因分析
心脏病是一种死亡率极高的病,那到底是什么原因导致了一个人会获得心脏病呢,对于这个问题,你可以取详细分析 Kaggle 提供的数据,看看是否能从其中找到答案。选题详情和数据下载可以参考 Kaggle 官方页面。
选题二:共享汽车分布位置分析
共享汽车现在是一个比较热门的行业,其给我们提供了极大的方便。那么对共享汽车厂商来说,共享汽车的位置分布非常重要,因为这可以指导共享汽车的投放量和投放位置。因此,该选题就是让你分析一下共享汽车的分布状况。选题详情和数据下载可以参考 Kaggle 官方页面。
选题三:全球人民幸福指数分析
第二次世界大战以来,整个世界整体处于和平状态,各国也都在努力的发展,GDP 也都是直线上升。那么各国人民的幸福指数如何呢,幸福指数会跟什么有关系,GDP 越高幸福指数越高吗?对于这些问题,你可以分析一下 Kaggle 提供的数据。去寻找你想要的答案。选题详情和数据下载可以参考 Kaggle 官方页面。

你可能感兴趣的:(50Kaggle 数据分析项目入门实战--分销商产品未来销售情况预测)