Kaggle项目:Predict Future Sales(商品未来销量预测)

本文详细介绍了Kaggle上的一个商品未来销量预测项目,通过对数据的预处理、清洗、分析和模型训练,最终实现对未来销量的预测。在数据预处理阶段,涉及训练集和测试集的处理、商店和商品数据的整合,以及异常值过滤。通过销量和营收分析,揭示了销量下降的原因,并探讨了特定商品对营业收入的影响。在模型训练中,采用LightGBM模型,经过特征工程处理,最终得到预测效果,项目得分0.93740,排名2560/7373。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 关于项目

1.1 背景介绍

这是Kaggle竞赛上的一个项目。项目数据由俄罗斯最大的软件公司之一的 1C Company 提供。数据集包含了2013年1月1日到2015年10月31日该公司各商店的商品销售记录。
项目目标是预测该公司接下来2015年11月的商品销量。
项目得分使用RMSD(均方根误差,即得分越低代表预测结果的误差越小,预测效果越好。)进行评估。
项目提交的预测值范围需要在[0,20]。
项目链接:https://www.kaggle.com/c/competitive-data-science-predict-future-sales


1.2项目数据集说明

项目数据集简要说明
数据集说明
sales_train.csv训练集
(包含2013年1月至2015年10月间每天各商店各商品的销量)
items.csv商品的补充数据集
(包含商品名称和所属分类字段)
item_categories.csv商品类目的补充数据集
(包含商品所属类目的详细信息)
shops.csv商店信息的补充数据集
(包含商店所在城市和商店规模的信息)
shops.csv test.csv测试集
(需要预测的接下来的2015年11月份的各商店的商品销量)
sample_submission.csv项目提交数据的模板


2. 目标

分析该公司的经营状况,找出影响商品销量的相关因素,预测未来一个月该公司各商店不同商品的销量。


3. 数据预处理

3.1 项目数据集预处理

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

3.1.1 训练集和测试集

train = pd.read_csv('sales_train.csv')
test= pd.read_csv('test.csv')
train.head()
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
002.01.201305922154999.001.0
103.01.20130252552899.001.0
205.01.20130252552899.00-1.0
306.01.201302525541709.051.0
415.01.201302525551099.001.0
test.head()
IDshop_iditem_id
0055037
1155320
2255233
3355232
4455268
print('训练集的商店数量: %d ,商品数量: %d;\n' % (train['shop_id'].unique().size, train['item_id'].unique().size),
     '测试的商店数量: %d,商品数量: %d。' % (test['shop_id'].unique().size, test['item_id'].unique().size))
训练集的商店数量: 60 ,商品数量: 21807;
 测试的商店数量: 42,商品数量: 5100。
test[~test['shop_id'].isin(train['shop_id'].unique())]
IDshop_iditem_id

test[~test['item_id'].isin(train['item_id'].unique())]['item_id'].unique()[:10]
array([5320, 5268, 5826, 3538, 3571, 3604, 3407, 3408, 3405, 3984],
      dtype=int64)

测试集里面出现了训练集没有的商品,但是可以根据商店的营销情况和商品类目进行预测。如果直接设置为0的话,反而会使提交的数据成绩减分。这个估计也是要考察模型的泛化能力


3.1.2 商店数据集

shops = pd.read_csv('shops.csv')
shops.head()
shop_nameshop_id
0!Якутск Орджоникидзе, 56 фран0
1!Якутск ТЦ "Центральный" фран1
2Адыгея ТЦ "Мега"2
3Балашиха ТРК "Октябрь-Киномир"3
4Волжский ТЦ "Волга Молл"4

经过谷歌翻译和百度翻译得知这个是俄罗斯的语言。其中有几个相同商店名称但是不同ID的店铺
- 39号: РостовНаДону ТРК "Мегацентр Горизонт"
- 40号: РостовНаДону ТРК "Мегацентр Горизонт" Островной
上面这两个商店名,差别在最后一个单词,翻译是“岛”,但是在谷歌地图中查找俄罗斯包含“Мегацентр Горизонт”这个名字的购物中心只有РостовНаДону ТРК这个地方上有且只有一个。推测这两个是同一个商店不同叫法。

- 10号: Жуковский ул. Чкалова 39м?
- 11号: Жуковский ул. Чкалова 39м2
这两个推测是书写不一致导致的。

- 0号: !Якутск Орджоникидзе, 56 фран
- 57号: !Якутск Орджоникидзе, 56
这两个也是书写的不一致的问题,类似某某街道56,和某某街道56号的区别。

- 58号:Якутск ТЦ "Центральный"
- 1号: !Якутск ТЦ "Центральный" фран
同上。

- 12 和 56 是线上商店


# 查看测试集是否包含了这几个商店
test[test['shop_id'].isin([39, 40, 10, 11, 0, 57, 58, 1, 12 ,56])]['shop_id'].unique()
array([10, 12, 57, 58, 56, 39], dtype=int64)

测试集中没有包含同一商店的不同ID, 需要对训练集重复商店的不同ID进行修改,修改的ID则以测试集为准。

shop_id_map = {11: 10, 0: 57, 1: 58, 40: 39}
train.loc[train['shop_id'].isin(shop_id_map), 'shop_id'] = train.loc[train['shop_id'].isin(shop_id_map), 'shop_id'].map(shop_id_map)
train.loc[train['shop_id'].isin(shop_id_map), 'shop_id']
Series([], Name: shop_id, dtype: int64)
train.loc[train['shop_id'].isin([39, 40, 10, 11, 0, 57, 58, 1]), 'shop_id'].unique()
array([57, 58, 10, 39], dtype=int64)


对商店名称进行简单分析后,发现商店名称有一个命名规律

大部分商店的名称:

  • 开头是一个地区的名称;
  • 中间是商店的规模(比如购物中心:ТЦ、大型购物娱乐中心:ТРЦ等);
  • 尾部带引号的是商店的名称,比如‘xxx’购物中心,大部分可以在谷歌地图上搜索到。
shops['shop_city'] = shops['shop_name'].map(lambda x:x.split(' ')[0].strip('!'))
shop_types = ['ТЦ', 'ТРК', 'ТРЦ', 'ТК', 'МТРЦ']
shops['shop_type'] = shops['shop_name'].map(lambda x:x.split(' ')[1] if x.split(' ')[1] in shop_types else 'Others')
shops.loc[shops['shop_id'].isin([12, 56]), ['shop_city', 'shop_type']] = 'Online'  # 12和56号是网上商店
shops.head(13)
shop_nameshop_idshop_cityshop_type
0!Якутск Орджоникидзе, 56 фран0ЯкутскOthers
1!Якутск ТЦ "Центральный" фран1ЯкутскТЦ
2Адыгея ТЦ "Мега"2АдыгеяТЦ
3Балашиха ТРК "Октябрь-Киномир"3БалашихаТРК
4Волжский ТЦ "Волга Молл"4ВолжскийТЦ
5Вологда ТРЦ "Мармелад"5ВологдаТРЦ
6Воронеж (Плехановская, 13)6ВоронежOthers
7Воронеж ТРЦ "Максимир"7ВоронежТРЦ
8Воронеж ТРЦ Сити-Парк "Град"8ВоронежТРЦ
9Выездная Торговля9ВыезднаяOthers
10Жуковский ул. Чкалова 39м?10ЖуковскийOthers
11Жуковский ул. Чкалова 39м²11ЖуковскийOthers
12Интернет-магазин ЧС12OnlineOnline
# 对商店信息进行编码,降低模型训练的内存消耗
shop_city_map = dict([(v,k) for k, v in enumerate(shops['shop_city'].unique())])
shop_type_map = dict([(v,k) for k, v in enumerate(shops['shop_type'].unique())])
shops['shop_city_code'] = shops['shop_city'].map(shop_city_map)
shops['shop_type_code'] = shops['shop_type'].map(shop_type_map)
shops.head(7)
shop_nameshop_idshop_cityshop_typeshop_city_codeshop_type_code
0!Якутск Орджоникидзе, 56 фран0ЯкутскOthers00
1!Якутск ТЦ "Центральный" фран1ЯкутскТЦ01
2Адыгея ТЦ "Мега"2АдыгеяТЦ11
3Балашиха ТРК "Октябрь-Киномир"3БалашихаТРК22
4Волжский ТЦ "Волга Молл"4ВолжскийТЦ31
5Вологда ТРЦ "Мармелад"5ВологдаТРЦ43
6Воронеж (Плехановская, 13)6ВоронежOthers50

3.1.3 商品数据集

items = pd.read_csv('items.csv')
items
item_nameitem_iditem_category_id
0! ВО ВЛАСТИ НАВАЖДЕНИЯ (ПЛАСТ.) D040
1!ABBYY FineReader 12 Professional Edition Full...176
2***В ЛУЧАХ СЛАВЫ (UNV) D240
3***ГОЛУБАЯ ВОЛНА (Univ) D340
4***КОРОБКА (СТЕКЛО) D440
............
22165Ядерный титбит 2 [PC, Цифровая версия]2216531
22166Язык запросов 1С:Предприятия [Цифровая версия]2216654
22167Язык запросов 1С:Предприятия 8 (+CD). Хрустале...2216749
22168Яйцо для Little Inu2216862
22169Яйцо дракона (Игра престолов)2216969

22170 rows × 3 columns


# 数据集比较大,只分析有没有重复名称不同ID的商品
items['item_name'] = items['item_name'].map(lambda x: ''.join(x.split(' ')))  # 删除空格
duplicated_item_name = items[items['item_name'].duplicated()]
duplicated_item_name 
item_nameitem_iditem_category_id
2558DEEPPURPLEComeHellOrHighWaterDVD255859
2970Divinity:DragonCommander[PC,Цифроваяверсия]297031
5063NIRVANAUnpluggedInNewYorkLP506358
14539МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион)1453940
19475СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel)1947543
19581ТАРЗАН(BD)1958137

duplicated_item_name_rec = items[items['item_name'].isin(duplicated_item_name['item_name'])]  # 6个商品相同名字不同id的记录
duplicated_item_name_rec
item_nameitem_iditem_category_id
2514DEEPPURPLEComeHellOrHighWaterDVD251459
2558DEEPPURPLEComeHellOrHighWaterDVD255859
2968Divinity:DragonCommander[PC,Цифроваяверсия]296831
2970Divinity:DragonCommander[PC,Цифроваяверсия]297031
5061NIRVANAUnpluggedInNewYorkLP506158
5063NIRVANAUnpluggedInNewYorkLP506358
14537МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион)1453740
14539МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион)1453940
19465СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel)1946543
19475СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel)1947543
19579ТАРЗАН(BD)1957937
19581ТАРЗАН(BD)1958137


【依旧是查看测试里面包含了哪一些重复项】

test[test['item_id'].isin(duplicated_item_name_rec['item_id'])]['item_id'].unique()
array([19581,  5063], dtype=int64)


【测试集包含了2个同名不同id的商品。且都是较大的ID值。需要把训练集里小的ID值都映射为对应较大的ID值。】

old_id = duplicated_item_name_rec['item_id'].values[::2]
new_id = duplicated_item_name_rec['item_id'].values[1::2]
old_new_map = dict(zip(old_id, new_id))
old_new_map
{2514: 2558, 2968: 2970, 5061: 5063, 14537: 14539, 19465: 19475, 19579: 19581}
train.loc[train['item_id'].isin(old_id), 'item_id'] = train.loc[train['item_id'].isin(old_id), 'item_id'].map(old_new_map)
train[train['item_id'].isin(old_id)]
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
train[train['item_id'].isin(duplicated_item_name_rec['item_id'].values)]['item_id'].unique()  # 旧id成功替换成新id
array([ 2558, 14539, 19475, 19581,  5063,  2970], dtype=int64)

3.1.4 商品类目数据集

items.groupby('item_id').size()[items.groupby('item_id').size() > 1]  # 检查同一个商品是否分了不同类目
Series([], dtype: int64)
cat = pd.read_csv('item_categories.csv')
cat
item_category_nameitem_category_id
0PC - Гарнитуры/Наушники0
1Аксессуары - PS21
2Аксессуары - PS32
3Аксессуары - PS43
4Аксессуары - PSP4
.........
79Служебные79
80Служебные - Билеты80
81Чистые носители (шпиль)81
82Чистые носители (штучные)82
83Элементы питания83

84 rows × 2 columns

cat[cat['item_category_name'].duplicated()]
item_category_nameitem_category_id

对类别名称进行简单分析后,发现大部分都是‘大类-小类’的组合形式
Аксессуары :配件
Аксессуары - PS2 :PS2游戏机配件
Игровые консоли :游戏机

【先拆分大类】

cat['item_type'] = cat['item_category_name'].map(lambda x: 'Игры' if x.find('Игры ')>0 else x.split(' -')[0].strip('\"')) 
cat.iloc[[32, 33, 34, -3, -2, -1]]  # 有几个比较特殊,需要另外调整一下
item_category_nameitem_category_iditem_type
32Карты оплаты (Кино, Музыка, Игры)32Карты оплаты (Кино, Музыка, Игры)
33Карты оплаты - Live!33Карты оплаты
34Карты оплаты - Live! (Цифра)34Карты оплаты
81Чистые носители (шпиль)81Чистые носители (шпиль)
82Чистые носители (штучные)82Чистые носители (штучные)
83Элементы питания83Элементы питания
cat.iloc[[32,-3, -2], -1] = ['Карты оплаты', 'Чистые носители', 'Чистые носители' ]
cat.iloc[[32,-3, -2]]
item_category_nameitem_category_iditem_type
32Карты оплаты (Кино, Музыка, Игры)32Карты оплаты
81Чистые носители (шпиль)81Чистые носители
82Чистые носители (штучные)82Чистые носители
item_type_map = dict([(v,k) for k, v in enumerate(cat['item_type'].unique())])
cat['item_type_code'] = cat['item_type'].map(item_type_map)
cat.head()
item_category_nameitem_category_iditem_typeitem_type_code
0PC - Гарнитуры/Наушники0PC0
1Аксессуары - PS21Аксессуары1
2Аксессуары - PS32Аксессуары1
3Аксессуары - PS43Аксессуары1
4Аксессуары - PSP4Аксессуары1

【接着是拆分小类】
cat['sub_type'] = cat['item_category_name'].map(lambda x: x.split('-',1)[-1]) 
cat
item_category_nameitem_category_iditem_typeitem_type_codesub_type
0PC - Гарнитуры/Наушники0PC0Гарнитуры/Наушники
1Аксессуары - PS21Аксессуары1PS2
2Аксессуары - PS32Аксессуары1PS3
3Аксессуары - PS43Аксессуары1PS4
4Аксессуары - PSP4Аксессуары1PSP
..................
79Служебные79Служебные15Служебные
80Служебные - Билеты80Служебные15Билеты
81Чистые носители (шпиль)81Чистые носители16Чистые носители (шпиль)
82Чистые носители (штучные)82Чистые носители16Чистые носители (штучные)
83Элементы питания83Элементы питания17Элементы питания

84 rows × 5 columns

cat['sub_type'].unique()
array([' Гарнитуры/Наушники', ' PS2', ' PS3', ' PS4', ' PSP', ' PSVita',
       ' XBOX 360', ' XBOX ONE', 'Билеты (Цифра)', 'Доставка товара',
       ' Прочие', ' Аксессуары для игр', ' Цифра',
       ' Дополнительные издания', ' Коллекционные издания',
       ' Стандартные издания', 'Карты оплаты (Кино, Музыка, Игры)',
       ' Live!', ' Live! (Цифра)', ' PSN', ' Windows (Цифра)', ' Blu-Ray',
       ' Blu-Ray 3D', ' Blu-Ray 4K', ' DVD', ' Коллекционное',
       ' Артбуки, энциклопедии', ' Аудиокниги', ' Аудиокниги (Цифра)',
       ' Аудиокниги 1С', ' Бизнес литература', ' Комиксы, манга',
       ' Компьютерная литература', ' Методические материалы 1С',
       ' Открытки', ' Познавательная литература', ' Путеводители',
       ' Художественная литература', ' CD локального производства',
       ' CD фирменного производства', ' MP3', ' Винил',
       ' Музыкальное видео', ' Подарочные издания', ' Атрибутика',
       ' Гаджеты, роботы, спорт', ' Мягкие игрушки', ' Настольные игры',
       ' Настольные игры (компактные)', ' Открытки, наклейки',
       ' Развитие', ' Сертификаты, услуги', ' Сувениры',
       ' Сувениры (в навеску)', ' Сумки, Альбомы, Коврики д/мыши',
       ' Фигурки', ' 1С:Предприятие 8', ' MAC (Цифра)',
       ' Для дома и офиса', ' Для дома и офиса (Цифра)', ' Обучающие',
       ' Обучающие (Цифра)', 'Служебные', ' Билеты',
       'Чистые носители (шпиль)', 'Чистые носители (штучные)',
       'Элементы питания'], dtype=object)
sub_type_map = dict([(v,k) for k, v in enumerate(cat['sub_type'].unique())])
cat['sub_type_code'] = cat['sub_type'].map(sub_type_map)
cat.head()
item_category_nameitem_category_iditem_typeitem_type_codesub_typesub_type_code
0PC - Гарнитуры/Наушники0PC0Гарнитуры/Наушники0
1Аксессуары - PS21Аксессуары1PS21
2Аксессуары - PS32Аксессуары1PS32
3Аксессуары - PS43Аксессуары1PS43
4Аксессуары - PSP4Аксессуары1PSP4

【合并商品和类目数据集】
items = items.merge(cat[['item_category_id', 'item_type_code', 'sub_type_code']], on='item_category_id', how='left')
items.head()
item_nameitem_iditem_category_iditem_type_codesub_type_code
0!ВОВЛАСТИНАВАЖДЕНИЯ(ПЛАСТ.)D0401024
1!ABBYYFineReader12ProfessionalEditionFull[PC,Ц...1761459
2***ВЛУЧАХСЛАВЫ(UNV)D2401024
3***ГОЛУБАЯВОЛНА(Univ)D3401024
4***КОРОБКА(СТЕКЛО)D4401024
import gc
del cat
gc.collect()
2917



3.2 训练集数据清洗

3.2.1 过滤离群值

利用散点图观察商品价格和单日销量的分布情况

sns.jointplot('item_cnt_day', 'item_price', train, kind='scatter')
<seaborn.axisgrid.JointGrid at 0xd6e1d68>

在这里插入图片描述

先过滤明显的离群值

train_filtered = train[(train['item_cnt_day'] < 800) & (train['item_price'] < 70000)].copy()
sns.jointplot('item_cnt_day', 'item_price', train_filtered, kind='scatter')
<seaborn.axisgrid.JointGrid at 0xd7bd7b8>

在这里插入图片描述

查看价格和销量的异常情况

outer = train[(train['item_cnt_day'] > 400) | (train['item_price'] > 40000)]
outer
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
88513817.09.20138121136559200.0000001.0
100663824.10.2013912723842000.0000001.0
116315813.12.201311126066307980.0000001.0
148813520.03.201414251319950999.0000001.0
150116015.03.20141424209495.000000405.0
157325223.04.2014152780571200.000000401.0
157325322.04.2014152780571200.000000502.0
170820728.06.20141725209495.000000501.0
204851802.10.2014211292421500.000000512.0
206766704.10.2014215519437899.000000401.0
206766909.10.2014215519437899.000000508.0
206767709.10.20142155194451249.000000412.0
214390320.11.201422121417340900.0000001.0
225729919.12.20142312209494.000000500.0
232693015.01.20152412209494.0000001000.0
232715929.01.20152412724149782.0000001.0
260804014.04.2015271237311904.548077624.0
262584719.05.20152812102091499.000000480.0
262618119.05.2015281211373155.192950539.0
285107329.09.2015325592491500.000000533.0
285109130.09.2015325592491702.825746637.0
286423530.09.2015321292481692.526158669.0
286426029.09.2015321292481500.000000504.0
288569223.10.201533421340342990.0000001.0
289310020.10.201533381340341990.0000001.0
290940114.10.20153312209494.000000500.0
290981828.10.20153312113730.9087142169.0
291015520.10.201533121340341990.0000001.0
291015629.10.201533121340342990.0000001.0
291326722.10.201533181340341990.0000001.0
291776020.10.20153331340342990.0000001.0
292757222.10.201533281340340991.0000001.0
293138020.10.201533221340342990.0000001.0

再检查是否需要修改过滤的阈值

outer_set = train_filtered[train_filtered['item_id'].isin(outer['item_id'].unique())].groupby('item_id')
 
fig, ax = plt.subplots(1,1,figsize=(10, 10))
colors = sns.color_palette() + sns.color_palette('bright')  # 使用调色板。默认颜色只有10来种,会重复使用,不便于观察
i = 1
for name, group in outer_set:
    ax.plot(group['item_cnt_day'], group['item_price'], marker='o', linestyle='', ms=12, label=name, c=colors[i])
    i += 1
ax.legend()

plt.show()

在这里插入图片描述

上图可以看出:
1. 青蓝的11365、 橙色的13199 、深红的7241 出现了价格异常高的情况 (大于45000)。(价格在25000 到 45000 是否要考虑)
2. 浅粉的9248、灰色的9249、橘色的3731 出现了销量特别高的情况 (大于600)。(销量高于500的记录也大部分偏离较远,是否需要计入离群值的考量)

train[train['item_id'].isin([13403,7238, 14173])]
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
100663824.10.2013912723842000.01.0
214390320.11.201422121417340900.01.0
288569223.10.201533421340342990.01.0
288569325.10.201533421340328992.01.0
288569429.10.201533421340337991.02.0
289061726.10.201533311340335991.01.0
289310020.10.201533381340341990.01.0
291015520.10.201533121340341990.01.0
291015629.10.201533121340342990.01.0
291326722.10.201533181340341990.01.0
291776020.10.20153331340342990.01.0
292757222.10.201533281340340991.01.0
293138020.10.201533221340342990.01.0
293263726.10.201533251340337991.01.0

7238号和14173号只出现过一次,13403号则是新品, 可以考虑过滤掉7238号和214173号

train.loc[train['item_id']==13403].boxplot(['item_cnt_day', 'item_price'])
<matplotlib.axes._subplots.AxesSubplot at 0xf53dc50>

在这里插入图片描述

销量高于500的记录中, 蓝色11373和灰色9249的记录看起来明显离群,应该过滤。
剩下的大于400小于600之间的最大值是512。
再查看400到520这中间的商品的销量情况。

m_400 = train[(train['item_cnt_day'] > 400) & (train['item_cnt_day'] < 520)]['item_id'].unique()
n = m_400.size
fig, axes = plt.subplots(1,n,figsize=(n*4, 6))
for i in range(n):
    train[train['item_id'] == m_400[i]].boxplot(['item_cnt_day'], ax=axes[i])
    axes[i].set_title('Item%d' % m_400[i])
plt.show()

在这里插入图片描述

销量大于400的记录都偏离其总体水平较远。

总结:
过滤掉7238号和214173号,过滤掉单价高于45000的记录,过滤掉日销量高于400的记录 。
本该查看所有商品销量的离群值。但是数量太多了,耗时且不实际。暂且只过滤与总体差异较为显著的。
况且商品交易本身存在一定的不确定性,去掉全部异常,保留范围内的数据建立的理想化模型来模拟实际情况,结果可能适得其反。保留数据中可接受范围的噪声数据反而更符合实际情况。

filtered = train[(train['item_cnt_day'] < 400) & (train['item_price'] < 45000)].copy()
filtered.head()
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
002.01.201305922154999.001.0
103.01.20130252552899.001.0
205.01.20130252552899.00-1.0
306.01.201302525541709.051.0
415.01.201302525551099.001.0
filtered.drop(index=filtered[filtered['item_id'].isin([7238, 14173])].index, inplace=True)
del train, train_filtered
gc.collect()
60

训练集出现销量为-1的情况,估计是出现退货了,属于正常情况。
需要查看有没有小于0的id或者价格。

(filtered[['date_block_num', 'shop_id','item_id', 'item_price']] < 0).any()
date_block_num    False
shop_id           False
item_id           False
item_price         True
dtype: bool
# 商品单价小于0的情况
filtered[filtered['item_price'] <= 0]
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
48468315.05.20134322973-1.01.0
filtered.groupby(['date_block_num','shop_id', 'item_id'])['item_price'].mean().loc[4, 32, 2973]
1249.0
filtered.loc[filtered['item_price'] <= 0, 'item_price'] = 1249.0  # 用了同一个月同一个商店该商品的均价
filtered[filtered['item_price'] <= 0]  # 检查是否替换成功
datedate_block_numshop_iditem_iditem_priceitem_cnt_day
# 下面也给出替换的函数
def clean_by_mean(df, keys, col):
    """
    用同一月份的均值替换小于等于0的值
    keys 分组键;col 需要替换的字段
    """
    group = df[df[col] <= 0]
    # group = df[df['item_price'] <= 0]
    mean_price = df.groupby(keys)[col].mean()
    # mean_price = df.groupby(['date_block_num', 'shop_id', 'item_id'])['item_price'].mean()
    for i, row in group.iterrows:
        record = group.loc[i]
        df.loc[i,col] = mean_price.loc[record[keys[0]], record[keys[1]], record[keys[2]]]
        # df.loc[i,'item_price'] = mean_price.loc[record['date_block_num'], record['shop_id'], record['item_id']]
    return df



4. 数据规整 和 数据分析(EDA)

# 添加日营业额
filtered['turnover_day'] = filtered['item_price'] * filtered['item_cnt_day']
filtered
datedate_block_numshop_iditem_iditem_priceitem_cnt_dayturnover_day
002.01.201305922154999.001.0999.00
103.01.20130252552899.001.0899.00
205.01.20130252552899.00-1.0-899.00
306.01.201302525541709.051.01709.05
415.01.201302525551099.001.01099.00
........................
293584410.10.201533257409299.001.0299.00
293584509.10.201533257460299.001.0299.00
293584614.10.201533257459349.001.0349.00
293584722.10.201533257440299.001.0299.00
293584803.10.201533257460299.001.0299.00

2935824 rows × 7 columns



4.1 销量分析

item_sales_monthly = filtered.pivot_table(columns='item_id',
                                          index='date_block_num', 
                                          values='item_cnt_day',
                                          fill_value=0,
                                          aggfunc=sum)
item_sales_monthly.head()
item_id0123456789...22160221612216222163221642216522166221672216822169
date_block_num
00000000000...11000000020
10000000000...7000000020
20000000000...6000000010
30000000000...2000000000
40000000000...6100000000

5 rows × 21796 columns

fig, axes = plt.subplots(1,2, figsize=(20, 8))

item_sales_monthly.sum(1).plot(ax=axes[0], title='Total sales of each month', xticks=[i for i in range(0,34,2)])  # 每月总销量
item_sales_monthly.sum(0).plot(ax=axes[1], title='Total sales of each item')  # 每个商品的总销量
plt.subplots_adjust(wspace=0.2)

在这里插入图片描述

描述:
总体销量出现下滑趋势,且每月销量大部分都同比下降。
有一款商品的销量异常高。

top_sales = item_sales_monthly.sum().sort_values(ascending=False)
top_sales
item_id
20949    184736
2808      17245
3732      16642
17717     15830
5822      14515
          ...  
8515          0
11871        -1
18062        -1
13474        -1
1590        -11
Length: 21796, dtype: int64
test[test['item_id'].isin(top_sales[top_sales<=0].index)]
IDshop_iditem_id


4.1.1 销量最高的商品

top_sales.iloc[0] / item_sales_monthly.sum().sum() * 100  # 销量占比
5.0801850069973
item_sales_monthly[top_sales.index[0]].plot(kind='bar', figsize=(12,6))  # 每月销量
<matplotlib.axes._subplots.AxesSubplot at 0x1101a240>

在这里插入图片描述

item_turnover_monthly = filtered.pivot_table(index= 'date_block_num',
                                               columns= 'item_id',
                                               values='turnover_day',
                                               fill_value=0,
                                               aggfunc=sum)
item_turnover_monthly.head()
item_id0123456789...22160221612216222163221642216522166221672216822169
date_block_num
00000000000...147700.00.00.0000.01598.00
10000000000...96200.00.00.0000.01598.00
20000000000...89400.00.00.0000.0798.50
30000000000...29800.00.00.0000.00.00
40000000000...894580.00.00.0000.00.00

5 rows × 21796 columns

item_sales_monthly = item_sales_monthly.drop(columns=top_sales[top_sales<=0].index, axis=1)  # 去掉销量为0和负值的商品
item_turnover_monthly = item_turnover_monthly.drop(columns=top_sales[top_sales<=0].index, axis=1)
total_turnover = item_turnover_monthly.sum().sum()
item_turnover_monthly[top_sales.index[0]].sum() / total_turnover * 100
0.02703470087824863

在总销量排名第一的商品,其总销量占到所有总销量的5.080%,但是其总营收却只占到了所有商品总营收的0.027%

items[items['item_id']==20949 ]
item_nameitem_iditem_category_iditem_type_codesub_type_code
20949Фирменныйпакетмайка1СИнтересбелый(34*42)45мкм20949711354

翻译过来是一种打包用的包装。该商品的销量依赖于其他需要包装的商品的销售情况。

该产品出现月销量总体下降的情况,说明相应依赖的商品也是总体销量呈下降的趋势。


4.1.2 分析总体销量呈现下降趋势可能存在的原因

使该公司月销量出现总体下降的原因可能存在于两个方面:
一是:商店商品本身热度或生命名周期,等商品内在相关因素,使得商品销量减少。
二是:商品下架或者缺货等外在原因导致商品不再销售,从而影响商品销量。
这两方面的因素不是孤立存在的,对于总体销量来说,这两个影响因素往往是同时存在的。

(item_sales_monthly > 0).sum(1).plot(figsize=(12, 6))
<matplotlib.axes._subplots.AxesSubplot at 0x1e59a860>

在这里插入图片描述

2013年每个月在有销量的商品的数量(在售商品数量)都在7500个以上。
2014年每月有销量的商品下降到了约7500-6000个。
2015年则下降到约6500-500个。

小结:在售商品数量呈现总体持续下降的趋势。

item_sales_monthly.sum(1).div((item_sales_monthly > 0).sum(1)).plot(figsize=(12, 6))
# 商品月总销量 / 当月在售商品数量 = 当月在售商品平均销量
<matplotlib.axes._subplots.AxesSubplot at 0x1896d940>

在这里插入图片描述

小结:2013年和2014年在售商品月平均销量基本都在13-16个,2015年的在售商品月平均销量则下降到12-14个。

结论:
商品丰富度下降是导致总体销量呈现下降趋势的主要因素之一。
除此之外,2015年在售商品的月均销量相比2013年和2014年下降有所下降,使得2015年总体销量下降幅度高于2013年和2014年。



4.2 营收分析

fig, axes = plt.subplots(1,2, figsize=(20, 8))
item_turnover_monthly.sum(1).plot(ax=axes[0], title='Total turnovers of each month', xticks=[i for i in range(0,34,2)])  # 每月总营收
item_turnover_monthly.sum(0).plot(ax=axes[1], title='Total turnovers of each item')  # 每个商品的总营收
plt.subplots_adjust(wspace=0.2)

在这里插入图片描述

描述:
第23个月份的营业收入比第11个月同增长明显,而在销量方面则是同比下跌的。
有一款商品的总营业收入异常的高。


4.2.1 总营收最高的商品

top_turnover = item_turnover_monthly.sum().sort_values(ascending=False)
top_turnover
item_id
6675     2.193915e+08
3732     4.361798e+07
13443    3.433125e+07
3734     3.106516e+07
3733     2.229886e+07
             ...     
18098    2.100000e+01
3856     1.700000e+01
7756     1.500000e+01
22010    1.400000e+01
22098    7.000000e+00
Length: 21788, dtype: float64
item_turnover_monthly[top_turnover.index[0]].sum() / total_turnover * 100
6.472732889978659
item_sales_monthly[top_turnover.index[0]].sum() / item_sales_monthly.sum().sum() * 100
0.2829433478063709

在总营业收入排名第一的商品,其销量占总销量的0.28%,其营业收入占公司总营业收入的6.47% 。

item_turnover_monthly[top_turnover.index[0]].plot(kind='bar', figsize=(12, 6))
<matplotlib.axes._subplots.AxesSubplot at 0x18b3b8d0>

在这里插入图片描述

item_turnover_monthly[top_turnover.index[0]].div(item_turnover_monthly.sum(1)).plot(figsize=(12, 6),xticks=[i for i in range(0,34,2)])
<matplotlib.axes._subplots.AxesSubplot at 0x18cce588>

在这里插入图片描述

items[items['item_id']==top_turnover.index[0]]
item_nameitem_iditem_category_iditem_type_codesub_type_code
6675SonyPlayStation4(500Gb)Black(CUH-1008A/1108A/B01)66751243

这是一款索尼PS4游戏机的一个型号,在13年11月份开始销售,营业收入最的月份为2013年12月,占当月公司总营业收入比例约23%。在15年最后2个月已经没有销量了。



4.3 分析导致14年底销量和营收同比增长趋势不一致的可能原因

turnover_monthly = item_turnover_monthly.sum(1)
sales_monthly = item_sales_monthly.sum(1)
fig, axe1 = plt.subplots(1, 1, figsize=(16, 6))
axe2 = axe1.twinx()
axe1.plot(turnover_monthly.index, turnover_monthly.values, c='r')

axe2.plot(sales_monthly.index, sales_monthly.values, c='b')
axe2.grid(c='c', alpha=0.3)
axe1.legend(['Monthly Turnover'],fontsize=13, bbox_to_anchor=(0.95, 1))
axe2.legend(['Monthly Sales'],fontsize=13, bbox_to_anchor=(0.93, 0.9))
axe1.set_ylabel('Monthly Turnover', c='r')
axe2.set_ylabel('Monthly Sales', c='b')
plt.show()

在这里插入图片描述

sales_growth = item_sales_monthly.loc[23].sum() - item_sales_monthly.loc[11].sum()
sales_growth_rate = sales_growth / item_sales_monthly.loc[11].sum() * 100
turnover_growth = item_turnover_monthly.loc[23].sum() - item_turnover_monthly.loc[11].sum()
turnover_growth_rate = turnover_growth / item_turnover_monthly.loc[11].sum() * 100
print(
    ' 销售同比增长量为: %.2f ,同比增长率为: %.2f%%;\n' % (sales_growth, sales_growth_rate),
    '营收同比增长量为: %.2f ,同比增长率为: %.2f%%。' % (turnover_growth, turnover_growth_rate)
     )
 销售同比增长量为: -15086.00 ,同比增长率为: -8.23%;
 营收同比增长量为: 24759419.35 ,同比增长率为: 11.95%。

第23月(2014年12月)销量同比第11月(2013年12月)下降了约15000,但是第23月营业收入同比第11月反而上涨了2500000。
同比增长比率为:销量同比下降8.23%,营业收入同比上涨11.95%。

dec_set = item_turnover_monthly.loc[[11, 23]]
dec_set
item_id0123456789...22160221612216222163221642216522166221672216822169
date_block_num
110000000000...000.00.00.00480024613.20.00
23000002802800...000.00.00.00165011960.00.00

2 rows × 21788 columns


观察这两个月份营业收入的分布情况:

plt.figure(figsize=(8, 4))
sns.boxenplot(x=dec_set)
'c' argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with 'x' & 'y'.  Please use a 2-D array with a single row if you really want to specify the same RGB or RGBA value for all points.





<matplotlib.axes._subplots.AxesSubplot at 0x18b3bf28>

在这里插入图片描述

有三款商品的营业收入明显高于其他商品

dec_top = dec_set.loc[:,dec_set.sum() > 5000000]
dec_top  # 年底营收最高的商品
item_id66751340513443
date_block_num
114.645973e+070.000000e+000.0
234.014789e+067.906979e+0624203004.2

在2013年或2014年年底营业收入远超其他商品的商品中,13405号和13443号商品只出现在2014年年底。

可以从新增的这两款商品对营收影响这方面着手分析。

dec_top.iloc[1, 1:].sum() / dec_set.iloc[1].sum() * 100  # 只在第23月出售的商品其营业额占第23个月所有商品营业额的百分比
13.839127677594432
item_sales_monthly.loc[23,dec_top.columns[1:]].sum() / item_sales_monthly.loc[23].sum() * 100
# 13405和13443号商品在第23月销量之和与所有商品总销量的百分比
0.8701078719800304

13405号和13443号商品在2014年12月的营业收入占该月所有商品营业收入的约13.84%,而该月销量只占该月所有总销量的0.87%。

(dec_set.iloc[1].sum() - dec_set.iloc[0].sum()) / dec_set.iloc[0].sum() * 100  # 同比增长率
11.945851204367324
(dec_set.iloc[1].sum() - dec_set.iloc[0].sum()) / dec_set.iloc[1].sum() * 100  # 增长量占总额的百分比
10.67109774578344
item_turnover_monthly[dec_top.columns[1:]].plot(figsize=(12, 6))
<matplotlib.axes._subplots.AxesSubplot at 0x18a29860>

在这里插入图片描述

该公司14年12月份营业收入同比增长11.94%,同比增长量占总量10.67%。
而13405号和13443号商品在2014年12月的营业收入占该月所有商品营业收入的约13.84%,超过增长量。

结论:
13405号和13443号商品是该公司14年12月份营业收入同比大幅度增长的主要因素之一。
此外,在前面分析6675号商品中,该商品在2013年12月营收占比约为23%,在2014年的营收占比下降到了约为3%。说明2014年还有其他商品贡献了这约为20%的营业收入。



4.4 帕累托贡献度分析

item_turnover_prop_cumsum = item_turnover_monthly.sum().div(total_turnover).sort_values(ascending=False).cumsum()
pct80 = item_turnover_prop_cumsum.searchsorted(0.8) + 1
pct80_items = item_turnover_prop_cumsum.iloc[:pct80].index
pct80_items
Int64Index([ 6675,  3732, 13443,  3734,  3733, 16787,  3731, 13405, 17717,
             5823,
            ...
             7073, 14333, 15240, 15450,  5581,  3458,  2040,  4837,  1515,
            20658],
           dtype='int64', name='item_id', length=1678)
sales_pct = item_sales_monthly[pct80_items].sum().sum()/ item_sales_monthly.sum().sum()
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.pie([sales_pct, 1 - sales_pct],
        labels=['占总营收80%的商品总销量 ', '占总营收20%的商品总销量 '],
       autopct="%.1f%%",
       textprops={'fontsize':13,'color':"black"}, 
        startangle=90)
plt.title('帕累托贡献度分析:销量对营收的贡献', fontdict={'fontsize':15,'color':"black"})
plt.show() 

在这里插入图片描述

小结: 54.2%的商品销量产生了80%的营收效益。45.8%的商品销量只产生了20%的效益。


4.5 店铺对营业收入的贡献

filtered.groupby('shop_id')['item_cnt_day'].sum().sort_values().plot(kind='bar', figsize=(12, 6))
<matplotlib.axes._subplots.AxesSubplot at 0x1e959710>

在这里插入图片描述

filtered.groupby('shop_id')['turnover_day'].sum().sort_values().plot(kind='bar', figsize=(12, 6))
<matplotlib.axes._subplots.AxesSubplot at 0x1e6c35c0>

在这里插入图片描述

  • 对总销量贡献的前三个商店分别是31、25、54号。28号的销量与54号相差不大。
  • 对总营业收费贡献的前三个商店分别是31、25、28号。
shops[shops['shop_id'].isin([31, 25, 54, 28])]
shop_nameshop_idshop_cityshop_typeshop_city_codeshop_type_code
25Москва ТРК "Атриум"25МоскваТРК142
28Москва ТЦ "МЕГА Теплый Стан" II28МоскваТЦ141
31Москва ТЦ "Семеновский"31МоскваТЦ141
54Химки ТЦ "Мега"54ХимкиТЦ271

小结:

对总销量和总营业收入贡献前三名的商店都来自同一个城市。
Москва 即:莫斯科,是俄罗斯联邦首都、莫斯科州首府,Химки也是来自莫斯科州,莫斯科州GDP占俄罗斯全国GDP的1/3。
* 商店所在城市的经济规模在很大程度上影响了商店的商品销量。


4.6 产品种类对营业收入的贡献

filtered = filtered.merge(items.iloc[:,1:], on='item_id', how='left')
filtered.head()
datedate_block_numshop_iditem_iditem_priceitem_cnt_dayturnover_dayitem_category_iditem_type_codesub_type_code
002.01.201305922154999.001.0999.00371021
103.01.20130252552899.001.0899.00581241
205.01.20130252552899.00-1.0-899.00581241
306.01.201302525541709.051.01709.05581241
415.01.201302525551099.001.01099.00561239
filtered.groupby('item_category_id')['turnover_day'].sum().sort_values().plot(kind='bar',figsize=(16,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0x1e1d31d0>

在这里插入图片描述

filtered.groupby('item_type_code')['turnover_day'].sum().sort_values().plot(kind='bar',figsize=(12,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0xf7c1438>

在这里插入图片描述

filtered.groupby('sub_type_code')['turnover_day'].sum().sort_values().plot(kind='bar',figsize=(12,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0x6c28a20>

在这里插入图片描述

小结:

  • 商品类目中对总营业收入贡献最大的前三类分别是第19、20、12类。
  • 商品大类中第5类商品对总营业收入贡献最大,且与其他类差异明显。
  • 商品小类中对总营业收入贡献最大的前三类分别是第3、2、6类。
filtered.groupby('item_category_id')['item_cnt_day'].sum().sort_values().plot(kind='bar',figsize=(16,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0x1e9510f0>

在这里插入图片描述

filtered.groupby('item_type_code')['item_cnt_day'].sum().sort_values().plot(kind='bar',figsize=(12,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0x10fc67b8>

在这里插入图片描述

filtered.groupby('sub_type_code')['item_cnt_day'].sum().sort_values().plot(kind='bar',figsize=(12,6), rot=0)
<matplotlib.axes._subplots.AxesSubplot at 0x1089d9b0>

在这里插入图片描述

小结:

  • 商品类目中对总销量贡献最大的前三类分别是第40、30、55类。
  • 商品大类中对总销量贡献最大的前三类分别是第10、8、5类,
  • 商品小类中对总销量贡献最大的前三类分别是第24、15、38类。

  • 在商品大类销量与营业收入综合贡献中,影响最大的是第5和第8类。
  • 在商品类目和商品小类中销量和营收前三名中,没有出现重叠的情况。没有综合贡献突出的类。

* 不同种类的商品存在较为明显的销量差异。

4.7 城市和商店类型对营业收入的贡献

filtered = filtered.merge(shops[['shop_id','shop_city_code','shop_type_code']], on='shop_id', how='left')
filtered.head()
datedate_block_numshop_iditem_iditem_priceitem_cnt_dayturnover_dayitem_category_iditem_type_codesub_type_codeshop_city_codeshop_type_code
002.01.201305922154999.001.0999.00371021291
103.01.20130252552899.001.0899.00581241142
205.01.20130252552899.00-1.0-899.00581241142
306.01.201302525541709.051.01709.05581241142
415.01.201302525551099.001.01099.00561239142
filtered.groupby('shop_city_code')['turnover_day'].sum().plot(kind='bar',figsize=(12,6))
<matplotlib.axes._subplots.AxesSubplot at 0x10895860>

在这里插入图片描述

filtered.groupby('shop_type_code')['turnover_day'].sum().plot(kind='bar',figsize=(12,6))
<matplotlib.axes._subplots.AxesSubplot at 0x1af6aac8>

在这里插入图片描述

小结: 14区城市的商店对总营业收入贡献最大。1类商店对总营业收入贡献最大。

filtered.groupby('shop_city_code')['item_cnt_day'].sum().plot(kind='bar',figsize=(12,6))
<matplotlib.axes._subplots.AxesSubplot at 0x1b2500b8>

在这里插入图片描述

filtered.groupby('shop_type_code')['item_cnt_day'].sum().plot(kind='bar',figsize=(12,6))
<matplotlib.axes._subplots.AxesSubplot at 0x1b27cda0>

在这里插入图片描述

小结:

  • 在销量方面也是14区城市的商店和1类商店贡献最大。
  • 主要影响因素是总营收和总销量排名前三的商店都在14区城市。

* 除了前面分析的商店所在城市会影响商品销量,商店的规模同样在较大程度上影响了商品销量。

题外话:
前面三句红色加粗的话,看起来从常识就可以推断出来了,我在查阅别人的方案时,看到的几乎是直接跳过对这些特征的分析的过程,只给出哪些特征是有用的,容易让人不明所以。
以我个人的理解,通过观测这些特征的值的分布情况,可以直观地了解不同特征对结果影响程度的差异。
而且在后面特征重要性画图时,这些特征的重要性和特征本身对结果影响程度两者之间的一致性,可以作为特征选择可靠性的评判参考。



5. 预测未来销量

5.1 处理关闭的商店和停售的商品

shop_sales_monthly = filtered.pivot_table(index='date_block_num',
                                          columns='shop_id',
                                          values='item_cnt_day',
                                          fill_value=0,
                                          aggfunc=sum)
shop_open_month_cnt = (shop_sales_monthly.iloc[-6:] >  0).sum()  # 有销量的记录
shop_open_month_cnt.head()  # 每个店铺最后半年里有几个月有销量
shop_id
2    6
3    6
4    6
5    6
6    6
dtype: int64
shop_c_n = shop_sales_monthly[shop_open_month_cnt[shop_open_month_cnt < 6].index]
shop_c_n.tail(12)
# 最后半年经营月数少于6个月的店铺
shop_id8913172023272930323336435154
date_block_num
2200011990046751926204408140265910906389
23000183200789624022700010560313916527677
240006890056601564184201006013409766043
25000000383912737450792006604221
260000003634123900505005454625
270-100003518121600-100494732
280000003786880000007580
2900000033570000006590
3000000024780000007480
3100000000000009160
32000000-10000006240
3303186002611000000330000

9, 20, 36 当作是新店,其他的当作已经关闭了

open_shop = shop_sales_monthly[shop_open_month_cnt[shop_open_month_cnt == 6].index]
open_shop.tail(7) # 最后半年都正常经营的商店
shop_id23456710121415...48495052535556575859
date_block_num
27859740899105419981340594262010551364...10815428951152132234221237286017101054
2884373189310121748121746629309331277...1110692107389412062117131524081378916
2980467279395415391235441183010191332...99078990082010781909156624401554913
307855358429911484132744915549541316...1102856112682812571658149123521689992
31942666947129415751409442147110611360...13089661081932131819761604278017381214
32822745732109217251287519404210941267...1101567906108612295697119422661319914
33727613831105218021212428151210021243...111164894984710611972126323161446790

7 rows × 41 columns

item_selling_month_cnt = (item_sales_monthly.iloc[-6:] >  0).sum() 
item_selling_month_cnt.head()  # 这些商品在最后半年有几个月有销量
item_id
0    0
1    0
2    0
3    0
4    0
dtype: int64
item_zero = item_sales_monthly[item_selling_month_cnt[item_selling_month_cnt == 0].index]
# 这些商品在最后半年都没有销量
item_zero.tail(12)
item_id0123456789...22150221512215222156221572216022161221652216822169
date_block_num
220010000000...0000000000
230000010100...0000000000
240000000000...0000000000
250000000000...0000000000
260000000000...0000000000
270000000000...0000000000
280000000000...0000000000
290000000000...0000000000
300000000000...0000000000
310000000000...0000000000
320000000000...0000000000
330000000000...0000000000

12 rows × 12878 columns

selling_item = item_sales_monthly[item_selling_month_cnt[item_selling_month_cnt > 0].index]
selling_item.tail(12)  # 最后半年有销量的商品
item_id30313233383940424549...22153221542215522158221592216222163221642216622167
date_block_num
22131129201001383...000000001649
231618402111052102...100000001140
24142542197012112...00000000733
2514133226411124...100003110289846
265124020100232...000001940921240
274132013001155...0000078027438
28552012300222...00001235023831
294102611200113...0010322061033
30462115500134...0800027012834
316533014710044...160001429201129
32391916200163...041007209521
331182216001112...050101026151137

12 rows × 8910 columns



5.2 处理训练集

只保留最后6个月正常经营的商店和有销量的商品

cl_set = filtered[filtered['shop_id'].isin(open_shop.columns) & filtered['item_id'].isin(selling_item.columns)]
cl_set
datedate_block_numshop_iditem_iditem_priceitem_cnt_dayturnover_dayitem_category_iditem_type_codesub_type_codeshop_city_codeshop_type_code
002.01.201305922154999.01.0999.0371021291
103.01.20130252552899.01.0899.0581241142
205.01.20130252552899.0-1.0-899.0581241142
1003.01.20130252574399.02.0798.0551238142
1105.01.20130252574399.01.0399.0551238142
.......................................
293581910.10.201533257409299.01.0299.0551238142
293582009.10.201533257460299.01.0299.0551238142
293582114.10.201533257459349.01.0349.0551238142
293582222.10.201533257440299.01.0299.0571240142
293582303.10.201533257460299.01.0299.0551238142

1782392 rows × 12 columns

统计月销量

from itertools import product
import time
ts = time.time()
martix = []
for i in range(34):
    record = cl_set[cl_set['date_block_num'] == i]
    group = product([i],record.shop_id.unique(),record.item_id.unique())
    martix.append(np.array(list(group)))
            
cols = ['date_block_num', 'shop_id', 'item_id']
martix = pd.DataFrame(np.vstack(martix), columns=cols)

martix
date_block_numshop_iditem_id
005922154
10592552
20592574
30592607
40592515
............
494019933217635
494020033217638
494020133217640
494020233217632
494020333217440

4940204 rows × 3 columns

from itertools import product
import time
ts = time.time()
martix = []
for i in range(34):
    record = filtered[filtered['date_block_num'] == i]
    group = product([i],record.shop_id.unique(),record.item_id.unique())
    martix.append(np.array(list(group)))
            
cols = ['date_block_num', 'shop_id', 'item_id']
martix = pd.DataFrame(np.vstack(martix), columns=cols)

martix
date_block_numshop_iditem_id
005922154
10592552
20592554
30592555
40592564
............
1084073533217635
1084073633217638
1084073733217640
1084073833217632
1084073933217440

10840740 rows × 3 columns

del cl_set
gc.collect()
20
group = filtered.groupby(['date_block_num', 'shop_id', 'item_id']).agg({'item_cnt_day': np.sum})
group.columns = ['item_cnt_month']
group.reset_index(inplace=True)
group
date_block_numshop_iditem_iditem_cnt_month
002271.0
102331.0
2023171.0
3024381.0
4024712.0
...............
16082423359220876.0
16082433359220882.0
16082443359220911.0
16082453359221001.0
16082463359221021.0

1608247 rows × 4 columns

martix = pd.merge(martix, group, on=['date_block_num', 'shop_id', 'item_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_month
0059221541.0
10592552NaN
20592554NaN
30592555NaN
40592564NaN

测试集是第34月的数据,所以给测试集合添加上'date_block_num'字段并设置为34。
待预测的字段‘item_cnt_mun’就设置为0。

test['date_block_num'] = 34
test['item_cnt_month'] = 0
martix = pd.concat([martix.fillna(0), test.drop(columns='ID')], sort=False, ignore_index=True, keys=['date_block_num','shop_id','item_id'])
martix
date_block_numshop_iditem_iditem_cnt_month
0059221541.0
105925520.0
205925540.0
305925550.0
405925640.0
...............
110549353445184540.0
110549363445161880.0
110549373445157570.0
110549383445196480.0
1105493934459690.0

11054940 rows × 4 columns



5.3 特征处理

5.3.1 融合商店数据集和商品数据集的特征

martix = martix.merge(shops[['shop_id', 'shop_type_code', 'shop_city_code']], on='shop_id', how='left')
martix = martix.merge(items.drop(columns='item_name'), on='item_id', how='left')
martix
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_code
0059221541.0129371021
105925520.0129581241
205925540.0129581241
305925550.0129561239
405925640.0129591242
..............................
110549353445184540.0121551238
110549363445161880.0121641347
110549373445157570.0121551238
110549383445196480.0121401024
1105493934459690.0121371021

11054940 rows × 9 columns

添加具体的年份和月份

martix['year'] =  martix['date_block_num'].map(lambda x: x // 12 + 2013)
martix['month'] = martix['date_block_num'].map(lambda x: x % 12)
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonth
0059221541.012937102120130
105925520.012958124120130
205925540.012958124120130
305925550.012956123920130
405925640.012959124220130


5.3.2 添加当月销量特征


当月商店、商品销量均值
# 商品 月销量均值
group = martix.groupby(['date_block_num','item_id']).agg({'item_cnt_month':'mean'})
group.columns = ['item_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num', 'item_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avg
0059221541.0129371021201300.400000
105925520.0129581241201300.000000
205925540.0129581241201300.022222
305925550.0129561239201300.044444
405925640.0129591242201300.111111
# 商店 月销量均值
group = martix.groupby(['date_block_num','shop_id']).agg({'item_cnt_month':'mean'})
group.columns = ['shop_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num', 'shop_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avg
0059221541.0129371021201300.4000000.248613
105925520.0129581241201300.0000000.248613
205925540.0129581241201300.0222220.248613
305925550.0129561239201300.0444440.248613
405925640.0129591242201300.1111110.248613

当月种类销量均值
# 类别 月销量均值
group = martix.groupby(['date_block_num','item_category_id']).agg({'item_cnt_month':'mean'})
group.columns = ['cat_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num', 'item_category_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.199738
105925520.0129581241201300.0000000.2486130.043386
205925540.0129581241201300.0222220.2486130.043386
305925550.0129561239201300.0444440.2486130.049630
405925640.0129591242201300.1111110.2486130.093842
# 商店-类别 月销量均值
group = martix.groupby(['date_block_num','shop_id','item_category_id']).agg({'item_cnt_month':'mean'})
group.columns = ['shop_cat_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num','shop_id','item_category_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avgshop_cat_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.1997380.088496
105925520.0129581241201300.0000000.2486130.0433860.000000
205925540.0129581241201300.0222220.2486130.0433860.000000
305925550.0129561239201300.0444440.2486130.0496300.008333
405925640.0129591242201300.1111110.2486130.0938420.012048
# 大类 月销量均值
group = martix.groupby(['date_block_num', 'item_type_code']).agg({'item_cnt_month':'mean'})
group.columns = ['itemtype_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num', 'item_type_code'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avgshop_cat_cnt_month_avgitemtype_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.1997380.0884960.284744
105925520.0129581241201300.0000000.2486130.0433860.0000000.166607
205925540.0129581241201300.0222220.2486130.0433860.0000000.166607
305925550.0129561239201300.0444440.2486130.0496300.0083330.166607
405925640.0129591242201300.1111110.2486130.0938420.0120480.166607
# 小类 月销量均值
group = martix.groupby(['date_block_num', 'sub_type_code']).agg({'item_cnt_month':'mean'})
group.columns = ['subtype_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num','sub_type_code'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avgshop_cat_cnt_month_avgitemtype_cnt_month_avgsubtype_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.1997380.0884960.2847440.199738
105925520.0129581241201300.0000000.2486130.0433860.0000000.1666070.043386
205925540.0129581241201300.0222220.2486130.0433860.0000000.1666070.043386
305925550.0129561239201300.0444440.2486130.0496300.0083330.1666070.049630
405925640.0129591242201300.1111110.2486130.0938420.0120480.1666070.093842

当月商店城市和商店类型的销量均值
# 城市-商品 月销量均值
group = martix.groupby(['date_block_num','shop_city_code','item_id']).agg({'item_cnt_month':'mean'})
group.columns = ['city_item_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num','shop_city_code','item_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avgshop_cat_cnt_month_avgitemtype_cnt_month_avgsubtype_cnt_month_avgcity_item_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.1997380.0884960.2847440.1997381.0
105925520.0129581241201300.0000000.2486130.0433860.0000000.1666070.0433860.0
205925540.0129581241201300.0222220.2486130.0433860.0000000.1666070.0433860.0
305925550.0129561239201300.0444440.2486130.0496300.0083330.1666070.0496300.0
405925640.0129591242201300.1111110.2486130.0938420.0120480.1666070.0938420.0
# 商店类型-商品 月销量均值
group = martix.groupby(['date_block_num','shop_type_code','item_id']).agg({'item_cnt_month':'mean'})
group.columns = ['shoptype_item_cnt_month_avg']
group.reset_index(inplace=True)
martix = martix.merge(group, on=['date_block_num','shop_type_code','item_id'], how='left')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonthitem_cnt_month_avgshop_cnt_month_avgcat_cnt_month_avgshop_cat_cnt_month_avgitemtype_cnt_month_avgsubtype_cnt_month_avgcity_item_cnt_month_avgshoptype_item_cnt_month_avg
0059221541.0129371021201300.4000000.2486130.1997380.0884960.2847440.1997381.00.28
105925520.0129581241201300.0000000.2486130.0433860.0000000.1666070.0433860.00.00
205925540.0129581241201300.0222220.2486130.0433860.0000000.1666070.0433860.00.00
305925550.0129561239201300.0444440.2486130.0496300.0083330.1666070.0496300.00.04
405925640.0129591242201300.1111110.2486130.0938420.0120480.1666070.0938420.00.12
del group
gc.collect()
28



5.3.3 添加销量特征的历史特征

本次项目目标是用历史销量数据预测未来销量。
所以在这里我们转换一下思路,用历史销量数据作为模型的特征,将本月销量结果作为标签建立模型进行回归分析。

def lag_feature(df, lags, col):
    tmp = df[['date_block_num','shop_id','item_id',col]]
    for i in lags:
        shifted = tmp.copy()
        shifted.columns = ['date_block_num','shop_id','item_id', col+'_lag_'+str(i)]
        shifted['date_block_num'] += i
        df = pd.merge(df, shifted, on=['date_block_num','shop_id','item_id'], how='left')
    return df
martix = lag_feature(martix, [1,2,3,6,12], 'item_cnt_month')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...shop_cat_cnt_month_avgitemtype_cnt_month_avgsubtype_cnt_month_avgcity_item_cnt_month_avgshoptype_item_cnt_month_avgitem_cnt_month_lag_1item_cnt_month_lag_2item_cnt_month_lag_3item_cnt_month_lag_6item_cnt_month_lag_12
0059221541.01293710212013...0.0884960.2847440.1997381.00.28NaNNaNNaNNaNNaN
105925520.01295812412013...0.0000000.1666070.0433860.00.00NaNNaNNaNNaNNaN
205925540.01295812412013...0.0000000.1666070.0433860.00.00NaNNaNNaNNaNNaN
305925550.01295612392013...0.0083330.1666070.0496300.00.04NaNNaNNaNNaNNaN
405925640.01295912422013...0.0120480.1666070.0938420.00.12NaNNaNNaNNaNNaN

5 rows × 24 columns

martix = lag_feature(martix, [1,2,3,6,12], 'item_cnt_month_avg')
martix = lag_feature(martix, [1,2,3,6,12], 'shop_cnt_month_avg')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...item_cnt_month_avg_lag_1item_cnt_month_avg_lag_2item_cnt_month_avg_lag_3item_cnt_month_avg_lag_6item_cnt_month_avg_lag_12shop_cnt_month_avg_lag_1shop_cnt_month_avg_lag_2shop_cnt_month_avg_lag_3shop_cnt_month_avg_lag_6shop_cnt_month_avg_lag_12
0059221541.01293710212013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
105925520.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
205925540.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
305925550.01295612392013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
405925640.01295912422013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

5 rows × 34 columns

martix.drop(columns=[ 'item_cnt_month_avg', 'shop_cnt_month_avg'], inplace=True)  # 只保留特征的历史信息
gc.collect()
40
martix = lag_feature(martix, [1,2,3,6,12], 'cat_cnt_month_avg')
martix = lag_feature(martix, [1,2,3,6,12], 'shop_cat_cnt_month_avg')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...cat_cnt_month_avg_lag_1cat_cnt_month_avg_lag_2cat_cnt_month_avg_lag_3cat_cnt_month_avg_lag_6cat_cnt_month_avg_lag_12shop_cat_cnt_month_avg_lag_1shop_cat_cnt_month_avg_lag_2shop_cat_cnt_month_avg_lag_3shop_cat_cnt_month_avg_lag_6shop_cat_cnt_month_avg_lag_12
0059221541.01293710212013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
105925520.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
205925540.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
305925550.01295612392013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
405925640.01295912422013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

5 rows × 42 columns

martix.drop(columns=['cat_cnt_month_avg', 'shop_cat_cnt_month_avg'], inplace=True)
martix = lag_feature(martix, [1,2,3,6,12], 'itemtype_cnt_month_avg')
martix = lag_feature(martix, [1,2,3,6,12], 'subtype_cnt_month_avg')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...itemtype_cnt_month_avg_lag_1itemtype_cnt_month_avg_lag_2itemtype_cnt_month_avg_lag_3itemtype_cnt_month_avg_lag_6itemtype_cnt_month_avg_lag_12subtype_cnt_month_avg_lag_1subtype_cnt_month_avg_lag_2subtype_cnt_month_avg_lag_3subtype_cnt_month_avg_lag_6subtype_cnt_month_avg_lag_12
0059221541.01293710212013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
105925520.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
205925540.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
305925550.01295612392013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
405925640.01295912422013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

5 rows × 50 columns

martix.drop(columns=['itemtype_cnt_month_avg', 'subtype_cnt_month_avg'], inplace=True)
martix = lag_feature(martix, [1,2,3,6,12], 'city_item_cnt_month_avg')
martix = lag_feature(martix, [1,2,3,6,12], 'shoptype_item_cnt_month_avg')
martix.head()
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...city_item_cnt_month_avg_lag_1city_item_cnt_month_avg_lag_2city_item_cnt_month_avg_lag_3city_item_cnt_month_avg_lag_6city_item_cnt_month_avg_lag_12shoptype_item_cnt_month_avg_lag_1shoptype_item_cnt_month_avg_lag_2shoptype_item_cnt_month_avg_lag_3shoptype_item_cnt_month_avg_lag_6shoptype_item_cnt_month_avg_lag_12
0059221541.01293710212013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
105925520.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
205925540.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
305925550.01295612392013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
405925640.01295912422013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

5 rows × 58 columns

martix.drop(columns=[ 'city_item_cnt_month_avg','shoptype_item_cnt_month_avg'], inplace=True)
martix
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...city_item_cnt_month_avg_lag_1city_item_cnt_month_avg_lag_2city_item_cnt_month_avg_lag_3city_item_cnt_month_avg_lag_6city_item_cnt_month_avg_lag_12shoptype_item_cnt_month_avg_lag_1shoptype_item_cnt_month_avg_lag_2shoptype_item_cnt_month_avg_lag_3shoptype_item_cnt_month_avg_lag_6shoptype_item_cnt_month_avg_lag_12
0059221541.01293710212013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
105925520.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
205925540.01295812412013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
305925550.01295612392013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
405925640.01295912422013...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
..................................................................
110549353445184540.01215512382015...0.50.00.00.0NaN0.1111110.000.0526320.666667NaN
110549363445161880.01216413472015...0.00.0NaNNaNNaN0.0000000.05NaNNaNNaN
110549373445157570.01215512382015...0.00.50.00.00.00.1666670.100.1052630.1428570.250000
110549383445196480.01214010242015...0.00.00.00.0NaN0.0555560.050.2631580.142857NaN
1105493934459690.01213710212015...0.50.00.00.00.00.1111110.150.0000000.0952380.041667

11054940 rows × 56 columns

martix[martix.columns[:20]].isna().any()
date_block_num              False
shop_id                     False
item_id                     False
item_cnt_month              False
shop_type_code              False
shop_city_code              False
item_category_id            False
item_type_code              False
sub_type_code               False
year                        False
month                       False
item_cnt_month_lag_1         True
item_cnt_month_lag_2         True
item_cnt_month_lag_3         True
item_cnt_month_lag_6         True
item_cnt_month_lag_12        True
item_cnt_month_avg_lag_1     True
item_cnt_month_avg_lag_2     True
item_cnt_month_avg_lag_3     True
item_cnt_month_avg_lag_6     True
dtype: bool


使用之前月份的销量,会导致有很多记录缺失信息。需要将这部分缺失信息的记录去掉。

前面延迟了12个月的销量信息,这里就直接把前12个月的记录删除。

train_set = martix[martix['date_block_num'] > 11].fillna(0)
train_set
date_block_numshop_iditem_iditem_cnt_monthshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyear...city_item_cnt_month_avg_lag_1city_item_cnt_month_avg_lag_2city_item_cnt_month_avg_lag_3city_item_cnt_month_avg_lag_6city_item_cnt_month_avg_lag_12shoptype_item_cnt_month_avg_lag_1shoptype_item_cnt_month_avg_lag_2shoptype_item_cnt_month_avg_lag_3shoptype_item_cnt_month_avg_lag_6shoptype_item_cnt_month_avg_lag_12
44877061254102974.01273710212014...3.00.00.00.00.01.0800000.080.0000000.0000000.000000
44877071254102963.01273810222014...0.00.00.00.00.00.4800000.000.0000000.0000000.000000
448770812541029814.01274010242014...21.0119.07.00.00.08.32000033.683.5200000.0000000.000000
44877091254103003.01273710212014...1.031.00.00.00.01.0400008.241.1200000.0000000.000000
44877101254102841.01275712402014...0.00.00.01.00.00.0800000.040.1600000.1153850.000000
..................................................................
110549353445184540.01215512382015...0.50.00.00.00.00.1111110.000.0526320.6666670.000000
110549363445161880.01216413472015...0.00.00.00.00.00.0000000.050.0000000.0000000.000000
110549373445157570.01215512382015...0.00.50.00.00.00.1666670.100.1052630.1428570.250000
110549383445196480.01214010242015...0.00.00.00.00.00.0555560.050.2631580.1428570.000000
1105493934459690.01213710212015...0.50.00.00.00.00.1111110.150.0000000.0952380.041667

6567234 rows × 56 columns



5.3.4 对类别特征进行独热编码、类别编码

lightgbm算法模块的train函数中,有个categorical_feature参数,模型在训练时默认会将Dataframe的category类型字段自动识别为类别特征,就不需要预先对类别特征进行one-hot编码。
很多特征的值的动态范围都比较小,但数据类型默认都是folat64和int64的,非常的占用内存,在训练的时候可能会抛出导致内存不足的异常。这里可以相应地修改成占用内存较低的int32和float32等类型,降低模型训练时的内存压力。

for col in train_set.columns:
    if col.find('code') >= 0:
        train_set[col] = train_set[col].astype(np.int8)
    elif train_set[col].dtype == 'float64':
        train_set[col] = train_set[col].astype(np.float32)
    elif train_set[col].dtype == 'int64':
        train_set[col] = train_set[col].astype(np.int16)
        
train_set['item_type_code'] = train_set['item_type_code'].astype('category')
train_set['sub_type_code'] = train_set['sub_type_code'].astype('category')
train_set.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 6567234 entries, 4487706 to 11054939
Data columns (total 56 columns):
date_block_num                        int16
shop_id                               int16
item_id                               int16
item_cnt_month                        float32
shop_type_code                        int8
shop_city_code                        int8
item_category_id                      int16
item_type_code                        category
sub_type_code                         category
year                                  int16
month                                 int16
item_cnt_month_lag_1                  float32
item_cnt_month_lag_2                  float32
item_cnt_month_lag_3                  float32
item_cnt_month_lag_6                  float32
item_cnt_month_lag_12                 float32
item_cnt_month_avg_lag_1              float32
item_cnt_month_avg_lag_2              float32
item_cnt_month_avg_lag_3              float32
item_cnt_month_avg_lag_6              float32
item_cnt_month_avg_lag_12             float32
shop_cnt_month_avg_lag_1              float32
shop_cnt_month_avg_lag_2              float32
shop_cnt_month_avg_lag_3              float32
shop_cnt_month_avg_lag_6              float32
shop_cnt_month_avg_lag_12             float32
cat_cnt_month_avg_lag_1               float32
cat_cnt_month_avg_lag_2               float32
cat_cnt_month_avg_lag_3               float32
cat_cnt_month_avg_lag_6               float32
cat_cnt_month_avg_lag_12              float32
shop_cat_cnt_month_avg_lag_1          float32
shop_cat_cnt_month_avg_lag_2          float32
shop_cat_cnt_month_avg_lag_3          float32
shop_cat_cnt_month_avg_lag_6          float32
shop_cat_cnt_month_avg_lag_12         float32
itemtype_cnt_month_avg_lag_1          float32
itemtype_cnt_month_avg_lag_2          float32
itemtype_cnt_month_avg_lag_3          float32
itemtype_cnt_month_avg_lag_6          float32
itemtype_cnt_month_avg_lag_12         float32
subtype_cnt_month_avg_lag_1           float32
subtype_cnt_month_avg_lag_2           float32
subtype_cnt_month_avg_lag_3           float32
subtype_cnt_month_avg_lag_6           float32
subtype_cnt_month_avg_lag_12          float32
city_item_cnt_month_avg_lag_1         float32
city_item_cnt_month_avg_lag_2         float32
city_item_cnt_month_avg_lag_3         float32
city_item_cnt_month_avg_lag_6         float32
city_item_cnt_month_avg_lag_12        float32
shoptype_item_cnt_month_avg_lag_1     float32
shoptype_item_cnt_month_avg_lag_2     float32
shoptype_item_cnt_month_avg_lag_3     float32
shoptype_item_cnt_month_avg_lag_6     float32
shoptype_item_cnt_month_avg_lag_12    float32
dtypes: category(2), float32(46), int16(6), int8(2)
memory usage: 1.3 GB

5.4 使用模型训练数据集

这里选择使用lightgbm模型进行训练。

import lightgbm as lgb


X_train = train_set[train_set['date_block_num'] < 33].drop(columns=['item_cnt_month'])  # 训练集的样本特征
Y_train = train_set[train_set['date_block_num'] < 33]['item_cnt_month']  # 训练集的样本标签

X_validate = train_set[train_set['date_block_num'] == 33].drop(columns=['item_cnt_month'])  # 校对集
Y_validate = train_set[train_set['date_block_num'] == 33]['item_cnt_month']

X_test = train_set[train_set['date_block_num'] == 34].drop(columns=['item_cnt_month'])  # 测试集
del train_set
gc.collect()
20
# 把数据加载为模型适合的数据格式
train_data = lgb.Dataset(data=X_train, label=Y_train)
validate_data = lgb.Dataset(data=X_validate, label=Y_validate)
# 设置模型训练参数
import time
ts = time.time()
params = {"objective" : "regression", "metric" : "rmse", 'n_estimators':10000, 'early_stopping_rounds':50,
              "num_leaves" : 200, "learning_rate" : 0.01, "bagging_fraction" : 0.9,
              "feature_fraction" : 0.3, "bagging_seed" : 0}
print('Start....', ts)
lgb_model = lgb.train(params, train_data, valid_sets=[train_data, validate_data], verbose_eval=1000) 
print('End...', time.time() - ts)
Start.... 1591718735.6430335


c:\users\honk\appdata\local\programs\python\python37\lib\site-packages\lightgbm\engine.py:148: UserWarning: Found `n_estimators` in params. Will use it instead of argument
  warnings.warn("Found `{}` in params. Will use it instead of argument".format(alias))
c:\users\honk\appdata\local\programs\python\python37\lib\site-packages\lightgbm\engine.py:153: UserWarning: Found `early_stopping_rounds` in params. Will use it instead of argument
  warnings.warn("Found `{}` in params. Will use it instead of argument".format(alias))


Training until validation scores don't improve for 50 rounds
Early stopping, best iteration is:
[152]	training's rmse: 1.95378	valid_1's rmse: 1.57771
End... 330.06891345977783
# 特征重要性画图
lgb.plot_importance(lgb_model, max_num_features=40, figsize=(12, 8))
plt.title("Featurertances")
plt.show()

在这里插入图片描述

lgb_model.save_model('model_bestscore02.txt')  # 保存模型
<lightgbm.basic.Booster at 0x1e86f1d0>
# 根据项目要求,把数据“裁剪”到[0,20]的区间。
Y_test = lgb_model.predict(X_test).clip(0, 20)
Y_test
array([0.57359759, 0.3126507 , 0.93170284, ..., 0.11140766, 0.10172111,
       0.1178424 ])
X_test['item_cnt_month'] = Y_test
X_test
date_block_numshop_iditem_idshop_type_codeshop_city_codeitem_category_iditem_type_codesub_type_codeyearmonth...city_item_cnt_month_avg_lag_2city_item_cnt_month_avg_lag_3city_item_cnt_month_avg_lag_6city_item_cnt_month_avg_lag_12shoptype_item_cnt_month_avg_lag_1shoptype_item_cnt_month_avg_lag_2shoptype_item_cnt_month_avg_lag_3shoptype_item_cnt_month_avg_lag_6shoptype_item_cnt_month_avg_lag_12item_cnt_month
108407403455037341952201510...1.03.01.01.00.4444442.004.2500002.5000001.2500000.573598
10840741345532034551238201510...0.00.00.00.00.0000000.000.0000000.0000000.0000000.312651
108407423455233341952201510...3.01.03.00.01.2222222.504.5000002.1250000.0000000.931703
108407433455232342356201510...0.01.00.00.00.5555561.252.0000000.0000000.0000000.403808
108407443455268342053201510...0.00.00.00.00.0000000.000.0000000.0000000.0000002.331300
..................................................................
11054935344518454121551238201510...0.00.00.00.00.1111110.000.0526320.6666670.0000000.172498
11054936344516188121641347201510...0.00.00.00.00.0000000.050.0000000.0000000.0000000.118829
11054937344515757121551238201510...0.50.00.00.00.1666670.100.1052630.1428570.2500000.111408
11054938344519648121401024201510...0.00.00.00.00.0555560.050.2631580.1428570.0000000.101721
110549393445969121371021201510...0.00.00.00.00.1111110.150.0000000.0952380.0416670.117842

214200 rows × 56 columns


将预测结果合并到测试集。

result = pd.merge(test[['ID', 'shop_id', 'item_id']],X_test[['shop_id','item_id','item_cnt_month']], on=['shop_id', 'item_id'], how='left')
result
IDshop_iditem_iditem_cnt_month
00550370.573598
11553200.312651
22552330.931703
33552320.403808
44552682.331300
...............
21419521419545184540.172498
21419621419645161880.118829
21419721419745157570.111408
21419821419845196480.101721
214199214199459690.117842

214200 rows × 4 columns

result.isna().any()
ID                False
shop_id           False
item_id           False
item_cnt_month    False
dtype: bool

前面分析关闭的商店中,有3个商在最近半年里只有最后一个月有销量,推断是新开的商店。
还有一些商品是最近半年都没有销量,推断是已经下架的商品,预测值填为0 。

result[result.shop_id.isin(shop_c_n.columns)]['shop_id'].unique()
array([36], dtype=int64)
result.loc[result.item_id.isin(item_zero), 'item_cnt_month'] = 0
result.loc[result.item_id.isin(item_zero), 'item_cnt_month']
298       0.0
493       0.0
817       0.0
953       0.0
1165      0.0
         ... 
214165    0.0
214167    0.0
214176    0.0
214179    0.0
214180    0.0
Name: item_cnt_month, Length: 7812, dtype: float64
result[['ID','item_cnt_month']].to_csv('fromfinal01.csv',sep=',',index=False)

5.5 预测效果评估

得分:0.93740

在这里插入图片描述
当前排名:2560/7373


6. 项目总结

项目经验:
本次项目花了几个星期的业余时间才完成的,花费了很多精力和时间,但总算是有所收获。
从数据分析方法到特征工程和预测模型的构建,都花了很多时间去研究和梳理。通过这次项目学到了很多,包括问题切入的有效面、分析算法的代码实现,分析流程的设计等,让我从整体上更好的掌握数据分析的思维。


项目不足:

  • 数据分析方法分两种:一种是统计分析方法,另一种是挖掘方法。在做预测型分析的时候,特征分析和模型构建往往都需要用到数据挖掘的方法和思维,在这个一方面的理论和数据支撑还不够简明有力,还需要加强学习。

  • 在特征处理中,还有一个商品价格的特征没有加入到预测模型里面。主要原因是对价格特征的处理还存在一些不足之处,价格特征比较特殊,和销量不一样,如果某商品当月没有销量的话,销量可以记为0,但是价格则不能记录为0 。我的做法是使用最近半年内上次有销量的价格,但是预测效果并不好,需要重新理解这内在的联系,重新构建价格特征。
### 关于Kaggle平台上的销售预测项目、教程及资源 #### Kaggle竞赛中的Rossmann Store Sales案例分析 在大数据机器学习比赛中,Rossmann Store Sales是一个广受关注的比赛。此项目旨在基于历史数据来预测德国连锁药店Rossmann的日销售额[^1]。 ```python import pandas as pd data = pd.read_csv('rossmann-store-sales/train.csv') print(data.head()) ``` 该数据集包含了诸如商店编号、商品种类、促销活动等特征变量以及目标变量——每日销售额。参赛者需构建模型以尽可能精确地估计未来一段时间内的销售收入情况。 #### Wal-Mart Stores Inc. 销量预估解决方案解析 另一个值得研究的是Walmart 2014年举办的销量预测挑战赛的第一名方案。该项目不仅提供了完整的代码实现,还分享了许多实用技巧和技术细节用于处理大规模零售业时间序列数据分析任务[^2]。 对于希望深入理解如何应用高级算法解决实际商业问题的研究人员来说,这些资料具有很高的参考价值。例如,在这个仓库里可以看到作者是如何利用Python编写脚本来清洗原始交易记录并提取有用的信息作为输入给后续建模过程使用的。 #### 可视化工具助力效果评估 为了直观展示预测性能的好坏程度,可以通过图表形式对比不同时间段内真实发生额与预期值之间的差异状况。比如采用折线图表示法分别画出整体均值走势曲线和平滑后的趋势变动轨迹,从而帮助我们更清晰地观察到两者间的吻合度高低变化规律[^3]。
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值