def data_missing_indicator(data_train,var_type_dict,data_test=None): ''' 进行特缺失值标记变量衍生 data_train: 需要进行转换的训练集 var_type_dict: 变量信息记录dict data_test: 需要进行转换的测试集 可以不给 不给就不会进行相应的转换 return: data_train_completed 衍生完成的训练集 var_type_dict 更新完的变量信息记录dict data_test_completed 衍生完成的测试集 ''' numeric_feature = var_type_dict.get('numeric_var',[]) category_feature = var_type_dict.get('category_var',[]) print('开始进行特缺失值标记变量衍生'.center(50, '=')) ##从dict里面把特征list拿出来 is_miss_feature = ['is_'+i+'_missing' for i in numeric_feature+category_feature] print('原始数据维度:',data_train.shape) print('新增数据维度:',len(is_miss_feature)) check_unique(numeric_feature+is_miss_feature) ##数值列和类别列用指定的方法填充 miss_indicator = MissingIndicator(features='all') data_train_completed = miss_indicator.fit_transform(data_train[numeric_feature+category_feature]) data_train_completed = pd.concat([data_train,pd.DataFrame(data_train_completed,columns=is_miss_feature)],axis=1) print('变量衍生完成:',data_train_completed.shape) ##更新var_type_dict文件 全部加入到numeric_var当中 var_type_dict['numeric_var'] = numeric_feature+is_miss_feature ##如果测试数据不为空 那么对测试数据进行transform 并返回 if data_test is not None: data_test_completed = miss_indicator.transform(data_test[numeric_feature+category_feature]) data_test_completed = pd.concat([data_test,pd.DataFrame(data_test_completed,columns=is_miss_feature)],axis=1) return data_train_completed,var_type_dict,data_test_completed return data_train_completed,var_type_dict
def interpolate_binning(data, var, special_attribute=[]): ''' 用于插值分箱(每两个值之间作为一个分箱节点) 返回分箱节点 不允许存在缺失值 data: 数据源 DataFrame var: 待分箱的变量 str special_attribute: 在切分数据集的时候,某些特殊值需要排除在外 list return: cp: 分箱切分的节点 ''' print('正在进行变量{0}的插值分箱'.format(var)) ##从DataFrame里面取出对应列的Series 并做好排序工作 binning_series = data[var].loc[~data[var].isin(special_attribute )].sort_values() ##判断是否存在缺失值 if np.sum(binning_series.isna()) > 0: raise ValueError("detect nan values in {0}".format(var)) ##判断不同值的个数是否满足条件 value_list = list(binning_series.value_counts().sort_index().index) cp = [(value_list[i] + value_list[i + 1]) / 2 for i in np.arange(len(value_list) - 1)] ##判断分箱点是否存在重复值 if not check_unique(cp): print( "quantile cut off points for {0} with {1} bins is not unique, need extra operation" .format(var, max_interval)) cp = sorted(list(set(cp))) return cp
def mix_binning(data, var, max_interval=10, special_attribute=[]): ''' 用于混合分箱返回分箱节点 不允许存在缺失值 不同值的个数一定要超过max_interval的值 混合分箱的存在是为了防止异常值的存在对等距分箱的影响 在头尾进行等频率的分箱 然后剩下的部分用等距分箱 data: 数据源 DataFrame var: 待分箱的变量 str max_interval: 分箱的组数 int special_attribute: 在切分数据集的时候,某些特殊值需要排除在外 list return: cp: 分箱切分的节点 ''' print('正在进行变量{0}的混合分箱'.format(var)) ##从DataFrame里面取出对应列的Series 并做好排序工作 binning_series = data[var].loc[~data[var].isin(special_attribute )].sort_values() ##判断是否存在缺失值 if np.sum(binning_series.isna()) > 0: raise ValueError("detect nan values in {0}".format(var)) ##判断不同值的个数是否满足条件 different_value_nums = len(binning_series.value_counts()) if different_value_nums < max_interval: raise ValueError( "value_counts for {0} is {1}, less than max_interval {2}".format( var, different_value_nums, max_interval)) ##混合分箱 quantile_cp = [ binning_series.quantile(i) for i in np.linspace(0, 1, max_interval + 1)[1:-1] ] distance_cp = list( np.linspace(quantile_cp[0], quantile_cp[-1], max_interval - 1, endpoint=True)[1:-1]) cp = [quantile_cp[0]] + distance_cp + [quantile_cp[-1]] ##判断分箱点是否存在重复值 if not check_unique(cp): print( "quantile cut off points for {0} with {1} bins is not unique, need extra operation" .format(var, max_interval)) cp = sorted(list(set(cp))) return cp
def distance_binning(data, var, max_interval=10, special_attribute=[]): ''' 用于等距分箱返回分箱节点 不允许存在缺失值 不同值的个数一定要超过max_interval的值 data: 数据源 DataFrame var: 待分箱的变量 str max_interval: 分箱的组数 int special_attribute: 在切分数据集的时候,某些特殊值需要排除在外 list return: cp: 分箱切分的节点 ''' print('正在进行变量{0}的等距分箱'.format(var)) ##从DataFrame里面取出对应列的Series 并做好排序工作 binning_series = data[var].loc[~data[var].isin(special_attribute )].sort_values() ##判断是否存在缺失值 if np.sum(binning_series.isna()) > 0: raise ValueError("detect nan values in {0}".format(var)) ##判断不同值的个数是否满足条件 different_value_nums = len(binning_series.value_counts()) if different_value_nums < max_interval: raise ValueError( "value_counts for {0} is {1}, less than max_interval {2}".format( var, different_value_nums, max_interval)) ##这里用1:-1的原因是10分箱只需要9个cut off point就可以了 cp = list( np.linspace(binning_series.min(), binning_series.max(), max_interval + 1, endpoint=True)[1:-1]) ##判断分箱点是否存在重复值 if not check_unique(cp): print( "quantile cut off points for {0} with {1} bins is not unique, need extra operation" .format(var, max_interval)) cp = sorted(list(set(cp))) return cp
def data_clean(config_dict,save_var_type_dict=True): """ read rawdata lists and then combine them, based on filter condition to get clean data 1、读取原始数据(若多数据源,那么进行数据聚合),把\\N转换成缺失值,增加一列source_index列; 2、读取配置文件解析出当前数据集中的y相关、连续型、类别型3种变量 3、去掉不需要的数据列和数据表 4、特殊类别变量和异常值处理 5、Y值相关变量处理 6、指定变量的缺失值添补 7、训练集和测试集划分(跨时间) 8、去掉在某个数据集上缺失百分百的变量 9、把清洗好的数据集输出到本地目录 然后返回保留的变量信息 config_dict需要包含以下key和value: 1、data_path数据所在文件的绝对地址 2、data_list数据文件的名称 只能读csv文件 .csv不用加上 3、config_name变量配置文件的名称 只能读xlsx文件 .xlsx要加上 4、train_output_path最终训练数据的输出名称 5、test_output_path最终测试数据的输出名称 6、data_select筛选阐述 包含要删除的列list 要删除的表list 以及要筛选的时间段 7、source_index数据源列名 8、time_index时间列列名 9、sample_index样本id列列名 config_dict现成的例子: config_dict = {'data_path':r'E:\rmpgy\data', 'data_list':['data_a','data_b'], 'config_name':'var_config_0306.xlsx', 'train_output_path':'train_clear.csv', 'test_output_path':'test_clear.csv', 'var_type_dict_output_path':'var_type_dict.json', 'data_select':{'table_to_drop':['arc_mxdata_taobao','arc_kexin_xbehavior'], 'var_to_drop':['zm_score'], 'ext_var':['first_login_phone_type','first_login_time','fitsr_register_time','gmt_mobile','last_login_phone_type','last_login_time','nation'], 'time_to_stay':{'data_a_train':['2018-10-01 00:00:00','2018-11-30 23:59:59'], 'data_a_test':['2018-12-01 00:00:00','2018-12-10 23:59:59'], 'data_b_train':['2018-10-01 00:00:00','2018-11-30 23:59:59'], 'data_b_test':['2018-12-01 00:00:00','2018-12-10 23:59:59'] }}, 'source_index':'source_index', ##数据源index的列名 'time_index':'risk_time', ##时间index的列名 'sample_index':'consumer_no', ##样本index的列名 'y_type':'type_2', ##通过哪种方式计算样本标签 model_y_generator函数参数 'is_grey_good':True, ##通过哪种方式计算样本标签 model_y_generator函数参数 'y_column':'user_type', ##通过哪种方式计算样本标签 model_y_generator函数参数 'save_all_data':True ##是否要保留全部数据 如果是False 会把无标签的样本都删除 如果要做拒绝推断 建议保留全部样本然后自己进行筛选 } """ ##参数 ##数据所在的地址 data_path = config_dict.get('data_path') ##需要读取的数据名称list 只可读取csv文件 data_list = config_dict.get('data_list') ##变量配置文件 config_name = config_dict.get('config_name') ##训练数据输出名称 train_output_path = config_dict.get('train_output_path') ##测试数据输出名称 test_output_path = config_dict.get('test_output_path') ##变量筛选结果输出名称 var_type_dict_output_path = config_dict.get('var_type_dict_output_path') ##筛选参数 data_select = config_dict.get('data_select') #需要保留的列 source_index = config_dict.get('source_index') time_index = config_dict.get('time_index') sample_index = config_dict.get('sample_index') id_col_to_retain = [source_index,time_index,sample_index] y_type = config_dict.get('y_type','type_1') is_grey_good = config_dict.get('is_grey_good',False) y_column = config_dict.get('y_column','user_type') ##数据筛选函数 def data_time_select(orig_data,time_to_stay,plat_name): start_time = dt.strptime(time_to_stay.get(plat_name+'_train')[0],'%Y-%m-%d %H:%M:%S') end_time = dt.strptime(time_to_stay.get(plat_name+'_test')[1],'%Y-%m-%d %H:%M:%S') orig_data = orig_data[(orig_data['risk_time'] >= start_time)&(orig_data['risk_time'] <= end_time)] return orig_data def train_test_select(orig_data,select_type,time_to_stay): output_data = pd.DataFrame() for i in np.arange(len(data_list)): temp_data = orig_data[orig_data[source_index] == data_list[i]] start_time = dt.strptime(time_to_stay.get(data_list[i]+'_'+select_type)[0],'%Y-%m-%d %H:%M:%S') end_time = dt.strptime(time_to_stay.get(data_list[i]+'_'+select_type)[1],'%Y-%m-%d %H:%M:%S') temp_data = temp_data[(temp_data['risk_time'] >= start_time)&(temp_data['risk_time'] <= end_time)] if i == 0: output_data = temp_data else: output_data = pd.concat([output_data,temp_data]) return output_data ##一、读取原始数据(若多平台数据,进行数据聚合),把\\N转换成缺失值,增加一列plat列; print('读取原始数据'.center(50, '=')) raw_data = pd.DataFrame() for i in np.arange(len(data_list)): print('正在读取'+data_list[i]) path = os.path.join(data_path,data_list[i]+'.csv') temp_data = pd.read_csv(path,header = 0, encoding = 'utf-8',na_values = '\\N') temp_data[source_index] = data_list[i] temp_data['risk_time'] = temp_data['risk_time'].astype('datetime64[ns]') temp_data = data_time_select(temp_data,data_select.get('time_to_stay',[]),data_list[i]) print('筛选结果区间:' + dt.strftime(temp_data['risk_time'].min(),'%Y-%m-%d') + '~' + dt.strftime(temp_data['risk_time'].max(),'%Y-%m-%d')) if i == 0: raw_data = temp_data else: raw_data = pd.concat([raw_data,temp_data]) raw_data.reset_index(drop=True,inplace=True) ##二、读取配置文件 print('读取配置文件'.center(50, '='),'\n') ##读取配置文件 var_config = pd.read_excel(os.path.join(data_path,config_name),sheet_name=u'全量变量分类细节') ##解析出当前数据集中的y相关、连续型、类别型3种变量 y_label_var = [i for i in list(raw_data.columns) if i in list(var_config['var_en'][(var_config['is_useful'] == 1)&(var_config['is_y_related'] == 1)])] numeric_var = [i for i in list(raw_data.columns) if i in list(var_config['var_en'][(var_config['is_useful'] == 1)&(var_config['is_continuous'] == 1)])] category_var = [i for i in list(raw_data.columns) if i in list(var_config['var_en'][(var_config['is_useful'] == 1)&(var_config['is_categorical'] == 1)])] ext_var = [i for i in list(raw_data.columns) if i in data_select.get('ext_var',[])] print('ext_var在数据集中找到如下几个:%s'%ext_var) ##特殊类别转换文件 special_replace = pd.read_excel(os.path.join(data_path,config_name),sheet_name=u'字符转换规则') special_replace = special_replace[(special_replace['label_cn'] == '-')&(special_replace['var_en'].isin(numeric_var))] ##(2)去掉不需要的数据列 print('去掉不需要的数据列'.center(50, '=')) for col in data_select.get('var_to_drop',[]): print('正在删除数据列'+col) numeric_var = [i for i in numeric_var if i not in [col]] category_var = [i for i in category_var if i not in [col]] ##(3)去掉不需要的数据表 print('去掉不需要的数据表'.center(50, '=')) for table in data_select.get('table_to_drop',[]): print('正在删除数据表'+table) table_col = var_config['var_en'][var_config['table_en'] == table].values numeric_var = [i for i in numeric_var if i not in table_col] category_var = [i for i in category_var if i not in table_col] ##(4)按需求转换Y ## 如果y_label_var小于等于1个 那么可以认为数据里面没有足够转换标签的数据 为拒绝样本 标记为-3 if len(y_label_var) > 1: raw_data[y_column] = model_y_generator(raw_data[y_label_var],y_type,is_grey_good) y_label_var = [y_column] else: raw_data[y_column] = -3 y_label_var = [y_column] if config_dict['save_all_data']: print('保留无标签数据'.center(50, '=')) raw_data = raw_data else: raw_data = raw_data[raw_data[y_column].isin([0,1])] ##三、把代表缺失值和异常值的数据转成缺失值,同时修正变量的类型; ##对特殊类别的变量,进行优先特殊转换 print('特殊类别变量转换'.center(50, '='),'\n') for var in special_replace['var_en']: if var in numeric_var + category_var: print(('正在转换特殊字段'+var+'的异常值').center(50, ' ')) raw_data[var] = raw_data[var].replace({special_replace['label_cn'][special_replace['var_en']==var].values[0]:special_replace['label_num'][special_replace['var_en']==var].values[0]}) if 'tmall_level' in numeric_var + category_var: print(('正在转换特殊字段'+'tmall_level'+'的异常值').center(50, ' ')) raw_data['tmall_level'] = raw_data['tmall_level'].replace({'-':np.nan}) raw_data['tmall_level'] = raw_data['tmall_level'].astype('float64') raw_data['tmall_level'][raw_data['tmall_level'] <= 0] = np.nan ##对数值型 小于等于anomaly_value记为异常值 转换缺失值 并统一成float类型 print('数值型异常处理'.center(50, '=')) for var in numeric_var: ##进行异常值转换 if ~var_config['anomaly_value'].isna()[var_config['var_en'] == var].values[0]: ##判断是否有标记的异常值 anomaly_value = var_config['anomaly_value'][var_config['var_en'] == var].values[0] print(('正在转换连续字段'+var+'的异常值:'+str(anomaly_value)+' 类型:'+str(type(anomaly_value)).split("'")[1])) if type(anomaly_value) is str: raw_data[var] = raw_data[var].replace(to_replace=anomaly_value,value=np.nan) else: raw_data[var][raw_data[var] <= anomaly_value] = np.nan ##统一numeric_var的类型 if raw_data[var].dtype != np.float64: print('正在转换连续字段'+var+'的字段类型') raw_data[var] = raw_data[var].astype(np.float64) ##对类别型 等于anomaly_value记为异常值 转换成缺失值 print('类别型异常处理'.center(50, '=')) for var in category_var: if ~var_config['anomaly_value'].isna()[var_config['var_en'] == var].values[0]: ##判断是否有标记的异常值 anomaly_value = var_config['anomaly_value'][var_config['var_en'] == var].values[0] print(('正在转换类别字段'+var+ '的异常值:'+str(anomaly_value)+' 类型:'+str(type(anomaly_value)).split("'")[1])) if type(anomaly_value) is str: raw_data[var] = raw_data[var].replace(to_replace=anomaly_value,value=np.nan) # 常数类别变量的去除 # category_var_new = [cate for cate in list(category_var) if len(raw_data[cate].value_counts().index) != 1] ##五、缺失值的填补 ##进行部分指定变量的缺失值填补工作 print('缺失值填补'.center(50, '='),'\n') trans_var = var_config['var_en'][~var_config['na_fill'].isna()].values for var in trans_var: raw_data[var] = raw_data[var].replace(to_replace=np.nan,value=var_config['na_fill'][var_config['var_en']==var].values[0]) ##六、训练集验证集划分 print('训练集验证集划分'.center(50, '=')) train_data = train_test_select(raw_data,'train',data_select.get('time_to_stay',[])) test_data = train_test_select(raw_data,'test',data_select.get('time_to_stay',[])) print('训练集大小:' + str(len(train_data)) + ' 验证集大小:' + str(len(test_data))) ##去掉缺失值百分之百的变量 print('删除在某个数据集上缺失为百分百的变量'.center(50, '=')) train_na_percent = train_data.apply(lambda x:np.sum(x.isna())/len(x)) test_na_percent = test_data.apply(lambda x:np.sum(x.isna())/len(x)) na_drop_var = [i for i in set(train_na_percent.index[train_na_percent==1].values) | set(test_na_percent.index[test_na_percent==1].values) if i in numeric_var+category_var] print('删除数值变量:%s'%[i for i in numeric_var if i in na_drop_var]) print('删除类别变量:%s'%[i for i in category_var if i in na_drop_var] ) print('删除ext变量:%s'% [i for i in ext_var if i in na_drop_var] ) numeric_var = [i for i in numeric_var if i not in na_drop_var] category_var = [i for i in category_var if i not in na_drop_var] ext_var = [i for i in ext_var if i not in na_drop_var] ##检查不同组内的列名是否有重复 if not check_unique(numeric_var): print('numeric_var存在重复列') if not check_unique(category_var): print('category_var存在重复列') if not check_unique(ext_var): print('ext_varr存在重复列') ##检查不同组间是否列名有重复 check_non_intersect(numeric_var,category_var) check_non_intersect(ext_var,category_var) check_non_intersect(ext_var,numeric_var) ##七、输出结果 print('输出最终结果'.center(50, '=')) print('数值型、类别型、ext变量:',len(numeric_var),len(category_var),len(ext_var),'总数据集维度:',raw_data[numeric_var+category_var+ext_var+y_label_var].shape) print('训练集输出'.center(50, '-')) for plat in data_list: print(plat+'时间区间:'+dt.strftime(train_data[train_data[source_index] == plat]['risk_time'].min(),'%Y-%m-%d')+'~'+dt.strftime(train_data[train_data[source_index] == plat]['risk_time'].max(),'%Y-%m-%d')+\ ' 数据集大小:'+str(len(train_data[train_data[source_index] == plat]))) print('验证集输出'.center(50, '-')) for plat in data_list: print(plat+'时间区间:'+dt.strftime(test_data[test_data[source_index] == plat]['risk_time'].min(),'%Y-%m-%d')+'~'+dt.strftime(test_data[test_data[source_index] == plat]['risk_time'].max(),'%Y-%m-%d')+\ ' 数据集大小:'+str(len(test_data[test_data[source_index] == plat]))) train_data[id_col_to_retain+numeric_var+category_var+ext_var+y_label_var].to_csv(os.path.join(data_path,train_output_path),index=False) test_data[id_col_to_retain+numeric_var+category_var+ext_var+y_label_var].to_csv(os.path.join(data_path,test_output_path),index=False) var_type_dict = {'source_index':source_index, 'time_index':time_index, 'sample_index':sample_index, 'numeric_var':numeric_var, 'category_var':category_var, 'ext_var':ext_var, 'y_label_var':y_label_var[0] } if save_var_type_dict: with open(os.path.join(data_path,var_type_dict_output_path),'w') as f: f.write(json.dumps(var_type_dict))