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

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()
date date_block_num shop_id item_id item_price item_cnt_day
0 02.01.2013 0 59 22154 999.00 1.0
1 03.01.2013 0 25 2552 899.00 1.0
2 05.01.2013 0 25 2552 899.00 -1.0
3 06.01.2013 0 25 2554 1709.05 1.0
4 15.01.2013 0 25 2555 1099.00 1.0
test.head()
ID shop_id item_id
0 0 5 5037
1 1 5 5320
2 2 5 5233
3 3 5 5232
4 4 5 5268
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())]
ID shop_id item_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_name shop_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_name shop_id shop_city shop_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 Интернет-магазин ЧС 12 Online Online
# 对商店信息进行编码,降低模型训练的内存消耗
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_name shop_id shop_city shop_type shop_city_code shop_type_code
0 !Якутск Орджоникидзе, 56 фран 0 Якутск Others 0 0
1 !Якутск ТЦ "Центральный" фран 1 Якутск ТЦ 0 1
2 Адыгея ТЦ "Мега" 2 Адыгея ТЦ 1 1
3 Балашиха ТРК "Октябрь-Киномир" 3 Балашиха ТРК 2 2
4 Волжский ТЦ "Волга Молл" 4 Волжский ТЦ 3 1
5 Вологда ТРЦ "Мармелад" 5 Вологда ТРЦ 4 3
6 Воронеж (Плехановская, 13) 6 Воронеж Others 5 0

3.1.3 商品数据集

items = pd.read_csv('items.csv')
items
item_name item_id item_category_id
0 ! ВО ВЛАСТИ НАВАЖДЕНИЯ (ПЛАСТ.) D 0 40
1 !ABBYY FineReader 12 Professional Edition Full... 1 76
2 ***В ЛУЧАХ СЛАВЫ (UNV) D 2 40
3 ***ГОЛУБАЯ ВОЛНА (Univ) D 3 40
4 ***КОРОБКА (СТЕКЛО) D 4 40
... ... ... ...
22165 Ядерный титбит 2 [PC, Цифровая версия] 22165 31
22166 Язык запросов 1С:Предприятия [Цифровая версия] 22166 54
22167 Язык запросов 1С:Предприятия 8 (+CD). Хрустале... 22167 49
22168 Яйцо для Little Inu 22168 62
22169 Яйцо дракона (Игра престолов) 22169 69

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_name item_id item_category_id
2558 DEEPPURPLEComeHellOrHighWaterDVD 2558 59
2970 Divinity:DragonCommander[PC,Цифроваяверсия] 2970 31
5063 NIRVANAUnpluggedInNewYorkLP 5063 58
14539 МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион) 14539 40
19475 СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel) 19475 43
19581 ТАРЗАН(BD) 19581 37

duplicated_item_name_rec = items[items['item_name'].isin(duplicated_item_name['item_name'])]  # 6个商品相同名字不同id的记录
duplicated_item_name_rec
item_name item_id item_category_id
2514 DEEPPURPLEComeHellOrHighWaterDVD 2514 59
2558 DEEPPURPLEComeHellOrHighWaterDVD 2558 59
2968 Divinity:DragonCommander[PC,Цифроваяверсия] 2968 31
2970 Divinity:DragonCommander[PC,Цифроваяверсия] 2970 31
5061 NIRVANAUnpluggedInNewYorkLP 5061 58
5063 NIRVANAUnpluggedInNewYorkLP 5063 58
14537 МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион) 14537 40
14539 МЕНЯЮЩИЕРЕАЛЬНОСТЬ(регион) 14539 40
19465 СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel) 19465 43
19475 СтругацкиеА.иБ.Улитканасклоне(mp3-CD)(Jewel) 19475 43
19579 ТАРЗАН(BD) 19579 37
19581 ТАРЗАН(BD) 19581 37


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

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)]
date date_block_num shop_id item_id item_price item_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_name item_category_id
0 PC - Гарнитуры/Наушники 0
1 Аксессуары - PS2 1
2 Аксессуары - PS3 2
3 Аксессуары - PS4 3
4 Аксессуары - PSP 4
... ... ...
79 Служебные 79
80 Служебные - Билеты 80
81 Чистые носители (шпиль) 81
82 Чистые носители (штучные) 82
83 Элементы питания 83

84 rows × 2 columns

cat[cat['item_category_name'].duplicated()]
item_category_name item_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_name item_category_id item_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_name item_category_id item_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_name item_category_id item_type item_type_code
0 PC - Гарнитуры/Наушники 0 PC 0
1 Аксессуары - PS2 1 Аксессуары 1
2 Аксессуары - PS3 2 Аксессуары 1
3 Аксессуары - PS4 3 Аксессуары 1
4 Аксессуары - PSP 4 Аксессуары 1

【接着是拆分小类】
cat['sub_type'] = cat['item_category_name'].map(lambda x: x.split('-',1)[-1]) 
cat
item_category_name item_category_id item_type item_type_code sub_type
0 PC - Гарнитуры/Наушники 0 PC 0 Гарнитуры/Наушники
1 Аксессуары - PS2 1 Аксессуары 1 PS2
2 Аксессуары - PS3 2 Аксессуары 1 PS3
3 Аксессуары - PS4 3 Аксессуары 1 PS4
4 Аксессуары - PSP 4 Аксессуары 1 PSP
... ... ... ... ... ...
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_name item_category_id item_type item_type_code sub_type sub_type_code
0 PC - Гарнитуры/Наушники 0 PC 0 Гарнитуры/Наушники 0
1 Аксессуары - PS2 1 Аксессуары 1 PS2 1
2 Аксессуары - PS3 2 Аксессуары 1 PS3 2
3 Аксессуары - PS4 3 Аксессуары 1 PS4 3
4 Аксессуары - PSP 4 Аксессуары 1 PSP 4

【合并商品和类目数据集】
items = items.merge(cat[['item_category_id', 'item_type_code', 'sub_type_code']], on='item_category_id', how='left')
items.head()
item_name item_id item_category_id item_type_code sub_type_code
0 !ВОВЛАСТИНАВАЖДЕНИЯ(ПЛАСТ.)D 0 40 10 24
1 !ABBYYFineReader12ProfessionalEditionFull[PC,Ц... 1 76 14 59
2 ***ВЛУЧАХСЛАВЫ(UNV)D 2 40 10 24
3 ***ГОЛУБАЯВОЛНА(Univ)D 3 40 10 24
4 ***КОРОБКА(СТЕКЛО)D 4 40 10 24
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
date date_block_num shop_id item_id item_price item_cnt_day
885138 17.09.2013 8 12 11365 59200.000000 1.0
1006638 24.10.2013 9 12 7238 42000.000000 1.0
1163158 13.12.2013 11 12 6066 307980.000000 1.0
1488135 20.03.2014 14 25 13199 50999.000000 1.0
1501160 15.03.2014 14 24 20949 5.000000 405.0
1573252 23.04.2014 15 27 8057 1200.000000 401.0
1573253 22.04.2014 15 27 8057 1200.000000 502.0
1708207 28.06.2014 17 25 20949 5.000000 501.0
2048518 02.10.2014 21 12 9242 1500.000000 512.0
2067667 04.10.2014 21 55 19437 899.000000 401.0
2067669 09.10.2014 21 55 19437 899.000000 508.0
2067677 09.10.2014 21 55 19445 1249.000000 412.0
  • 38
    点赞
  • 219
    收藏
    觉得还不错? 一键收藏
  • 41
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值