文章目录
(1)数据结构的选择
在进行特征开发时经常会遇到数据的增删改查的问题,为了代码更高效的运行,需要选择合适的数据结构:
-
写数据时,用列表list
-
查找数据时,用字典dict (或集合set)
(2)如果枚举型参数较多,可以将参数写入列表或者字典中,避免平铺,使得代码更简练
需要进行大量的判断时或者重复性操作时,也可以将参数写入列表中,改为循环,避免平铺,使得代码简练
例如:
def degree_1_overdue_rate():
#统计用户的一度联系人中,逾期天数分别超过7,15,30和60日的人数以及占比
degree_1_sp_overdue_day_list = np.random.randint(-1,50,20)
overdue_labels = [7, 15,30, 60]
result = {}
for overdue_label in overdue_labels:
result[overdue_label] = {}
result[overdue_label]["num"]=0.0
result[overdue_label]["rate"]=0.0
total_count = sum(degree_1_sp_overdue_day_list>=0)
for overdue_label in overdue_labels:
result[overdue_label]["num"] = sum(degree_1_sp_overdue_day_list>=overdue_label)
result[overdue_label]["rate"] = result[overdue_label]["num"] / total_count
i = 1000
fmap = {}
for overdue_label in overdue_labels:
for j in ["num", "rate"]:
fmap[i] = result[overdue_label][j]
i += 1
for k in fmap:
print k, fmap[k]
(3)将代码模块化|工具化
将代码中频繁用到的步骤,抽出来写到方法中,这样编码即简练又能解耦合
例如:
初始化操作,初始化字典
除法操作
数据格式化
连接数据库
从字典中查数据
计算列表的统计值:统计列表的最大值、最小值、平均值等
给定订单的应还款日期和已还款日期,计算逾期天数
字符串转化为日期
字符串转化为时间戳
(4)挖掘特征时,要将特征有条理的分类,将每一类特征写到一个函数中,函数中可以包含子函数
def init(self):
self.feature_map = {
400000:self.category_feature, #商品类别名称相关
400001:self.shouquan_feature, #授权实名相关
400100:self.order_list_feature, #购物订单相关
400110:self.asserts_basic_feature, #用户资产相关
}
def order_list_feature(self):
def make_time_feature(xx,xx):
.....
def make_month_feature(xx,xx):
......
........
fmap = make_time_feature(fmap, num_all)
fmap = make_month_feature(fmap, month_money)
fmap = make_address_feature(fmap, phone_dict, address_dict,person_dict,real_name, order_phone, address_money_dict, cate_finsh_all,cate_address_num)
(5)集合的浅拷贝和深拷贝
参考:https://www.cnblogs.com/Pengdachui-1/p/10995635.html
集合的复制:浅拷贝,只拷贝第一层,第二层的数据区域是一样的
深拷贝:拷贝所有的数据,不管是第一层还是第N层,两个变量的数据域互不影响。
import copy
copy.deepcopy()
(6)用户的profile信息如何衍生特征:
- 性别
- 年龄分段
- 手机的运营商(前三位)
- 用户所属的省ID
- 用户所属的城市的级别(第N线城市)
- 用户所属的省市是否含“自治”
- 是否填写了某些可选项
- 交叉验证
- 紧急联系人跟本人的关系(是否有同姓的)
- 用户的渠道来源
- 用户的profile是否在黑名单中
- sp授权时间与申请订单的时间的差
- 行业编号
- 职业编号
- sos类型编号
(7)使用numpy 和pandas进行数据的统计和转化
pandas不仅替代了循环操作,使得代码更简洁,而且向量运算也加快了代码的执行效率,更重要的是pandas中大量的数据统计的方法使编程更简单
例如:
统计序列中每个值出现的次数
统计序列去重后的个数
统计序列的最小值、最大值、分位数等
序列的分桶pd.cut() | pd.qcut()
序列的one-hot编码
筛选某几列特征
arr = np.random.randint(0,10,size=(10,5))
df = DataFrame(arr)
df.columns = list("abcde")
#批量统计某几列的特征
sub_cols = ["a","c","d"]
df_2 = df[sub_cols].apply(lambda k:sum(k==1), axis=0)
#print df_2
#统计序列中某些值出现的次数以及占比
total_count = df["a"].size
print "total_count:", total_count
df_3 = df["a"].value_counts()
dic = df_3.to_dict()
for k in [1,3,5]:
count = dic.get(k, 0)
rate = count / total_count
print k, count, rate
(8)分层批量计算特征:
以device_call_feature.py为例:
层级结构如下:
sos1|sos2
—>phone|name
—>最近半年|最近3个月
-----> 时段:工作日|周末|白天|黑夜|凌晨
---->通话类型:打入|打出|未接通|挂断|全部
---->联系密度:次数|时长
—>统计:最高时长|最低时长|平均时长
---->统计:距离订单申请的最远天数|最近天数
分类型
- 通话次数相关:
(全部 |最近一个月)(打入|打出|未接通|挂断|全部)的通话(次数|占比)
(全部 |最近一个月)(固话| 手机号| 大于11位号码 | 小于11位号码)的(打入|打出|未接通|挂断|全部)通话(次数| 占比)
(全部 |最近一个月)(工作日|周末|白天|黑夜|凌晨)的(打入|打出|未接通|挂断|全部)通话(次数|占比)
(全部 |最近一个月) 通话时长为0 的(打入|打出|未接通|挂断|全部)通话(次数|占比)
(全部 |最近一个月)(有通讯录姓名| 无通讯录姓名)的(打入|打出|未接通|挂断|全部)通话(次数|占比)
(全部 |最近一个月)(打入|打出|未接通|挂断|全部)的 催收电话 的通话次数、占固话的比例
- 通话时长相关:
最近一个月(打入|打出|未接通|挂断|全部)的(最大、最小、平均)通话时长
#计算特征值
def feature_generator(self, all_call):
fmap = self.init()
if all_call == None or len(all_call) == 0:
return fmap
tmp_dic = { 0:[], 1:[], 2:[], 3:[]}
feature_list = ['week_days','weekends','day','night','midnight']
feature_list2 = ['max','min','average']
feature_list3 = [ "persons", "persons_times", "persons_duration", "persons_average" , "max_persons_duration", "max_persons_average"]
feature_list4 = [ "long", "v_long", "short", "self_phone", "comp_phone", "long_ratio", "v_long_ratio", "short_ratio", "self_phone_ratio", "comp_phone_ratio"]
feature_list5 = [ 'name', 'anonymous', 'name_ratio', 'anonymous_ratio' ]
type_list = [0,1,2,3]
#区分通话类型构造字典
for line in all_call:
if int(line['type']) in tmp_dic:
tmp_dic[ int(line['type']) ].append( line)
for line in type_list:
#统计每种通话类型的(全部|电话类型|长短号码|正常异常姓名)的通话次数|时长|占比
tmp_dura = self.duration( tmp_dic[ line ] )
tmp_week = self.week_weekends( tmp_dic[ line ] )
tmp_per = self.personal_info( tmp_dic[ line ] )
tmp_len = self.phone_len( tmp_dic[ line ] )
tmp_name = self.name_check( tmp_dic[ line ] )
for word in feature_list:
fmap[ str(line)+'_'+str(word) ] = ( str(tmp_week[word]) )
for word in feature_list2:
fmap[ str(line)+'_'+str(word) ] = ( str(tmp_dura[word]) )
for word in feature_list3:
fmap[ str(line)+'_'+str(word) ] = ( str(tmp_per[word]) )
for word in feature_list4:
fmap[ str(line)+'_'+str(word) ] = ( str(tmp_len[word]) )
for word in feature_list5:
fmap[ str(line)+'_'+str(word) ] = ( str(tmp_name[word]) )
return fmap
(9)交叉验证
用户是否跟填写的紧急联系人有短信联系,几个有联系
用户是否跟填写的紧急联系人通话记录,一般在哪个时段联系,通话的时长、次数等
用户填写的紧急联系人是否异常,是否是代办|中介,是否也在平台接过款,是否正在借款中
用户填写的紧急联系人的关系是否是亲属关系,如果是的话,是否同姓?
用户填写的居住城市是否在gps出现过的城市中
(10)数据格式化(数据清洗)
- 一般原始的数据是不能直接使用的,需要首先转化为我们需要的格式类型
- 原始数据中有一些脏数据比如空值、异常值等,需要剔除掉
- 原始数据中不是所有的字段都是必要的,需要提取我们需要的字段
注意:
- 在格式化数据时,在函数的开头必须先初始化,否则会报错
- 从字典中取数时,一定要用get()设置默认值,否则容易抛异常
- 在函数的开头最好表明:原始数据输入格式,格式化后的输出格式, 以及过滤的规则
数据格式化后,需要根据时间对格式化数据排序,然后提取序列数据
然后对序列数据进行统计,如计算最大值、最小值、均值、方差、中位数、个数等
(11)在计算特征时,最好根据特征的类型,分开计算
例如:
区分字符串型特征| 枚举型特征| 数值型特征,便于后续格式化特征
字符串特征:手机的型号、手机的品牌、用户的居住城市
枚举型特征:是否root、使用该设备的用户是否唯一、当前使用的网络是否是wifi
连续型特征:用户更换的 设备数、该设备登录过几个用户
def init(self):
self.feature_map = {
100: self.device_string_feature, #字符串性特征
101: self.device_enumerated_feature, #枚举型特征
102: self.device_continuous_feature, #连续型特征
}
(12)时间型数据处理–时间窗口化
在特征工程中,近期的数据一般包含的信息量更准确,往往需要按时间窗口筛选数据,例如:筛选最近一个月的通话数据、最近3个月的订单数据、最近半年的多平台数等
dateutil模块的relativedelta()函数有年月周日时分秒的参数,来设置时间窗口,在实际中使用较广。而datetime模块的timedelta()仅有周、日的参数,有局限。
import datetime
from dateutil.relativedelta import relativedelta
now_date = datetime.datetime.now()
print "now_date:", now_date
two_year_bf = now_date - relativedelta(months=24)
print two_year_bf
print now_date + datetime.timedelta(days=2)
print now_date + datetime.timedelta(weeks=1)
print "**************************"
print now_date + relativedelta(months=1)
print now_date + relativedelta(years=1)
print now_date + relativedelta(days=1)
print now_date + relativedelta(weeks=1)
print now_date + relativedelta(minutes=1)
print now_date + relativedelta(hours=1)
print now_date + relativedelta(seconds=1)
--------------------result-------------------
now_date: 2020-01-07 19:36:32.935444
2020-01-08 19:36:32.935444
2020-01-14 19:36:32.935444
******************************
2021-01-07 19:36:32.935444
2020-02-07 19:36:32.935444
2020-01-08 19:36:32.935444
2020-01-14 19:36:32.935444
2020-01-07 20:36:32.935444
2020-01-07 19:37:32.935444
2020-01-07 19:36:33.935444
(13)时间型数据的处理1-转化为离散型
在挖掘特征时,需要查看用户的行为发生时间是否异常 或者 用户在网页或者app上的浏览偏好,如:订单的创建日期等 是工作日、周末、白天、前半夜还是后半夜,那么今天就来看看如何计算这些特征
如何计算某个日期是周几呢?有以下两种方法:
(1)date_.strptime("%w")
(2)date_.isoweekday()
获取某个日期的小时,也有两种方法:
(1)date_.strptime("%H")
(2)date_.hour
from dateutil.relativedelta import relativedelta
import datetime
#判断是否是工作日、周末、白天、前半夜、后半夜等
def week_weekends():
call_list = []
for i in range(10):
call_list.append(datetime.datetime.now()+relativedelta(hours=i))
tmp_dic = {
'week_days' : 0,
'weekends' : 0,
'day' : 0,
'night' : 0,
'midnight': 0
}
for tmp_time in call_list:
print tmp_time
#if tmp_time.strptime("%w") in [1,2,3,4,5]:
if tmp_time.isoweekday() in [1,2,3,4,5]:
tmp_dic['week_days'] += 1
#elif tmp_time.strptime("%w") in [1,2,3,4,5]:
elif tmp_time.isoweekday() in [6,7]:
tmp_dic['weekends'] += 1
if tmp_time.hour in range(6,18):
tmp_dic['day'] += 1
elif tmp_time.hour in range(18,24):
tmp_dic['night'] += 1
elif tmp_time.hour in range(0,6):
tmp_dic['midnight'] += 1
for k,v in tmp_dic.items():
print k,v
week_weekends()
(14)时间型数据的处理2-转化为连续型
什么是序列性数据?指有时间先后顺序的行为数据
例如:
- 用户在某电商平台的浏览行为列表,每个浏览行为包括:时间戳、页面ID、埋点ID(页面上某个链接、按钮等的ID)
- 用户在互联网金融借贷平台上的浏览行为
如何挖掘特征呢?
首先获取用户最近一段时间内的用户序列数据,并按照时间先后排序
前后两个浏览行为的时间差,作为用户在某个埋点|页面的停留时长;注意需要设置可以接受的最大时间间隔;若行为之间间隔大于改值时,认为已经离开app,且该时间间隔不算在内
统计用户在每个埋点|页面的点击次数和停留(浏览)总时长作为特征
统计用户首次编辑身份证等号码类的时间间隔
(15)特征的编码规范
采用10位编码
1-3位:标识数据源
-----> 4-6位: 标识特征组
-----> 7-10位:标识具体特征项
对于特征组:
- 000:字符串型特征:取值没有可比性,类型:string
- 001-099:整数枚举型特征:原始取值大小是可比较的,如:"xx等级|级别’:1,2,3 分别对应低级、中级、高级,类型:int
- 100-999:连续型特征,类型:int or float
对于特征:
对于(-1, 1)之间的浮点数,保留小数点后8位
对于<-1 || >1
的浮点数,保留小数点后4位
对于整数,不保留小数点
err:特征生成代码的错误
nan:缺失
inv:无效