比赛链接:https://www.datafountain.cn/competitions/337
赛题信息
随着社会信用体系建设的深入推进, 社会信用标准建设飞速发展,相关的标准相继发布,包括信用服务标准、信用数据釆集和服务标准、信用修复标准、城市信用标准、行业信用标准等在内的多层次标准体系亟待出台,社会信用标准体系有望快速推进。社会各行业信用服务机构深度参与广告、政务、涉金融、共享单车、旅游、重大投资项目、教育、环保以及社会信用体系建设,社会信用体系建设是个系统工程,通讯运营商作为社会企业中不可缺少的部分同样需要打造企业信用评分体系,助推整个社会的信用体系升级。同时国家也鼓励推进第三方信用服务机构与政府数据交换,以增强政府公共信用信息中心的核心竞争力。
传统的信用评分主要以客户消费能力等少数的维度来衡量,难以全面、客观、及时的反映客户的信用。中国移动作为通信运营商拥有海量、广泛、高质量、高时效的数据,如何基于丰富的大数据对客户进行智能评分是中国移动和新大陆科技集团目前攻关的难题。运营商信用智能评分体系的建立不仅能完善社会信用体系,同时中国移动内部也提供了丰富的应用价值,包括全球通客户服务品质的提升、客户欠费额度的信用控制、根据信用等级享受各类业务优惠等,希望通过本次建模比赛,征集优秀的模型体系,准确评估用户信用分值。
数据清单
train_dataset.zip:训练数据,包含50000行
test_dataset.zip:测试集数据,包含50000行
数据说明
本次提供数据主要包含用户几个方面信息:身份特征、消费能力、人脉关系、位置轨迹、应用行为偏好。字段说明如下:
字段列表 字段说明
- 用户编码 数值 唯一性
- 用户实名制是否通过核实 1为是0为否
- 用户年龄 数值
- 是否大学生客户 1为是0为否
- 是否黑名单客户 1为是0为否
- 是否4G不健康客户 1为是0为否
- 用户网龄(月) 数值
- 用户最近一次缴费距今时长(月) 数值
- 缴费用户最近一次缴费金额(元) 数值
- 用户近6个月平均消费话费(元) 数值
- 用户账单当月总费用(元) 数值
- 用户当月账户余额(元) 数值
- 缴费用户当前是否欠费缴费 1为是0为否
- 用户话费敏感度 用户话费敏感度一级表示敏感等级最大。根据极值计算法、叶指标权重后得出的结果,根据规则,生成敏感度用户的敏感级别:先将敏感度用户按中间分值按降序进行排序,前5%的用户对应的敏感级别为一级:接下来的15%的用户对应的敏感级别为二级;接下来的15%的用户对应的敏感级别为三级;接下来的25%的用户对应的敏感级别为四级;最后40%的用户对应的敏感度级别为五级。
- 当月通话交往圈人数 数值
- 是否经常逛商场的人 1为是0为否
- 近三个月月均商场出现次数 数值
- 当月是否逛过福州仓山万达 1为是0为否
- 当月是否到过福州山姆会员店 1为是0为否
- 当月是否看电影 1为是0为否
- 当月是否景点游览 1为是0为否
- 当月是否体育场馆消费 1为是0为否
- 当月网购类应用使用次数 数值
- 当月物流快递类应用使用次数 数值
- 当月金融理财类应用使用总次数 数值
- 当月视频播放类应用使用次数 数值
- 当月飞机类应用使用次数 数值
- 当月火车类应用使用次数 数值
- 当月旅游资讯类应用使用次数 数值
评价方式
竞赛评价指标采用MAE系数。
平均绝对差值是用来衡量模型预测结果对标准结果的接近程度的一种衡量方法。计算方法如下:
𝑀𝐴𝐸=1𝑛∑𝑛𝑖=1|𝑝𝑟𝑒𝑑𝑖−𝑦𝑖|MAE=1n∑i=1n|predi−yi|
其中𝑝𝑟𝑒𝑑𝑖predi为预测样本,𝑦𝑖yi为真实样本。MAE的值越小,说明预测数据与真实数据越接近。
最终结果为:𝑆𝑐𝑜𝑟𝑒=11+𝑀𝐴𝐸Score=11+MAE
𝑀𝑆𝐸=1𝑛∑𝑛𝑖=1(pred𝑖−𝑦𝑖)2MSE=1n∑i=1n(predi−yi)2
MSE对预测偏差越大的样本惩罚越大,因为加上平方之后数据会越来越大。
最终的结果越接近1分数越高
全面探索¶
我们拿到数据的第一时间,应该进行分析观察,让自己对竞赛题型、数据大致了解,下面开始数据整体探索。
""" 导入数据 """
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
train_data=pd.read_csv("train_dataset.csv")
test_data=pd.read_csv("test_dataset.csv")
df_data=pd.concat([train_data,test_data],ignore_index=True)
#如果我们设置了ignore_index=True的话,数据合并后将会重新按照0,1,2,3.......的顺序重新构建索引。
df_data
用户编码 | 用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | a4651f98c82948b186bdcdc8108381b4 | 1 | 44 | 0 | 0 | 0 | 186 | 1 | 99.80 | 163.86 | ... | 1 | 1 | 713 | 0 | 2740 | 7145 | 0 | 0 | 30 | 664.0 |
1 | aeb10247db4e4d67b2550bbc42ff9827 | 1 | 18 | 0 | 0 | 1 | 5 | 1 | 29.94 | 153.28 | ... | 0 | 0 | 414 | 0 | 2731 | 44862 | 0 | 0 | 0 | 530.0 |
2 | 5af23a1e0e77410abb25e9a7eee510aa | 1 | 47 | 0 | 0 | 0 | 145 | 1 | 49.90 | 109.64 | ... | 0 | 0 | 3391 | 0 | 0 | 4804 | 0 | 0 | 1 | 643.0 |
3 | 43c64379d3c24a15b8478851b22049e4 | 1 | 55 | 0 | 0 | 0 | 234 | 1 | 99.80 | 92.97 | ... | 1 | 1 | 500 | 0 | 1931 | 3141 | 0 | 0 | 5 | 649.0 |
4 | f1687f3b8a6f4910bd0b13eb634056e2 | 1 | 40 | 0 | 0 | 0 | 76 | 1 | 49.90 | 95.47 | ... | 1 | 0 | 522 | 0 | 64 | 59 | 0 | 0 | 0 | 648.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
99995 | 573d8e9a1c2842d3a7fe0cac44145c78 | 1 | 54 | 0 | 0 | 0 | 23 | 1 | 299.40 | 46.00 | ... | 1 | 0 | 14 | 0 | 0 | 756 | 0 | 0 | 0 | NaN |
99996 | b186350facc940d3a288e48a9f2d367b | 1 | 24 | 0 | 0 | 0 | 52 | 1 | 50.00 | 123.54 | ... | 1 | 1 | 1039 | 0 | 8225 | 1233 | 0 | 3 | 141 | NaN |
99997 | bed687a2a5a44c2e9a764233b6866cd4 | 1 | 40 | 0 | 0 | 0 | 7 | 1 | 29.94 | 72.95 | ... | 1 | 0 | 452 | 0 | 331 | 1113 | 0 | 0 | 20 | NaN |
99998 | 1d0d9e079f164620bb84c9b1091de654 | 1 | 29 | 0 | 0 | 0 | 7 | 1 | 29.94 | 62.68 | ... | 0 | 0 | 478 | 0 | 177 | 3188 | 0 | 0 | 30 | NaN |
99999 | e9b5e148e5cc4993bd990cbf15a9c0e6 | 1 | 41 | 0 | 0 | 1 | 16 | 1 | 9.98 | 58.71 | ... | 0 | 0 | 5 | 0 | 0 | 820 | 0 | 0 | 0 | NaN |
100000 rows × 30 columns
在数据清单我们知道本次竞赛有一个训练集压缩包和一个预测集压缩包,在文件夹中解压后直接进行合并,以便后续数据内容变换的统一处理。pd.concat合并训练集和测试集
"""数据属性"""
df_data.info()#没有缺失值,因为信用分50000,训练数据有信用分。测试数据没有信用分
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 30 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 用户编码 100000 non-null object
1 用户实名制是否通过核实 100000 non-null int64
2 用户年龄 100000 non-null int64
3 是否大学生客户 100000 non-null int64
4 是否黑名单客户 100000 non-null int64
5 是否4G不健康客户 100000 non-null int64
6 用户网龄(月) 100000 non-null int64
7 用户最近一次缴费距今时长(月) 100000 non-null int64
8 缴费用户最近一次缴费金额(元) 100000 non-null float64
9 用户近6个月平均消费值(元) 100000 non-null float64
10 用户账单当月总费用(元) 100000 non-null float64
11 用户当月账户余额(元) 100000 non-null int64
12 缴费用户当前是否欠费缴费 100000 non-null int64
13 用户话费敏感度 100000 non-null int64
14 当月通话交往圈人数 100000 non-null int64
15 是否经常逛商场的人 100000 non-null int64
16 近三个月月均商场出现次数 100000 non-null int64
17 当月是否逛过福州仓山万达 100000 non-null int64
18 当月是否到过福州山姆会员店 100000 non-null int64
19 当月是否看电影 100000 non-null int64
20 当月是否景点游览 100000 non-null int64
21 当月是否体育场馆消费 100000 non-null int64
22 当月网购类应用使用次数 100000 non-null int64
23 当月物流快递类应用使用次数 100000 non-null int64
24 当月金融理财类应用使用总次数 100000 non-null int64
25 当月视频播放类应用使用次数 100000 non-null int64
26 当月飞机类应用使用次数 100000 non-null int64
27 当月火车类应用使用次数 100000 non-null int64
28 当月旅游资讯类应用使用次数 100000 non-null int64
29 信用分 50000 non-null float64
dtypes: float64(4), int64(25), object(1)
memory usage: 22.9+ MB
'''
print("共有数据集:", df_data.shape[0]) #shape是查看数据有多少行多少列
print("共有测试集:", test_data.shape[0])
print("共有训练集:", train_data.shape[0])
'''
共有数据集: 100000
共有测试集: 50000
共有训练集: 50000
'''
结论:数据集情况与数据清单相对应,说明我们数据没有下载错误,合并后100000行,可以看到合并数据集特征列中全为数值型特征并且不存在缺失值。在这里,比赛刚开始的时候,一个新手与数据敏感高手的区别就开始体现出来,新手通常会直接忽略该信息。但是我们试着推理一下,中国移动公司获取一个从不开定位的手机信息和一个从不移动的家用手机信息商场出现次数,是应该有区别的,那么从不开定位就是一种缺失值,但在赛题中并没有出现。经过咨询大赛主办方人员,收到反馈是直接将所有缺失值填补为0,导致数据集中不存在缺失值,与推理一致。
"""统计每个特征有多少个类别"""
for i,name in enumerate(df_data.columns):
name_sum = df_data[name].value_counts().shape[0]
print("{}、{} 特征类别个数是:{}".format(i + 1, name, name_sum)) #所有列的特征总结
'''
1、用户编码 特征类别个数是:100000
2、用户实名制是否通过核实 特征类别个数是:2
3、用户年龄 特征类别个数是:88
4、是否大学生客户 特征类别个数是:2
5、是否黑名单客户 特征类别个数是:2
6、是否4G不健康客户 特征类别个数是:2
7、用户网龄(月) 特征类别个数是:283
8、用户最近一次缴费距今时长(月) 特征类别个数是:2
9、缴费用户最近一次缴费金额(元) 特征类别个数是:532
10、用户近6个月平均消费值(元) 特征类别个数是:22520
11、用户账单当月总费用(元) 特征类别个数是:16597
12、用户当月账户余额(元) 特征类别个数是:316
13、缴费用户当前是否欠费缴费 特征类别个数是:2
14、用户话费敏感度 特征类别个数是:6
15、当月通话交往圈人数 特征类别个数是:554
16、是否经常逛商场的人 特征类别个数是:2
17、近三个月月均商场出现次数 特征类别个数是:93
18、当月是否逛过福州仓山万达 特征类别个数是:2
19、当月是否到过福州山姆会员店 特征类别个数是:2
20、当月是否看电影 特征类别个数是:2
21、当月是否景点游览 特征类别个数是:2
22、当月是否体育场馆消费 特征类别个数是:2
23、当月网购类应用使用次数 特征类别个数是:8382
24、当月物流快递类应用使用次数 特征类别个数是:239
25、当月金融理财类应用使用总次数 特征类别个数是:7232
26、当月视频播放类应用使用次数 特征类别个数是:16067
27、当月飞机类应用使用次数 特征类别个数是:209
28、当月火车类应用使用次数 特征类别个数是:180
29、当月旅游资讯类应用使用次数 特征类别个数是:934
30、信用分 特征类别个数是:278
'''
""" 数据统计(聚合后的) """
df_data.describe()
用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | 用户账单当月总费用(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.000000 | ... | 100000.00000 | 100000.000000 | 100000.000000 | 100000.000000 | 100000.00000 | 1.000000e+05 | 100000.000000 | 100000.000000 | 100000.000000 | 50000.000000 |
mean | 0.991240 | 37.907910 | 0.003620 | 0.048500 | 0.088690 | 96.271580 | 0.701420 | 53.721932 | 98.983241 | 99.709021 | ... | 0.47546 | 0.374730 | 1161.142610 | 1.025860 | 975.36609 | 3.386321e+03 | 0.649760 | 0.564590 | 19.394650 | 618.053060 |
std | 0.093184 | 11.625008 | 0.060058 | 0.214821 | 0.284297 | 59.112782 | 0.457637 | 62.214807 | 61.002422 | 65.314169 | ... | 0.49940 | 0.484056 | 4300.092242 | 37.482212 | 2965.36056 | 1.074417e+04 | 22.299903 | 7.973381 | 312.587384 | 42.443022 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.00000 | 0.000000e+00 | 0.000000 | 0.000000 | 0.000000 | 422.000000 |
25% | 1.000000 | 30.000000 | 0.000000 | 0.000000 | 0.000000 | 48.000000 | 0.000000 | 0.000000 | 54.320000 | 53.000000 | ... | 0.00000 | 0.000000 | 18.000000 | 0.000000 | 6.00000 | 1.000000e+01 | 0.000000 | 0.000000 | 0.000000 | 594.000000 |
50% | 1.000000 | 36.000000 | 0.000000 | 0.000000 | 0.000000 | 94.000000 | 1.000000 | 49.900000 | 89.670000 | 90.000000 | ... | 0.00000 | 0.000000 | 248.000000 | 0.000000 | 265.00000 | 3.340000e+02 | 0.000000 | 0.000000 | 0.000000 | 627.000000 |
75% | 1.000000 | 45.000000 | 0.000000 | 0.000000 | 0.000000 | 139.000000 | 1.000000 | 99.800000 | 131.560000 | 134.627500 | ... | 1.00000 | 1.000000 | 934.000000 | 0.000000 | 1145.00000 | 2.440000e+03 | 0.000000 | 0.000000 | 4.000000 | 649.000000 |
max | 1.000000 | 111.000000 | 1.000000 | 1.000000 | 1.000000 | 288.000000 | 1.000000 | 1000.000000 | 1792.740000 | 2117.010000 | ... | 1.00000 | 1.000000 | 417536.000000 | 8235.000000 | 496238.00000 | 1.382227e+06 | 5856.000000 | 775.000000 | 87681.000000 | 719.000000 |
8 rows × 29 columns
""" 观察训练/测试集数据同分布状况 """
df_data[df_data['信用分'].isnull()].describe() #测试数据的分布,选取信用分df_data['信用分'].isnull()是空的列,空的就是测试数据
用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | 用户账单当月总费用(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | ... | 50000.000000 | 50000.000000 | 50000.00000 | 50000.000000 | 50000.000000 | 50000.00000 | 50000.000000 | 50000.00000 | 50000.000000 | 0.0 |
mean | 0.992260 | 37.932380 | 0.003520 | 0.048200 | 0.088800 | 96.094480 | 0.702740 | 54.027936 | 99.234402 | 99.842912 | ... | 0.476500 | 0.375340 | 1173.46996 | 0.853880 | 979.229100 | 3406.12244 | 0.595100 | 0.57584 | 19.672180 | NaN |
std | 0.087637 | 11.636829 | 0.059226 | 0.214191 | 0.284458 | 59.048962 | 0.457057 | 62.614124 | 61.245686 | 65.301379 | ... | 0.499452 | 0.484215 | 4586.71334 | 28.848873 | 2924.008879 | 9919.40536 | 13.025441 | 8.20404 | 408.041808 | NaN |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.00000 | 0.000000 | 0.000000 | 0.00000 | 0.000000 | 0.00000 | 0.000000 | NaN |
25% | 1.000000 | 30.000000 | 0.000000 | 0.000000 | 0.000000 | 48.000000 | 0.000000 | 0.000000 | 54.450000 | 53.200000 | ... | 0.000000 | 0.000000 | 17.00000 | 0.000000 | 5.000000 | 10.00000 | 0.000000 | 0.00000 | 0.000000 | NaN |
50% | 1.000000 | 36.000000 | 0.000000 | 0.000000 | 0.000000 | 94.000000 | 1.000000 | 49.900000 | 90.000000 | 90.000000 | ... | 0.000000 | 0.000000 | 246.00000 | 0.000000 | 263.000000 | 333.00000 | 0.000000 | 0.00000 | 0.000000 | NaN |
75% | 1.000000 | 45.000000 | 0.000000 | 0.000000 | 0.000000 | 139.000000 | 1.000000 | 99.800000 | 132.000000 | 135.235000 | ... | 1.000000 | 1.000000 | 935.00000 | 0.000000 | 1144.000000 | 2455.00000 | 0.000000 | 0.00000 | 4.000000 | NaN |
max | 1.000000 | 108.000000 | 1.000000 | 1.000000 | 1.000000 | 288.000000 | 1.000000 | 1000.000000 | 1792.740000 | 2117.010000 | ... | 1.000000 | 1.000000 | 417536.00000 | 5462.000000 | 329767.000000 | 295210.00000 | 1645.000000 | 775.00000 | 87681.000000 | NaN |
8 rows × 29 columns
df_data[df_data['信用分'].notnull()].describe() #训练数据的分布,选取信用分df_data['信用分'].notnull()不是空的列,有值的是训练数据
用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | 用户账单当月总费用(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 50000.00000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | ... | 50000.00000 | 50000.00000 | 50000.000000 | 50000.000000 | 50000.00000 | 5.000000e+04 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 |
mean | 0.99022 | 37.883440 | 0.003720 | 0.048800 | 0.088580 | 96.448680 | 0.700100 | 53.415929 | 98.732081 | 99.575130 | ... | 0.47442 | 0.37412 | 1148.815260 | 1.197840 | 971.50308 | 3.366519e+03 | 0.704420 | 0.553340 | 19.117120 | 618.053060 |
std | 0.09841 | 11.613239 | 0.060879 | 0.215452 | 0.284139 | 59.176593 | 0.458218 | 61.812022 | 60.757758 | 65.327335 | ... | 0.49935 | 0.48390 | 3992.957952 | 44.469584 | 3006.16776 | 1.151006e+04 | 28.721302 | 7.735913 | 170.074772 | 42.443022 |
min | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.00000 | 0.00000 | 0.000000 | 0.000000 | 0.00000 | 0.000000e+00 | 0.000000 | 0.000000 | 0.000000 | 422.000000 |
25% | 1.00000 | 30.000000 | 0.000000 | 0.000000 | 0.000000 | 48.000000 | 0.000000 | 0.000000 | 54.180000 | 52.675000 | ... | 0.00000 | 0.00000 | 18.000000 | 0.000000 | 6.00000 | 1.000000e+01 | 0.000000 | 0.000000 | 0.000000 | 594.000000 |
50% | 1.00000 | 36.000000 | 0.000000 | 0.000000 | 0.000000 | 94.000000 | 1.000000 | 49.900000 | 89.320000 | 89.620000 | ... | 0.00000 | 0.00000 | 250.000000 | 0.000000 | 267.00000 | 3.350000e+02 | 0.000000 | 0.000000 | 0.000000 | 627.000000 |
75% | 1.00000 | 45.000000 | 0.000000 | 0.000000 | 0.000000 | 139.000000 | 1.000000 | 99.800000 | 131.160000 | 133.945000 | ... | 1.00000 | 1.00000 | 932.000000 | 0.000000 | 1147.25000 | 2.423250e+03 | 0.000000 | 0.000000 | 4.000000 | 649.000000 |
max | 1.00000 | 111.000000 | 1.000000 | 1.000000 | 1.000000 | 288.000000 | 1.000000 | 998.000000 | 840.570000 | 1164.290000 | ... | 1.00000 | 1.00000 | 234336.000000 | 8235.000000 | 496238.00000 | 1.382227e+06 | 5856.000000 | 474.000000 | 13965.000000 | 719.000000 |
8 rows × 29 columns
初级特征探索(数据预处理)
接下来开始分析特征与信用分的关联性、开展相关的初级特征探索。很多新手经常在特征探索容易出现无从下手的情况,在这里作者新手推荐两种方案,
- 1:按照特征顺序,
- 2:按照连续、离散、非结构化特征顺序来进行特征探索.
总之要先上手为强。高手在经历一定的比赛以后就会开始总结出根据业务场景相关的特征探索思路。
计算每个特征和标签的特征相关度
# np.corrcoef(df_data['当月是否看电影'].values,df_data.信用分.values)
df_train=df_data[df_data['信用分'].notnull()]
df_train.head()
用户编码 | 用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | a4651f98c82948b186bdcdc8108381b4 | 1 | 44 | 0 | 0 | 0 | 186 | 1 | 99.80 | 163.86 | ... | 1 | 1 | 713 | 0 | 2740 | 7145 | 0 | 0 | 30 | 664.0 |
1 | aeb10247db4e4d67b2550bbc42ff9827 | 1 | 18 | 0 | 0 | 1 | 5 | 1 | 29.94 | 153.28 | ... | 0 | 0 | 414 | 0 | 2731 | 44862 | 0 | 0 | 0 | 530.0 |
2 | 5af23a1e0e77410abb25e9a7eee510aa | 1 | 47 | 0 | 0 | 0 | 145 | 1 | 49.90 | 109.64 | ... | 0 | 0 | 3391 | 0 | 0 | 4804 | 0 | 0 | 1 | 643.0 |
3 | 43c64379d3c24a15b8478851b22049e4 | 1 | 55 | 0 | 0 | 0 | 234 | 1 | 99.80 | 92.97 | ... | 1 | 1 | 500 | 0 | 1931 | 3141 | 0 | 0 | 5 | 649.0 |
4 | f1687f3b8a6f4910bd0b13eb634056e2 | 1 | 40 | 0 | 0 | 0 | 76 | 1 | 49.90 | 95.47 | ... | 1 | 0 | 522 | 0 | 64 | 59 | 0 | 0 | 0 | 648.0 |
5 rows × 30 columns
# 皮尔逊计算可以使用np.corrcoef 和 pd.corr是一样的,
#当前是否看电影跟信用分之间的关系,显示大概为0.16,所以没有关系
np.corrcoef(df_train['当月是否看电影'].values,df_train['信用分'].values)[0,1] #看电影和信用分之间的关系
#0.16537652308422773
df_train.columns
'''
Index(['用户编码', '用户实名制是否通过核实', '用户年龄', '是否大学生客户', '是否黑名单客户', '是否4G不健康客户',
'用户网龄(月)', '用户最近一次缴费距今时长(月)', '缴费用户最近一次缴费金额(元)', '用户近6个月平均消费值(元)',
'用户账单当月总费用(元)', '用户当月账户余额(元)', '缴费用户当前是否欠费缴费', '用户话费敏感度', '当月通话交往圈人数',
'是否经常逛商场的人', '近三个月月均商场出现次数', '当月是否逛过福州仓山万达', '当月是否到过福州山姆会员店', '当月是否看电影',
'当月是否景点游览', '当月是否体育场馆消费', '当月网购类应用使用次数', '当月物流快递类应用使用次数',
'当月金融理财类应用使用总次数', '当月视频播放类应用使用次数', '当月飞机类应用使用次数', '当月火车类应用使用次数',
'当月旅游资讯类应用使用次数', '信用分'],
dtype='object')
'''
# 计算每一个特征和信用分(目标值)的相关系数 排序 画图,视图从特征相关性这个维度找到重要的特征
x_cols=[col for col in df_train.columns if col not in ['信用分'] if df_train[col].dtype!='object']
x_cols
'''
['用户实名制是否通过核实',
'用户年龄',
'是否大学生客户',
'是否黑名单客户',
'是否4G不健康客户',
'用户网龄(月)',
'用户最近一次缴费距今时长(月)',
'缴费用户最近一次缴费金额(元)',
'用户近6个月平均消费值(元)',
'用户账单当月总费用(元)',
'用户当月账户余额(元)',
'缴费用户当前是否欠费缴费',
'用户话费敏感度',
'当月通话交往圈人数',
'是否经常逛商场的人',
'近三个月月均商场出现次数',
'当月是否逛过福州仓山万达',
'当月是否到过福州山姆会员店',
'当月是否看电影',
'当月是否景点游览',
'当月是否体育场馆消费',
'当月网购类应用使用次数',
'当月物流快递类应用使用次数',
'当月金融理财类应用使用总次数',
'当月视频播放类应用使用次数',
'当月飞机类应用使用次数',
'当月火车类应用使用次数',
'当月旅游资讯类应用使用次数']
'''
labels=[]
values=[]
for col in x_cols:
labels.append(col) #把所有的列名字放在label里面
values.append(np.corrcoef(df_train[col].values,df_train['信用分'].values)[0,1]) #列和信用分之间的关系
corr_df=pd.DataFrame({'col_labels':labels,'corr_values':values}) #为取得的系数,添加标签列名作为名称
corr_df=corr_df.sort_values(by='corr_values') #排序,默认是升序
#print(corr_df)
fig,ax=plt.subplots(figsize=(12,40))
#此处是一个12*40的图,12为图形的宽,40为图形的高
#函数返回一个figure图像和子图ax的array列表
#plt.subplot()函数用于直接指定划分方式和位置进行绘图。
ind=np.arange(len(labels))
#len(labels) 28; #np.arange()函数返回一个序列
#序列中的元素都是有序的、拥有自己编号(从0开始),我们可以通过索引得到序列中对应的元素:
rects=ax.barh(ind,corr_df.corr_values.values,color='r')
#barh横向条形图
#corr_df.corr_values.values把系数值以数组形式列出
plt.grid() # 生成网格
ax.set_yticks(ind) #标记
#制定显示的y刻度的列表
ax.set_yticklabels(corr_df.col_labels.values,rotation='horizontal')
#设定y轴的标签文字 ax.set_yticks()
#旋转是水平的rotation='horizontal'
#label对整个轴的描述
ax.set_xlabel('Correlation coefficient') #设置横轴标题
ax.set_title('Correlation coefficient of the variables') #ax.set_title()是给ax这个子图设置标题
#输出 :Text(0.5, 1.0, 'Correlation coefficient of the variables')
# 连续值特征
name_list=['用户网龄(月)','用户近6个月平均消费值(元)','当月通话交往圈人数',
'用户账单当月总费用(元)','缴费用户最近一次缴费金额(元)',
'近三个月月均商场出现次数','当月金融理财类应用使用总次数','用户当月账户余额(元)']
f, ax = plt.subplots(4, 2, figsize=(20, 15))#4行2列,20×15的图
for i,name in enumerate(name_list):
sns.scatterplot(data=df_train, x=name, y='信用分', color='r', ax=ax[i // 2][i % 2])
#scatterplot 散点图
#// "表示整数除法,返回不大于结果的一个最大的整数
#i%2就是i除以2的余数
# ax=[1,1] 即位置是第2行、第二列。(python从0开始计数,所以“1”代表第2的)
plt.show()
name_list=['当月视频播放类应用使用次数','用户年龄','当月网购类应用使用次数','当月火车类应用使用次数',
'当月旅游资讯类应用使用次数','当月飞机类应用使用次数','当月物流快递类应用使用次数','当月物流快递类应用使用次数']
f, ax = plt.subplots(4, 2, figsize=(20, 15))
for i,name in enumerate(name_list):
sns.scatterplot(data=df_train, x=name, y='信用分', color='r', ax=ax[i // 2][i % 2]) #scatterplot 散点图
plt.show()
# 离散值特征
name_list = ['当月是否景点游览','当月是否体育场馆消费','用户最近一次缴费距今时长(月)',
'当月是否看电影','是否经常逛商场的人','是否黑名单客户','缴费用户当前是否欠费缴费',
'当月是否到过福州山姆会员店','当月是否逛过福州仓山万达',
'用户实名制是否通过核实','是否大学生客户','是否4G不健康客户']
f, ax = plt.subplots(4, 3, figsize=(20, 15))
for i,name in enumerate(name_list):
sns.boxplot(data=df_data, x=name, y='信用分',ax=ax[i // 3][i % 3])#箱线图
'''四行三列
ax=0,0 ax=0,1 ax=0,2
ax=1,0 ax=1,1 ax=1,2
a=2,0 ax=2,1 ax=2,2
ax=3,0 ax=3,1 ax=3,2
'''
# ax=[1,1] 即位置是第2行、第二列。
#print(i) #0 1 2 3 4 5 6 7 8 9 10 11
plt.show()
#蓝色 否;橙色 ,是
去过旅游景点的比没去过的景点游览整体高一点儿,去过体育场馆消费比没有去过的整体高一点儿,用户最近一次缴费距今时长(月)有的比没有的高一些,高度相差不大的,区分度就分不太清了。
# 离散值特征
f, ax = plt.subplots( figsize=(20, 6))
sns.boxplot(data=df_train, x='用户话费敏感度', y='信用分', color='r')
plt.show()
#0和5 是除了4个等级剩下的,0是缺失,1,2,3,4五个等级,
话费敏感度越低的,分布范围越广一些; 敏感度越高的,可能会差一些
对数据做基本处理,再次观察单变量相关关系¶
df_train.describe()
用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | 用户账单当月总费用(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 50000.00000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 | ... | 50000.00000 | 50000.00000 | 50000.000000 | 50000.000000 | 50000.00000 | 5.000000e+04 | 50000.000000 | 50000.000000 | 50000.000000 | 50000.000000 |
mean | 0.99022 | 37.883440 | 0.003720 | 0.048800 | 0.088580 | 96.448680 | 0.700100 | 53.415929 | 98.732081 | 99.575130 | ... | 0.47442 | 0.37412 | 1148.815260 | 1.197840 | 971.50308 | 3.366519e+03 | 0.704420 | 0.553340 | 19.117120 | 618.053060 |
std | 0.09841 | 11.613239 | 0.060879 | 0.215452 | 0.284139 | 59.176593 | 0.458218 | 61.812022 | 60.757758 | 65.327335 | ... | 0.49935 | 0.48390 | 3992.957952 | 44.469584 | 3006.16776 | 1.151006e+04 | 28.721302 | 7.735913 | 170.074772 | 42.443022 |
min | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.00000 | 0.00000 | 0.000000 | 0.000000 | 0.00000 | 0.000000e+00 | 0.000000 | 0.000000 | 0.000000 | 422.000000 |
25% | 1.00000 | 30.000000 | 0.000000 | 0.000000 | 0.000000 | 48.000000 | 0.000000 | 0.000000 | 54.180000 | 52.675000 | ... | 0.00000 | 0.00000 | 18.000000 | 0.000000 | 6.00000 | 1.000000e+01 | 0.000000 | 0.000000 | 0.000000 | 594.000000 |
50% | 1.00000 | 36.000000 | 0.000000 | 0.000000 | 0.000000 | 94.000000 | 1.000000 | 49.900000 | 89.320000 | 89.620000 | ... | 0.00000 | 0.00000 | 250.000000 | 0.000000 | 267.00000 | 3.350000e+02 | 0.000000 | 0.000000 | 0.000000 | 627.000000 |
75% | 1.00000 | 45.000000 | 0.000000 | 0.000000 | 0.000000 | 139.000000 | 1.000000 | 99.800000 | 131.160000 | 133.945000 | ... | 1.00000 | 1.00000 | 932.000000 | 0.000000 | 1147.25000 | 2.423250e+03 | 0.000000 | 0.000000 | 4.000000 | 649.000000 |
max | 1.00000 | 111.000000 | 1.000000 | 1.000000 | 1.000000 | 288.000000 | 1.000000 | 998.000000 | 840.570000 | 1164.290000 | ... | 1.00000 | 1.00000 | 234336.000000 | 8235.000000 | 496238.00000 | 1.382227e+06 | 5856.000000 | 474.000000 | 13965.000000 | 719.000000 |
8 rows × 29 columns
# 做极值处理 计算99.9%分位数 0.1%的分位数 把极小值 用0.1%的分位数替换 极大值用99.9%分位数替换 50
# 把特征分成了三类
#transform_value_feature 特征变换的特征
#user_fea+log_features log的处理
def base_process():
transform_value_feature=['用户年龄','用户网龄(月)','当月通话交往圈人数',
'近三个月月均商场出现次数','当月网购类应用使用次数','当月物流快递类应用使用次数',
'当月金融理财类应用使用总次数','当月视频播放类应用使用次数', '当月飞机类应用使用次数',
'当月火车类应用使用次数','当月旅游资讯类应用使用次数']
user_fea=['缴费用户最近一次缴费金额(元)','用户近6个月平均消费值(元)','用户账单当月总费用(元)','用户当月账户余额(元)']
log_features=['当月网购类应用使用次数','当月金融理财类应用使用总次数','当月视频播放类应用使用次数']
#处理离群点.这里我们将大于99.9%的数据直接赋值对应99.9%的值,将小于0.1%的数据直接赋值0.1%对应的值
for col in transform_value_feature+user_fea+log_features:
ulimit=np.percentile(df_train[col].values, 99.9) #计算一个多维数组的任意百分比分位数
llimit=np.percentile(df_train[col].values, 0.1)
df_train.loc[df_train[col]>ulimit,col]=ulimit # 大于99.9%的直接赋值ulimit
df_train.loc[df_train[col]<llimit,col]=llimit # 小于0.1%的直接赋值llimit
#对训练数据做正态分布处理
for col in user_fea+log_features:
df_train[col]=df_train[col].map(lambda x: np.log1p(x)) #取对数变化
#数据预处理时首先可以对偏度比较大的数据用og1p函数进行转化,
#使其更加服从高斯分布,此步处理可能会使我们后续的分类结果得到一个好的结果。
#log1p的逆运算expm1.
return df_train
train_df=base_process()
train_df.head()
用户编码 | 用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | ... | 当月是否景点游览 | 当月是否体育场馆消费 | 当月网购类应用使用次数 | 当月物流快递类应用使用次数 | 当月金融理财类应用使用总次数 | 当月视频播放类应用使用次数 | 当月飞机类应用使用次数 | 当月火车类应用使用次数 | 当月旅游资讯类应用使用次数 | 信用分 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | a4651f98c82948b186bdcdc8108381b4 | 1 | 44.0 | 0 | 0 | 0 | 186.0 | 1 | 4.613138 | 5.105097 | ... | 1 | 1 | 6.570883 | 0.0 | 7.916078 | 8.874308 | 0.0 | 0.0 | 30.0 | 664.0 |
1 | aeb10247db4e4d67b2550bbc42ff9827 | 1 | 18.0 | 0 | 0 | 1 | 5.0 | 1 | 3.432050 | 5.038769 | ... | 0 | 0 | 6.028279 | 0.0 | 7.912789 | 10.711369 | 0.0 | 0.0 | 0.0 | 530.0 |
2 | 5af23a1e0e77410abb25e9a7eee510aa | 1 | 47.0 | 0 | 0 | 0 | 145.0 | 1 | 3.929863 | 4.706282 | ... | 0 | 0 | 8.129175 | 0.0 | 0.000000 | 8.477412 | 0.0 | 0.0 | 1.0 | 643.0 |
3 | 43c64379d3c24a15b8478851b22049e4 | 1 | 55.0 | 0 | 0 | 0 | 234.0 | 1 | 4.613138 | 4.542976 | ... | 1 | 1 | 6.216606 | 0.0 | 7.566311 | 8.052615 | 0.0 | 0.0 | 5.0 | 649.0 |
4 | f1687f3b8a6f4910bd0b13eb634056e2 | 1 | 40.0 | 0 | 0 | 0 | 76.0 | 1 | 3.929863 | 4.569232 | ... | 1 | 0 | 6.259581 | 0.0 | 4.174387 | 4.094345 | 0.0 | 0.0 | 0.0 | 648.0 |
5 rows × 30 columns
经过数据处理之后再次进行相关度测量
train_df['交通次数']=train_df['当月飞机类应用使用次数']+train_df['当月火车类应用使用次数']
x_cols=[col for col in train_df.columns if col not in ['信用分'] if train_df[col].dtype!='object']
labels=[]
values=[]
for col in x_cols:
labels.append(col)
values.append(np.corrcoef(train_df[col].values,train_df.信用分.values)[0,1])
#0.05721307931292729 得出系数分数
corr_df=pd.DataFrame({'col_labels':labels,'corr_values':values})
corr_df=corr_df.sort_values(by='corr_values')
ind=np.arange(len(labels))
width=0.5
fig,ax=plt.subplots(figsize=(12,40))
#plt.subplots()是一个返回包含图形和轴对象的元组的函数.
rects=ax.barh(ind,corr_df.corr_values.values,color='y') #横向条形图
plt.grid() # 生成网格
ax.set_yticks(ind)
ax.set_yticklabels(corr_df.col_labels.values,rotation='horizontal')
ax.set_xlabel('Correlation coefficient')
ax.set_title('Correlation coefficient of the variables')
#输出结果Text(0.5, 1.0, 'Correlation coefficient of the variables')
结论:用户近6个月平均消费值和用户网龄顺序发生了变化。用户近6个月平均消费值提高的多一些,是否4G不健康客户,用户话费敏感度值比之前大了一点。 相关系数算夹角,log系数后,有的特征之间的顺序就会发生调整
所有连续变量之间两两相关关系
corrmat=train_df.corr(method='spearman') #默认是皮尔逊,现在指定斯皮尔曼
#Dataframe.corr(method='spearman'), 返回相关关系矩阵
#Pearson皮尔逊相关系数仅评估线性关系。
#Spearman斯皮尔曼相关系数仅评估单调关系。因此,即使相关系数为0,也可以存在有意义的关系。检查散点图以确定关系的形式。
#相比斯皮尔曼相关系数对于数据错误和极端值的反应不敏感。
f,ax=plt.subplots(figsize=(12,12))
sns.heatmap(corrmat,vmax=1,square=True)
#sns.heatmap热力图,展现变量之间两两之间关系的强弱
plt.title('Important Variables correlation map',fontsize=15)
#输出结果Text(0.5, 1.0, 'Important Variables correlation map')
特征工程
df_data['是否去过高档商场']=df_data['当月是否逛过福州仓山万达']+df_data['当月是否到过福州山姆会员店']
df_data['是否去过高档商场']
‘’‘
0 0
1 0
2 0
3 0
4 0
..
99995 0
99996 1
99997 0
99998 0
99999 0
Name: 是否去过高档商场, Length: 100000, dtype: int64
’‘’
df_data['是否_商场_电影']=df_data['是否去过高档商场']*df_data['当月是否看电影']
df_data['是否_商场_电影']
‘’‘
0 0
1 0
2 0
3 0
4 0
..
99995 0
99996 1
99997 0
99998 0
99999 0
Name: 是否_商场_电影, Length: 100000, dtype: int64
’‘’
df_data['是否_商场_体育馆_电影_旅游']=df_data['是否去过高档商场']*df_data['当月是否体育场馆消费']*df_data[
'当月是否看电影']*df_data['当月是否景点游览']
df_data['是否_商场_体育馆_电影_旅游']
‘’‘
0 0
1 0
2 0
3 0
4 0
..
99995 0
99996 1
99997 0
99998 0
99999 0
Name: 是否_商场_体育馆_电影_旅游, Length: 100000, dtype: int64
’‘’
def get_features():
# 缺失值填充 用众数去填
df_data.loc[df_data['用户年龄']==0,'用户年龄']=df_data['用户年龄'].mode() #众数比平均值好 mode()众数
#根据之前的特征重要性对其中几个强相关特征进行处理
df_data['缴费金额是否能覆盖当月账单']=df_data['缴费用户最近一次缴费金额(元)']-df_data['用户账单当月总费用(元)']
df_data['最近一次交费是否超过平均消费额']=df_data['缴费用户最近一次缴费金额(元)']-df_data['用户近6个月平均消费值(元)']
df_data['当月账单是否超过平均消费额']=df_data['用户账单当月总费用(元)']-df_data['用户近6个月平均消费值(元)']
# def map_age(x): #用户年龄进行分段处理
# if x<=18:
# return 1
# elif x<=30:
# return 2
# elif x<=35:
# return 3
# elif x<=45:
# return 4
# else:
# return 5
#这些特征相关性比较小
df_data['是否去过高档商场']=df_data['当月是否逛过福州仓山万达']+df_data['当月是否到过福州山姆会员店']
df_data['是否去过高档商场']=df_data['是否去过高档商场'].map(lambda x:1 if x>=1 else 0)
# 特征交叉 两个维度交叉,*实际上是取and,有一次不去,则为0,都去则为1. 1和1相乘是1
df_data['是否_商场_电影']=df_data['是否去过高档商场']*df_data['当月是否看电影'] #这里用的是乘法
df_data['是否_商场_旅游']=df_data['是否去过高档商场']*df_data['当月是否景点游览']
df_data['是否_商场_体育馆']=df_data['是否去过高档商场']*df_data['当月是否体育场馆消费']
df_data['是否_电影_体育馆']=df_data['当月是否看电影']*df_data['当月是否体育场馆消费']
df_data['是否_电影_旅游']=df_data['当月是否看电影']*df_data['当月是否景点游览']
df_data['是否_旅游_体育馆']=df_data['当月是否景点游览']*df_data['当月是否体育场馆消费']
# 特征交叉 三个维度交叉
df_data['是否_商场_旅游_体育馆']=df_data['是否去过高档商场']*df_data['当月是否景点游览']*df_data['当月是否体育场馆消费']
df_data['是否_商场_电影_体育馆']=df_data['是否去过高档商场']*df_data['当月是否看电影']*df_data['当月是否体育场馆消费']
df_data['是否_商场_电影_旅游']=df_data['是否去过高档商场']*df_data['当月是否看电影']*df_data['当月是否景点游览']
df_data['是否_体育馆_电影_旅游']=df_data['当月是否体育场馆消费']*df_data['当月是否看电影']*df_data['当月是否景点游览']
#特征交叉 四个维度交叉
df_data['是否_商场_体育馆_电影_旅游']=df_data['是否去过高档商场']*df_data['当月是否体育场馆消费']*df_data['当月是否看电影'
]*df_data['当月是否景点游览']
#将数据离散化,大多数情况下数据都是0或者1,所以采用离散化
discretize_features=['交通类应用使用次数','当月物流快递类应用使用次数','当月飞机类应用使用次数','当月火车类应用使用次数','当月旅游资讯类应用使用次数']
df_data['交通类应用使用次数']=df_data['当月飞机类应用使用次数']+df_data['当月火车类应用使用次数']
# 对 '交通类应用使用次数','当月物流快递类应用使用次数','当月飞机类应用使用次数','当月火车类应用使用次数','当月旅游资讯类应用使用次数'
# 上述特征做离散化操作 根据每一类的使用次数做离散
def map_discreteze(x):
if x==0:
return 0
elif x<=5:
return 1
elif x<=15:
return 2
elif x<=50:
return 3
elif x<=100:
return 4
else:
return 5
for col in discretize_features:
df_data[col]=df_data[col].map(lambda x: map_discreteze(x))
return df_data
all_data=get_features()
all_data.head()
用户编码 | 用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | ... | 是否_商场_体育馆 | 是否_电影_体育馆 | 是否_电影_旅游 | 是否_旅游_体育馆 | 是否_商场_旅游_体育馆 | 是否_商场_电影_体育馆 | 是否_商场_电影_旅游 | 是否_体育馆_电影_旅游 | 是否_商场_体育馆_电影_旅游 | 交通类应用使用次数 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | a4651f98c82948b186bdcdc8108381b4 | 1 | 44.0 | 0 | 0 | 0 | 186 | 1 | 99.80 | 163.86 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | aeb10247db4e4d67b2550bbc42ff9827 | 1 | 18.0 | 0 | 0 | 1 | 5 | 1 | 29.94 | 153.28 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 5af23a1e0e77410abb25e9a7eee510aa | 1 | 47.0 | 0 | 0 | 0 | 145 | 1 | 49.90 | 109.64 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 43c64379d3c24a15b8478851b22049e4 | 1 | 55.0 | 0 | 0 | 0 | 234 | 1 | 99.80 | 92.97 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | f1687f3b8a6f4910bd0b13eb634056e2 | 1 | 40.0 | 0 | 0 | 0 | 76 | 1 | 49.90 | 95.47 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 46 columns
def base_process():
transform_value_feature=['用户年龄','用户网龄(月)','当月通话交往圈人数','最近一次交费是否超过平均消费额',
'近三个月月均商场出现次数','当月网购类应用使用次数','当月物流快递类应用使用次数','当月账单是否超过平均消费额',
'当月金融理财类应用使用总次数','当月视频播放类应用使用次数', '当月飞机类应用使用次数','当月火车类应用使用次数',
'当月旅游资讯类应用使用次数']
user_bill_features=['缴费用户最近一次缴费金额(元)','用户近6个月平均消费值(元)','用户账单当月总费用(元)','用户当月账户余额(元)']
log_features=['当月网购类应用使用次数','当月金融理财类应用使用总次数','当月视频播放类应用使用次数']
#处理离群点
for col in transform_value_feature+user_bill_features+log_features:
ulimit=np.percentile(all_data[col].values, 99.9) #计算一个多维数组的任意百分比分位数
llimit=np.percentile(all_data[col].values, 0.1)
all_data.loc[all_data[col]>ulimit,col]=ulimit # 大于99.9%的直接赋值
all_data.loc[all_data[col]<llimit,col]=llimit
for col in user_bill_features+log_features:
all_data[col]=all_data[col].map(lambda x: np.log1p(x)) #取对数变化
train=all_data[:50000]
test=all_data[50000:]
return train,test
train,test=base_process()
#最终的训练集和测试集
train.head()
test.head()
用户编码 | 用户实名制是否通过核实 | 用户年龄 | 是否大学生客户 | 是否黑名单客户 | 是否4G不健康客户 | 用户网龄(月) | 用户最近一次缴费距今时长(月) | 缴费用户最近一次缴费金额(元) | 用户近6个月平均消费值(元) | ... | 是否_商场_体育馆 | 是否_电影_体育馆 | 是否_电影_旅游 | 是否_旅游_体育馆 | 是否_商场_旅游_体育馆 | 是否_商场_电影_体育馆 | 是否_商场_电影_旅游 | 是否_体育馆_电影_旅游 | 是否_商场_体育馆_电影_旅游 | 交通类应用使用次数 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
50000 | 7171737d49b143d1b38883a39e4a5730 | 1 | 30.0 | 0 | 0 | 0 | 22.0 | 1 | 4.613138 | 4.256038 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
50001 | 3af0a449d5424488912e8fb2bf4b9faa | 1 | 70.0 | 0 | 0 | 0 | 84.0 | 0 | 0.000000 | 2.631169 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
50002 | eb2cf02e0d5c4d1294dd73e776dbb441 | 1 | 35.0 | 0 | 0 | 0 | 237.0 | 0 | 0.000000 | 5.091969 | ... | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
50003 | 9c0f780ecb254670a11aa9e3f10777c5 | 1 | 44.0 | 0 | 0 | 0 | 161.0 | 0 | 0.000000 | 5.440685 | ... | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 |
50004 | d794eed46c1e44f785a575f18b3023a5 | 1 | 44.0 | 0 | 0 | 0 | 153.0 | 1 | 4.613138 | 4.667394 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 46 columns
feature_name=[col for col in train.columns if col not in ['信用分'] if col not in ['用户编码'] ]
label_name=['信用分']
train_feature=train[feature_name]
train_label=train[label_name]
test_feature=test[feature_name]
模型和参数
lightgbm做回归,因为目标是连续值
from sklearn.model_selection import KFold,StratifiedKFold
#StratifiedKFold分层采样
#k折交叉验证
from sklearn.metrics import accuracy_score #accuracy_score 分类准确率分数是指所有分类正确的百分比。
import lightgbm as lgb #高效率的并行训练
def labcv_predict(train_feature,train_label):
lgb_params1={
'boosting_type':'gbdt',
'num_leaves':31,
'reg_alpha':2.2,
'reg_lambda':1.5,
'max_depth':1,
'n_estimators':2000,
'subsample':0.8,
'colsample_bytree':0.7,
'subsample_freq':1,
'learning_rate':0.03,
'random_state':2019,
'n_jobs':-1}
clf2=lgb.LGBMRegressor(
##两种方法总结:lgb风格直接使用lgb就行,分类和回归使用相同的API。
#sklearn风格需要使用lgb.LGMRegressor或者lgb.Classifier进行回归和分类。同时参数的命名风格与sklearn通用,可以使用sklearn的网格搜索进行调参。
boosting_type='gbdt', #gbdt全称梯度下降树
num_leaves=31, # 叶子节点数
reg_alpha=1.2, #alpha:默认是0,别名是reg_alpha,L1 正则化项的权重系数,越大模型越保守;
reg_lambda=1.8, #lambda:默认是1,别名是reg_lambda,L2 正则化项的权重系数,越大模型越保守;
max_depth=-1, #max_depth:默认是6,树的最大深度,值越大,越容易过拟合;[0,∞]
n_estimators=2000, # n_estimators:弱学习器的数量
subsample=0.8,
#ubsample默认是1,这个参数控制对于每棵树,随机采样的比例.减小这个参数的值算法会更加保守,避免过拟合.但是这个值设置的过小,它可能会导致欠拟合。 (0,1]
colsample_bytree=0.7, #colsample_bytree:默认是1,用来控制每颗树随机采样的列数的占比; (0,1]
subsample_freq=1, #bagging_freq:(默认= 0,type = int)也称为subsample_freq,此参数表示装袋的频率。0表示禁止装袋过程,而k表示每k次处理装袋一次。
learning_rate=0.03, #learning_rate也称为收缩率,是梯度下降的步长。(默认为0.1,始终介于0.05和0.2之间)
random_state=2018, #随机数的种子
n_jobs=-1) #并行运行的多线程数,n_jobs设定工作的core数量,等于-1的时候,表示cpu里的所有core进行工作。
kf=KFold(n_splits=10,random_state=2019,shuffle=False)
#十折交叉验证,n_splits=k, k折交叉验证,表示划分几等份
#shuffle:在每次划分时,是否进行洗牌
modell=[]
model2=[]
best_score=[]
sub_list=[]
t_feature=train_feature.values
t_label=train['信用分'].values
for i,(train_index,val_index) in enumerate(kf.split(t_feature)):
X_train=t_feature[train_index,:]
y_train=t_label[train_index]
X_val=t_feature[val_index,:]
y_val=t_label[val_index]
#第一种参数预测
clf=lgb.LGBMRegressor(**lgb_params1)
clf.fit(X_train,y_train,eval_set=[(X_train,y_train),(X_val,y_val)],eval_metric='mae',early_stopping_rounds=100,verbose=200)
#将数据集转换一下格式,eval_set的作用是指明每加入一个新树,用什么数据来训练它
# eval_metric评价指标,指定这个衡量的标准,我们选用的是MAE(平均绝对误差)
# early_stopping_rounds : 指定当添加树loss变化不大这个状态持续的轮数,达到这个数就退出训练过程
#verbose 指多少轮迭代打印一次日志.日志显示数,1(默认);打印输出 大于1打印每棵树的进度和性能
#verbose表示详细信息,verbose=FALSE,意思就是设置运行的时候不显示详细信息。
#将为您提供最佳的迭代clf.best_iteration_
#将预测仅使用增强器,直到达到最佳迭代
pred_val1=clf.predict(X_val,num_iteration=clf.best_iteration_) ##预测的划分出来的测试集的标签
#vali_mae1=mean_absolute_error(y_val,np.round(pred_val1))
vali_mae1=accuracy_score(y_val,np.round(pred_val1)) #准确率
#np.round(a):一般该函数遵循四舍五入原则, 当整数部分以0结束时,round函数一律是向下取整,
#pred_test1=clf.predcit(test[feature_name],num_iteration=clf.best_iteration_) ##预测的未带标签的测试集的标签
modell.append(clf)
# 第二种参数预测
clf2.fit(X_train,y_train,eval_set=[(X_train,y_train),(X_val,y_val)],eval_metric='mse',early_stopping_rounds=100,verbose=200)
# 均方误差(MSE)
pred_val2=clf.predict(X_val,num_iteration=clf2.best_iteration_) #预测的划分出来的测试集的标签
#vali_mae2=mean_absolute_error(y_val,np.round(pred_val2))
vali_mae2=accuracy_score(y_val,np.round(pred_val2))
#pred_test2=clf.predcit(test_featur,num_iteration=clf2.best_iteration_) #预测的未带标签的测试集的标签
model2.append(clf2)
pred_val=np.round(pred_val1*0.5+pred_val2*0.5) #融合之后预测的划分出来的测试集的标签
vali_mae=accuracy_score(y_val,pred_val)
best_score.append(1/(1+vali_mae))
#pred_test=np.round(pred_test1*0.5+pred_test2*0.5) #融合之后预测的未带标签的测试集的标签
#显示特征重要程度
predictors=[i for i in train_feature.columns]
feat_imp=pd.Series(clf.feature_importances_,predictors).sort_values(ascending=False)
#feature_importances_做特征筛选,变量重要性指标feature_importances_
#sub_list.append(pred_test)
#pred_test=np.mean(np.array(sub_list),axis=0)
print(best_score,'\n',np.mean(best_score),np.std(best_score))
#np.mean()计算每一列的均值
#numpy 计算的是总体标准偏差
print('特征重要程度',feat_imp)
return pred_val,modell,model2
pred_result,modell,model2=labcv_predict(train_feature,train_label)
预测真实测试集
# 模型一预测的结果
pred_test1=pd.DataFrame()
for i,model in enumerate(modell):
pred_mae= model.predict(test[feature_name])
pred_test1['pred_mae'] = pred_mae
pred_test1['ranks'] = list(range(50000))
# 模型二预测的结果
pred_test2=pd.DataFrame()
for i,model in enumerate(model2):
pred_mse= model.predict(test[feature_name])
pred_test2['pred_mse'] = pred_mse
pred_test2['ranks'] = list(range(50000))
# 模型参数进行融合之后的结果
pred_test=pd.DataFrame()
pred_test['ranks']=list(range(50000))
pred_test['result']=1
pred_test.loc[pred_test.ranks<10000,'result'] = pred_test1.loc[pred_test1.ranks< 10000,'pred_mae'].values *0.4 + pred_test2.loc[pred_test2.ranks< 10000,'pred_mse'].values * 0.6
pred_test.loc[pred_test.ranks>40000,'result'] = pred_test1.loc[pred_test1.ranks> 40000,'pred_mae'].values *0.4 + pred_test2.loc[pred_test2.ranks> 40000,'pred_mse'].values * 0.6
pred_test
ranks | result | |
---|---|---|
0 | 0 | 598.533870 |
1 | 1 | 530.516413 |
2 | 2 | 666.629274 |
3 | 3 | 673.534122 |
4 | 4 | 659.024376 |
5 | 5 | 612.811358 |
6 | 6 | 637.156150 |
7 | 7 | 565.644711 |
8 | 8 | 669.879814 |
9 | 9 | 590.136902 |
10 | 10 | 649.729853 |
11 | 11 | 561.151267 |
12 | 12 | 644.911870 |
13 | 13 | 544.182093 |
14 | 14 | 657.243115 |
... | ... | ... |
49986 | 49986 | 647.389084 |
49987 | 49987 | 637.133138 |
49988 | 49988 | 643.674225 |
49989 | 49989 | 570.387354 |
49990 | 49990 | 534.523248 |
49991 | 49991 | 529.214002 |
49992 | 49992 | 617.057830 |
49993 | 49993 | 660.986107 |
49994 | 49994 | 622.924881 |
49995 | 49995 | 553.509451 |
49996 | 49996 | 631.636889 |
49997 | 49997 | 545.262918 |
49998 | 49998 | 534.875844 |
49999 | 49999 | 548.103747 |
50000 rows × 2 columns
watch_feat='用户话费敏感度'
df_data[watch_feat].value_counts()
for v in df_data[watch_feat].unique():
plt.subplots(figsize=(8,6))#subplots绘制子图,其中figsize用来设置图形的大小,a为图形的宽, b为图形的高,单位为英寸
sns.distplot(df_data.loc[df_data[watch_feat]==v,'信用分'].values,bins=50,kde=False)
#直方图
#kde 绘制kde曲线,一般会比较耗时,所以可以直接将kde设置为False
#密度图也被称作KDE(Kernel Density Estimate,核密度估计)图,即可生成一张密度图(标准混合正态分布KDE)
plt.xlabel('用户花费敏感度{}'.format(v),fontsize=12)
import seaborn as sns
f, ax = plt.subplots(figsize=(20, 6))
sns.distplot(df_train['缴费用户最近一次缴费金额(元)'].values, color='r', bins=50, kde=False)
#意义不是执行计算,而是把它们分成半开放的数据集合,只适用于数字数据
plt.show()
import seaborn as sns
name_list = ['当月旅游资讯类应用使用次数', '当月火车类应用使用次数', '当月物流快递类应用使用次数', '当月网购类应用使用次数',
'当月视频播放类应用使用次数', '当月金融理财类应用使用总次数', '当月飞机类应用使用次数', '用户年龄',
'用户当月账户余额(元)', '用户账单当月总费用(元)', '用户近6个月平均消费值(元)', '缴费用户最近一次缴费金额(元)']
f, ax = plt.subplots(3, 4, figsize=(20, 20))
for i,name in enumerate(name_list):
sns.scatterplot(data=df_data, x=name, y='信用分', color='b', ax=ax[i // 4][i % 4])
plt.show()
f, ax = plt.subplots(1, 3, figsize=(20, 6))
sns.kdeplot(data=df_data['当月飞机类应用使用次数'], color='r', shade=True, ax=ax[0])
#kdeplot(核密度估计图)
#shade:若为True,则在kde曲线下面的区域中进行阴影处理,color控制曲线及阴影的颜色
sns.kdeplot(data=df_data['当月火车类应用使用次数'], color='c', shade=True, ax=ax[1])
sns.kdeplot(data=df_data['当月旅游资讯类应用使用次数'], color='b', shade=True, ax=ax[2])
plt.show()
""" 离散特征分析 """
f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.boxplot(data=df_data, x='用户最近一次缴费距今时长(月)', y='信用分', ax=ax[0])#箱线图
sns.boxplot(data=df_data, x='缴费用户当前是否欠费缴费', y='信用分', ax=ax[1])
plt.show()
name_list = ['当月是否体育场馆消费', '当月是否到过福州山姆会员店', '当月是否景点游览', '当月是否看电影', '当月是否逛过福州仓山万达',
'是否4G不健康客户', '是否大学生客户', '是否经常逛商场的人', '是否黑名单客户', '用户实名制是否通过核实']
f, ax = plt.subplots(2, 5, figsize=(20, 12))
for i,name in enumerate(name_list):
sns.boxplot(data=df_data, x=name, y='信用分', ax=ax[i // 5][i % 5])
plt.show()
f, ax = plt.subplots(figsize=(10, 6))
sns.boxplot(data=df_data, x='用户话费敏感度', y='信用分', ax=ax)
plt.show()
数据预处理涉及的内容很多,也包括特征工程,是任务量最大的一部分。为了让大家更清晰的阅读,以下先列出处理部分大致要用到的一些方法。
- 数据清洗:缺失值,异常值,一致性;
- 特征编码:one-hot 和 label coding;
- 特征分箱:等频、等距,聚类等
- 衍生变量:可解释性强,适合模型输入;
- 特征选择:方差选择,卡方选择,正则化等;
最终确定的初级探索工程代码
df_data[df_data['当月通话交往圈人数'] > 1750].index
#Int64Index([], dtype='int64')
"""
为什么只取消一个特征的拖尾,其它特征拖尾为什么保留,即使线下提高分数也要
保留,这是因为在线下中比如逛商场拖尾的数据真实场景下可能为保安,在
训练集中可能只有一个保安,所以去掉以后线下验证会提高,但是在测试集
中也存在一个保安,如果失去拖尾最终会导致测试集保安信用分精度下降
"""
df_data.drop(df_data[df_data['当月通话交往圈人数'] > 1750].index, inplace=True)
df_data.reset_index(drop=True, inplace=True)
""" 0替换np.nan,通过线下验证发现数据实际情况缺失值数量大于0值数量,np.nan能更好的还原数据真实性 """
na_list = ['用户年龄', '缴费用户最近一次缴费金额(元)', '用户近6个月平均消费值(元)','用户账单当月总费用(元)']
for na_fea in na_list:
df_data[na_fea].replace(0, np.nan, inplace=True)
""" 话费敏感度0替换,通过线下验证发现替换为中位数能比np.nan更好的还原数据真实性 """
df_data['用户话费敏感度'].replace(0, df_data['用户话费敏感度'].mode()[0], inplace=True)
结论:通过多次观察离散型特征,让我们对于数据的理解加深,比如用户话费敏感度特征并不是用户在现实世界中直接产生,而是由中国移动特定的关联模型通过计算产生,能够在很大程度上反应用户对于信用的关联程度。在箱型图中,用户敏感度呈现出高斯分布结果,符合我们对于业务场景的猜想。
中级特征探索(数据预工程)
通过初级的特征探索能够让我们加深对数据的理解并且实现初步的数据预处理, 接下来我们开始分析特征与特征之间对于信用分的影响、开展相关的中级特征探索。中级特征探索一般都是基于业务场景,但是作为新手在竞赛中可以简单的凭借感觉来关联特征进行分析
f, ax = plt.subplots(figsize=(20, 6))
sns.boxenplot(data=df_data, x='当月是否逛过福州仓山万达', y='信用分', hue='当月是否到过福州山姆会员店', ax=ax)
plt.show()
""" 离散型探索 """
f, [ax0, ax1, ax2, ax3, ax4] = plt.subplots(1, 5, figsize=(20, 6))
sns.boxplot(data=df_data, x='当月是否逛过福州仓山万达', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.boxplot(data=df_data, x='当月是否到过福州山姆会员店', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.boxplot(data=df_data, x='当月是否看电影', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.boxplot(data=df_data, x='当月是否景点游览', y='信用分', hue='是否经常逛商场的人', ax=ax3)
sns.boxplot(data=df_data, x='当月是否体育场馆消费', y='信用分', hue='是否经常逛商场的人', ax=ax4)
plt.show()
""" 连续型探索 """
f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.scatterplot(data=df_data, x='用户账单当月总费用(元)', y='信用分', color='b', ax=ax[0])
sns.scatterplot(data=df_data, x='用户当月账户余额(元)', y='信用分', color='r', ax=ax[1])
plt.show()
f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.scatterplot(data=df_data, x='用户账单当月总费用(元)', y='信用分', color='b', ax=ax[0])
sns.scatterplot(data=df_data, x='用户近6个月平均消费值(元)', y='信用分', color='r', ax=ax[1])
plt.show()
f, [ax0, ax1, ax2, ax3] = plt.subplots(1, 4, figsize=(20, 6))
sns.scatterplot(data=df_data, x='当月网购类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.scatterplot(data=df_data, x='当月物流快递类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.scatterplot(data=df_data, x='当月金融理财类应用使用总次数', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.scatterplot(data=df_data, x='当月视频播放类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax3)
plt.show()
f, [ax0, ax1, ax2, ax3] = plt.subplots(1, 4, figsize=(20, 6))
sns.scatterplot(data=df_data, x='当月飞机类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.scatterplot(data=df_data, x='当月火车类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.scatterplot(data=df_data, x='当月旅游资讯类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.scatterplot(data=df_data, x='用户网龄(月)', y='信用分', hue='是否经常逛商场的人', ax=ax3)
plt.show()
最终确定的中级探索工程代码:
""" x / (y + 1) 避免无穷值Inf,采用高斯平滑 + 1 """
df_data['话费稳定'] = df_data['用户账单当月总费用(元)'] / (df_data['用户当月账户余额(元)'] + 1)
df_data['相比稳定'] = df_data['用户账单当月总费用(元)'] / (df_data['用户近6个月平均消费值(元)'] + 1)
df_data['缴费稳定'] = df_data['缴费用户最近一次缴费金额(元)'] / (df_data['用户近6个月平均消费值(元)'] + 1)
df_data['当月是否去过豪华商场'] = (df_data['当月是否逛过福州仓山万达'] + df_data['当月是否到过福州山姆会员店']).map(lambda x: 1 if x > 0 else 0)
df_data['应用总使用次数'] = df_data['当月网购类应用使用次数'] + df_data['当月物流快递类应用使用次数'] + df_data['当月金融理财类应用使用总次数'] + df_data['当月视频播放类应用使用次数'] + df_data['当月飞机类应用使用次数'] + df_data['当月火车类应用使用次数'] + df_data['当月旅游资讯类应用使用次数']
结论:通过大量的中级探索能够让我们加深对于数据之间的关联性更深刻,在进行中级探索的时候应该结合模型进行线下稳定的验证测试,在一些结构化竞赛中通过大量的中级探索就能够在竞赛中进入10%。
高级特征探索(数据真场景)
在数据竞赛中,想要获得高名次甚至拿下奖牌,除了需要扎实的特征工程基础,还需要对数据有着深刻的业务理解,能够将数据隐藏的信息进行挖掘提取,下面我将对该赛题进行高级特征探索。
1、对特征本身进行业务角度解读,提取出关键信息
通过对特征出处的挖掘,我们对缴费用户最近一次缴费金额(元)特征进行业务角度观察,发现该特征具有重要的隐藏含义,如有些用户没有缴费金额信息,也有些缴费金额存在个位数存在金额时缴费用户可能是通过互联网、自动缴费机等手段进行缴费,最终我们根据以上分析提取了用户缴费方式特征。
- count:100000.000000
- mean: 53.721932
- std: 62.214807
- min: 0.000000
- 25%: 0.000000
- 50%: 49.900000
- 75%: 99.800000
- max:1000.000000
- name:缴费用户最近一次缴费金额(元),dtype:float64
df_data['缴费方式'] = 0
df_data.loc[(df_data['缴费用户最近一次缴费金额(元)'] != 0) & (df_data['缴费用户最近一次缴费金额(元)'] % 10 == 0), '缴费方式'] = 1
df_data.loc[(df_data['缴费用户最近一次缴费金额(元)'] != 0) & (df_data['缴费用户最近一次缴费金额(元)'] % 10 > 0), '缴费方式'] = 2
#%是取余运算符;缴费用户最近一次缴费金额(元)可以被10整除,余数为0。赋值是2的,为小数
f, ax = plt.subplots(figsize=(20, 6))
sns.boxplot(data=df_data, x='缴费方式', y='信用分', ax=ax)
#boxplot箱线图
plt.show()
2、充分利用外部信息,让特征具有实际场景意义
通过对大量的中国移动星级信用分资料浏览,根据中国移动官网的套餐信息,我们对用户年龄特征进行了提取分类。
df_data['信用资格'] = df_data['用户网龄(月)'].apply(lambda x: 1 if x > 12 else 0)
f, ax = plt.subplots(figsize=(10, 6))
sns.boxenplot(data=df_data, x='信用资格', y='信用分', ax=ax)
#boxenplot增强箱图又称增强盒形图,可以为大数据集绘制增强的箱图。
plt.show()
3、充分利用官网信息,数据业务上的问题积极和主办方联系
在多次详细解读比赛官网题目,根据比赛官网提供的特征信息,我们对用户敏感度进行了用户敏感度占比提取。
用户话费敏感度一级表示敏感等级最大 根据极值计算法、叶指标权重后得出的结果,根据规则,生成敏感度用户的敏感级别:
先将敏感度用户按中间分值按降序进行排序:
- 前5%的用户对应的敏感级别为1级
- 接下来的15%的用户对应的敏感级别为二级;
- 接下来的15%的用户对应的敏感级别为三级;
- 接下来的25%的用户对应的敏感级别为四级;
- 最后的40%的用户对应的敏感度级别为五级;
df_data['敏度占比'] = df_data['用户话费敏感度'].map({1:1, 2:3, 3:3, 4:4, 5:8})
f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.boxenplot(data=df_data, x='敏度占比', y='信用分', ax=ax[0])
sns.boxenplot(data=df_data, x='用户话费敏感度', y='信用分', ax=ax[1])
plt.show()
结论:在高级探索阶段,工程师总是会比学生更为敏感,当然天赋也是会在此展现,在数据竞赛中最重要的是要有希望的努力!
算法模型
在结构化竞赛中,机器学习常用的模型有LGB、XGB、CAT等模型,算法速度快并且能够容纳缺失值,由于之前我们已经提取出缺失值,并且明确了缺失值的业务意义,所以我们采用LGB来作为训练模型。
模型数据
# 提取模型
lab = '信用分'
X = df_data.loc[df_data[lab].notnull(), (df_data.columns != lab) & (df_data.columns != '用户编码')]
y = df_data.loc[df_data[lab].notnull()][lab]
X_pred = df_data.loc[df_data[lab].isnull(), (df_data.columns != lab) & (df_data.columns != '用户编码')]
df_data.head()
信用分 | 当月旅游资讯类应用使用次数 | 当月是否体育场馆消费 | 当月是否到过福州山姆会员店 | 当月是否景点游览 | 当月是否看电影 | 当月是否逛过福州仓山万达 | 当月火车类应用使用次数 | 当月物流快递类应用使用次数 | 当月网购类应用使用次数 | 当月视频播放类应用使用次数 | 当月通话交往圈人数 | 当月金融理财类应用使用总次数 | 当月飞机类应用使用次数 | 是否4G不健康客户 | 是否大学生客户 | 是否经常逛商场的人 | 是否黑名单客户 | 用户实名制是否通过核实 | 用户年龄 | 用户当月账户余额(元) | 用户最近一次缴费距今时长(月) | 用户编码 | 用户网龄(月) | 用户话费敏感度 | 用户账单当月总费用(元) | 用户近6个月平均消费值(元) | 缴费用户当前是否欠费缴费 | 缴费用户最近一次缴费金额(元) | 近三个月月均商场出现次数 | 缴费金额是否能覆盖当月账单 | 最近一次交费是否超过平均消费额 | 当月账单是否超过平均消费额 | 是否去过高档商场 | 是否_商场_电影 | 是否_商场_旅游 | 是否_商场_体育馆 | 是否_电影_体育馆 | 是否_电影_旅游 | 是否_旅游_体育馆 | 是否_商场_旅游_体育馆 | 是否_商场_电影_体育馆 | 是否_商场_电影_旅游 | 是否_体育馆_电影_旅游 | 是否_商场_体育馆_电影_旅游 | 交通类应用使用次数 | 话费稳定 | 相比稳定 | 缴费稳定 | 当月是否去过豪华商场 | 应用总使用次数 | 缴费方式 | 信用资格 | 敏度占比 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 664.0 | 3.0 | 1 | 0 | 1 | 0 | 0 | 0.0 | 0.0 | 6.570883 | 8.874308 | 83.0 | 7.916078 | 0.0 | 0 | 0 | 1 | 0 | 1 | 44.0 | 5.198497 | 1 | a4651f98c82948b186bdcdc8108381b4 | 186.0 | 3 | 5.076423 | 5.105097 | 0 | 4.613138 | 75.0 | -59.40 | -64.06 | -4.66 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0.818976 | 0.831506 | 0.755621 | 0 | 26.361269 | 2 | 1 | 3 |
1 | 530.0 | 0.0 | 0 | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 6.028279 | 10.711369 | 21.0 | 7.912789 | 0.0 | 1 | 0 | 1 | 0 | 1 | 18.0 | 4.709530 | 1 | aeb10247db4e4d67b2550bbc42ff9827 | 5.0 | 3 | 4.984291 | 5.038769 | 0 | 3.432050 | 16.0 | -115.16 | -123.34 | -8.18 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0.872977 | 0.825382 | 0.568336 | 0 | 24.652436 | 2 | 0 | 3 |
2 | 643.0 | 1.0 | 0 | 0 | 0 | 0 | 0 | 0.0 | 0.0 | 8.129175 | 8.477412 | 59.0 | 0.000000 | 0.0 | 0 | 0 | 0 | 0 | 1 | 47.0 | 4.262680 | 1 | 5af23a1e0e77410abb25e9a7eee510aa | 145.0 | 1 | 4.797442 | 4.706282 | 0 | 3.929863 | 1.0 | -70.30 | -59.74 | 10.56 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0.911597 | 0.840730 | 0.688691 | 0 | 17.606587 | 2 | 1 | 1 |
3 | 649.0 | 1.0 | 1 | 0 | 1 | 0 | 0 | 0.0 | 0.0 | 6.216606 | 8.052615 | 78.0 | 7.566311 | 0.0 | 0 | 0 | 1 | 0 | 1 | 55.0 | 4.510860 | 1 | 43c64379d3c24a15b8478851b22049e4 | 234.0 | 3 | 5.126461 | 4.542976 | 0 | 4.613138 | 26.0 | -67.62 | 6.83 | 74.45 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0.930247 | 0.924857 | 0.832249 | 0 | 22.835532 | 2 | 1 | 3 |
4 | 648.0 | 0.0 | 0 | 0 | 1 | 0 | 0 | 0.0 | 0.0 | 6.259581 | 4.094345 | 70.0 | 4.174387 | 0.0 | 0 | 0 | 1 | 0 | 1 | 40.0 | 4.394449 | 1 | f1687f3b8a6f4910bd0b13eb634056e2 | 76.0 | 3 | 4.624973 | 4.569232 | 0 | 3.929863 | 44.0 | -51.10 | -45.57 | 5.53 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0.857358 | 0.830451 | 0.705638 | 0 | 14.528313 | 2 | 1 | 3 |
模型参数
""" 模型参数为作者祖传参数 """
lgb_param_l1 = {
'learning_rate': 0.01, #梯度下降的步长
'boosting_type': 'gbdt',#梯度提升决策树
'objective': 'regression_l1', #任务目标(L1 loss, alias=mean_absolute_error, mae)
'metric': 'mae',
'min_child_samples': 46,# 一个叶子上数据的最小数量
'min_child_weight': 0.01,
'feature_fraction': 0.6,#每次迭代中选择前60%的特征
'bagging_fraction': 0.8,#不进行重采样的情况下随机选择部分数据
'bagging_freq': 2, #每2次迭代执行bagging
'num_leaves': 31,#一棵树上的叶子数
'max_depth': 5,#树的最大深度
'lambda_l2': 1, # 表示的是L2正则化
'lambda_l1': 0,# 表示的是L1正则化
'n_jobs': -1, #并行运行的多线程数,等于-1的时候,表示cpu里的所有core进行工作。
'seed': 4590, #seed:随机数种子,相同的种子可以复现随机结果,用于调参!
}
模型框架
在实际激烈竞赛中线上提交次数总是有限,所以选手必须构建一个合理的线下验证框架。在本赛题中,为了保证线下验证的准确性,我选择五折交叉验证,能够很好的避免过拟合情况。
from sklearn.model_selection import KFold
import lightgbm as lgb
y_counts = 0
y_scores = np.zeros(5)
y_pred_l1 = np.zeros([5, X_pred.shape[0]])#[5,50000]五行5000列
y_pred_all_l1 = np.zeros(X_pred.shape[0])#[50000,]
for n in range(1): # 0
kfold = KFold(n_splits=5, shuffle=True, random_state=2019 + n)
kf = kfold.split(X, y)
for i, (train_iloc, test_iloc) in enumerate(kf):
#print(len(test_iloc))
print("{}、".format(i + 1), end='')
X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :],
y[train_iloc], y[test_iloc]
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid,
params=lgb_param_l1, num_boost_round=6000,
verbose_eval=-1, early_stopping_rounds=100)
#Python下只有train函数中的num_boost_round才能控制迭代次数
#verbose_eval 迭代多少次打印,-1代表一次性输出
#early_stopping_rounds:达到这个数就退出训练过程
y_scores[y_counts] = lgb_model.best_score['valid_0']['l1']
y_pred_l1[y_counts] = lgb_model.predict(X_pred, num_iteration=
lgb_model.best_iteration)#预测信用分
y_pred_all_l1 += y_pred_l1[y_counts]
y_counts += 1
#print(y_pred_l1)
y_pred_all_l1 /= y_counts
print(y_scores, y_scores.mean())
#[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
#表示过去一百轮当中 没什么改善
'''
1、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2555] valid_0's l1: 14.6827
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3616] valid_0's l1: 14.4936
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2196] valid_0's l1: 14.8204
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3355] valid_0's l1: 14.6649
5、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3195] valid_0's l1: 14.7147
[14.68266276 14.49360643 14.82035007 14.66492709 14.71471457] 14.675252185621542
y_scores
1、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[4434] valid_0's l1: 14.4686
[14.4686427 0. 0. 0. 0. ]
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3791] valid_0's l1: 14.5579
[14.4686427 14.55785788 0. 0. 0. ]
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3255] valid_0's l1: 14.7135
[14.4686427 14.55785788 14.71346416 0. 0. ]
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[5165] valid_0's l1: 14.8283
[14.4686427 14.55785788 14.71346416 14.82828992 0. ]
5、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2943] valid_0's l1: 14.7555
[14.4686427 14.55785788 14.71346416 14.82828992 14.75547104]
在该线下验证函数下我们不能够很好的观察与榜单上的分数对比状况,在这里高手通常在比赛中都会写一个验证函数插入参数。
'''
from sklearn.metrics import mean_absolute_error
#平均绝对误差:Mean Absolute Error(MAE)
def feval_lgb(y_pred, train_data): #自定义评价标准
y_true = train_data.get_label()
#y_pred = np.argmax(y_pred.reshape(7, -1), axis=0)
score = 1 / (1 + mean_absolute_error(y_true, y_pred))
return 'acc_score', score, True
第一组模型
lgb_param_l1 = {
'learning_rate': 0.01,
'boosting_type': 'gbdt',
'objective': 'regression_l1',
'metric': 'None',
'min_child_samples': 46,
'min_child_weight': 0.01,
'feature_fraction': 0.6,
'bagging_fraction': 0.8,
'bagging_freq': 2,
'num_leaves': 31,
'max_depth': 5,
'lambda_l2': 1,
'lambda_l1': 0,
'n_jobs': -1,
'seed': 4590,
}
n_fold = 5
y_counts = 0
y_scores = np.zeros(5)
y_pred_l1 = np.zeros([5, X_pred.shape[0]])
y_pred_all_l1 = np.zeros(X_pred.shape[0])
for n in range(1):
kfold = KFold(n_splits=n_fold, shuffle=True, random_state=2019 + n)
kf = kfold.split(X, y)
for i, (train_iloc, test_iloc) in enumerate(kf):
print("{}、".format(i + 1), end='')
X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :], y[train_iloc], y[test_iloc]
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid, feval=feval_lgb,
params=lgb_param_l1, num_boost_round=6000, verbose_eval=-1, early_stopping_rounds=100)
y_scores[y_counts] = lgb_model.best_score['valid_0']['acc_score']
y_pred_l1[y_counts] = lgb_model.predict(X_pred, num_iteration=lgb_model.best_iteration)
y_pred_all_l1 += y_pred_l1[y_counts]
y_counts += 1
y_pred_all_l1 /= y_counts
print(y_scores, y_scores.mean())
'''
1、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2555] valid_0's acc_score: 0.0637647
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3616] valid_0's acc_score: 0.0645428
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2196] valid_0's acc_score: 0.0632097
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3355] valid_0's acc_score: 0.0638369
5、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3195] valid_0's acc_score: 0.0636346
[0.06376468 0.06454275 0.06320973 0.06383688 0.06363463] 0.06379773255950914
'''
第二组模型
lgb_param_l2 = {
'learning_rate': 0.01,
'boosting_type': 'gbdt',
'objective': 'regression_l2',
'metric': 'None',
'feature_fraction': 0.6,
'bagging_fraction': 0.8,
'bagging_freq': 2,
'num_leaves': 40,
'max_depth': 7,
'lambda_l2': 1,
'lambda_l1': 0,
'n_jobs': -1,
}
n_fold = 5
y_counts = 0
y_scores = np.zeros(5)
y_pred_l2 = np.zeros([5, X_pred.shape[0]])
y_pred_all_l2 = np.zeros(X_pred.shape[0])
for n in range(1):
kfold = KFold(n_splits=n_fold, shuffle=True, random_state=2019 + n)
kf = kfold.split(X, y)
for i, (train_iloc, test_iloc) in enumerate(kf):
print("{}、".format(i + 1), end='')
X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :], y[train_iloc], y[test_iloc]
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid, feval=feval_lgb,
params=lgb_param_l1, num_boost_round=6000, verbose_eval=-1, early_stopping_rounds=100)
y_scores[y_counts] = lgb_model.best_score['valid_0']['acc_score']
y_pred_l2[y_counts] = lgb_model.predict(X_pred, num_iteration=lgb_model.best_iteration)
y_pred_all_l2 += y_pred_l2[y_counts]
y_counts += 1
y_pred_all_l2 /= y_counts
print(y_scores, y_scores.mean())
'''
1、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2555] valid_0's l1: 14.6827 valid_0's acc_score: 0.0637647
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3616] valid_0's l1: 14.4936 valid_0's acc_score: 0.0645428
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2196] valid_0's l1: 14.8204 valid_0's acc_score: 0.0632097
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3355] valid_0's l1: 14.6649 valid_0's acc_score: 0.0638369
5、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3195] valid_0's l1: 14.7147 valid_0's acc_score: 0.0636346
[0.06376468 0.06454275 0.06320973 0.06383688 0.06363463] 0.06379773255950914
'''
模型融合
在竞赛中采用了取整提交,能够获得线上前排的成绩。那么,如何能够达到TOP1~10呢?事实上,在任何竞赛中,模型融合都是冲顶必备,我们对模型采用了双损失分层加权的方案,经过几次线下验证,让队伍直接进入第一梯队。
使用不同的损失函数(MSE与MAE)得到多个模型。MSE损失函数能够加大对异常值的惩罚,在高分段(例如650分以上)和低分段(例如525以下)获得更好的表现。使用MAE误差的模型在中分段获得更好的表现,且更贴近指标。
lgb_param_l1 = {
'learning_rate': 0.01,
'boosting_type': 'gbdt',
'objective': 'regression_l1',
'metric': 'None',
'min_child_samples': 46,
'min_child_weight': 0.01,
'feature_fraction': 0.6,
'bagging_fraction': 0.8,
'bagging_freq': 2,
'num_leaves': 31,
'max_depth': 5,
'lambda_l2': 1,
'lambda_l1': 0,
'n_jobs': -1,
'seed': 4590,
}
lgb_param_l2 = {
'learning_rate': 0.01,
'boosting_type': 'gbdt',
'objective': 'regression_l2',
'metric': 'None',
'feature_fraction': 0.6,
'bagging_fraction': 0.8,
'bagging_freq': 2,
'num_leaves': 40,
'max_depth': 7,
'lambda_l2': 1,
'lambda_l1': 0,
'n_jobs': -1,
}
将以上双损失参数对模型框架进行替换,对得到的结果进行融合。
#提交数据
submit = pd.DataFrame()
#找到要提交数据的用户编码
submit['id'] =df_data[df_data['信用分'].isnull()]['用户编码']
#两个模型预测出来的结果
submit['score1'] = y_pred_all_l1
submit['score2'] = y_pred_all_l2
#针对score1去排序
submit = submit.sort_values('score1')
#添加序号
submit['rank'] = np.arange(submit.shape[0])
#前100个
min_rank = 100
#后100个
max_rank = 50000 - min_rank
# l1
l1_ext_rate = 1
l2_ext_rate = 1 - l1_ext_rate
#取出前后各100个
il_ext = (submit['rank'] <= min_rank) | (submit['rank'] >= max_rank)
#取出 剩下的49800
l1_not_ext_rate = 0.5
l2_not_ext_rate = 1 - l1_not_ext_rate
il_not_ext = (submit['rank'] > min_rank) & (submit['rank'] < max_rank)
submit['score'] = 0
submit.loc[il_ext, 'score'] = (submit[il_ext]['score1'] * l1_ext_rate + submit[il_ext]['score2'] * l2_ext_rate + 1 + 0.25)
submit.loc[il_not_ext, 'score'] = submit[il_not_ext]['score1'] * l1_not_ext_rate + submit[il_not_ext]['score2'] * l2_not_ext_rate + 0.25
""" 输出文件 """
submit[['id', 'score']].to_csv('submit.csv')
结论:采用分段式融合后,提升效果显著,超越了自身的stakcing方案,在之后又组到一群优秀队友,取得了A榜Top1,B榜首次提交Top1的成绩。
实际在竞赛中,你花下的时间应该
通常是:特征工程 > 模型融合 > 算法模型 > 参数调整
或者是:模型融合 > 特征工程 > 算法模型 > 参数调整
文章总结
本篇文章介绍了在中国移动消费者人群画像赛中的经验和心历路程,完成入门到冠军的基本复现。我在本文章的竞赛中进行实践和学习,很多知识只有实践过才能真正理解我在案例中学到了很多东西!在竞赛中,想要取得较好成绩,投入大量的时间是必不可少的,有很多时候你的时间投入下去没有回报,不要气馁,相信自己并付诸努力和实践,