数据清洗与预处理代码详解——德国信贷数据集(data cleaning and preprocessing - German credit datasets)


 

最近看了一本《Python金融大数据风控建模实战:基于机器学习》(机械工业出版社)这本书,看了其中第4章:数据清洗和预处理的内容,了解了代码,觉得写的不错,所以分享给大家。

 

1. 数据集

德国信贷数据集。官网地址 http://archive.ics.uci.edu/ml/datasets/Statlog+%28German+Credit+Data%29 。

该数据集包含 1000条样本数据,其中有20个标签变量('status_account', 'duration', 'credit_history', 'purpose', 'amount', 'svaing_account', 'present_emp', 'income_rate', 'personal_status', 'other_debtors', 'residence_info', 'property', 'age', 'inst_plans', 'housing', 'num_credits', 'job', 'dependents', 'telephone', 'foreign_worker')和1个结果变量('target')。

数据集以及说明我已经上传到网盘中:

链接:https://pan.baidu.com/s/1yWqbUHsP8wky0_kiOE1mEQ
提取码:6666

 

2. 代码思路

数据清洗和预处理的一般流程:

(1)数据集成:将多个数据源的数据构成一个统一的数据结构或数据表的过程。

(2)数据清洗:即清除“脏数据”的过程。数据清洗包括特殊字符清洗、数据格式转换、数据概念统一、数据类型转换和样本去冗余等。

(3)探索性数据分析(Exploratory Data Analysis,EDA)又称为描述性统计分析,是一种通过计算统计量、数据可视化等方法,快速了解原始数据的结构与规律的一种数据分析方法。

(4)数据预处理:对缺失值、异常值等“脏数据”进行处理的过程。

 

3. python代码详解(代码来自该书第四章内容),代码已经写了详细注释,该代码相对简单,只要认真走一遍就能看懂~

 1 import os  2 import pandas as pd  3 import numpy as np  4 import time  5 import datetime  6 # missingno是缺失值可视化工具库
 7 import missingno as msno  8 import warnings  9 warnings.filterwarnings("ignore")  # 忽略警告
 10 import matplotlib.pyplot as plt  11 import matplotlib  12 matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 用黑体显示中文
 13 matplotlib.rcParams['axes.unicode_minus'] = False  # 正常显示负号
 14 
 15 
 16 # 数据读取
 17 def data_read(data_path, file_name):  18 
 19     data_path_whole = os.path.join(data_path, file_name)  20     # header=None表示第一行不是列名
 21     # delim_whitespace表示是否用空格为分隔符,如果为True,那么其他字符如,就不会被认为是分隔符
 22     df = pd.read_csv(data_path_whole, delim_whitespace=True, header=None)  23     # 变量重命名
 24     columns = ['status_account', 'duration', 'credit_history', 'purpose', 'amount', 'svaing_account', 'present_emp', 'income_rate', 'personal_status',  25                'other_debtors', 'residence_info', 'property', 'age', 'inst_plans','housing', 'num_credits', 'job', 'dependents', 'telephone', 'foreign_worker', 'target']  26     # 添加列标题
 27     df.columns = columns  28     # 将标签变量由状态1,2转为0,1; 0表示好用户,1表示坏用户
 29     df.target = df.target - 1
 30 
 31     return df  32 
 33 
 34 # 离散变量与连续变量区分
 35 def category_continue_separation(df, feature_names):  36     categorical_var = []  37     numerical_var = []  38     # 移除 "target" 标签
 39     if 'target' in feature_names:  40         feature_names.remove('target')  41     # 先判断类型,如果是int或float就直接作为连续变量
 42     # DataFrame的select_dtypes方法是返回具有指定数据类型的列
 43     numerical_var = list(df[feature_names].select_dtypes(include=['int', 'float', 'int32', 'float32', 'int64', 'float64']).columns.values)  44     # 获取剩下的标签
 45     categorical_var = [x for x in feature_names if x not in numerical_var]  46 
 47     return categorical_var, numerical_var  48 
 49 
 50 # 字符串末位随机添加符号
 51 def add_str(x):  52     str_1 = ['%', ' ', '/t', '$', ';', '@']  53     str_2 = str_1[np.random.randint(0, high=len(str_1) - 1)]  54     return x + str_2  55 
 56 
 57 # 生成一列时间戳数据
 58 def add_time(num, style="%Y-%m-%d"):  59     # time.mktime(t) 函数接收struct_time对象作为参数,返回用秒数来表示时间的浮点数。
 60     # 它的参数t是 结构化的时间或者完整的9位元组元素
 61     start_time = time.mktime((2010, 1, 1, 0, 0, 0, 0, 0, 0))  62     stop_time = time.mktime((2015, 1, 1, 0, 0, 0, 0, 0, 0))  63     re_time = []  64     for i in range(num):  65         rand_time = np.random.randint(start_time, stop_time)  66         # 将时间戳生成时间元组
 67         # Python time localtime() 作用是格式化时间戳为本地的时间
 68         date_touple = time.localtime(rand_time)  69         # Python time strftime() 函数接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数 format 决定。
 70  re_time.append(time.strftime(style, date_touple))  71     return re_time  72 
 73 
 74 # 获取行值
 75 def add_row(df_temp, num):  76     # 生成num个随机值
 77     index_1 = np.random.randint(low=0, high=df_temp.shape[0] - 1, size=num)  78     # loc为Selection by Label函数,即为按标签取数据,这里是获取数值标签的数据,这里的数值标签是行,所以就是获取这些行的值
 79     return df_temp.loc[index_1]  80 
 81 
 82 if __name__ == '__main__':  83     # 获取当前文件路径
 84     path_current = os.getcwd()  85     data_path = os.path.join(path_current, 'data')  86     file_name = 'german.csv'
 87 
 88     # 读取数据
 89     df = data_read(data_path, file_name)  90 
 91     # 区分离散变量与连续变量
 92     feature_names = list(df.columns)  93     feature_names.remove('target')  94     categorical_var, numerical_var = category_continue_separation(df, feature_names)  95     # describe()函数就是返回数据结构的统计变量。其目的在于观察这一系列数据的范围、大小、波动趋势等等,为后面的模型选择打下基础。
 96     # 统计值变量说明: count:数量统计,此列共有多少有效值; unipue:不同的值有多少个; std:标准差; min:最小值
 97     # 25%:四分之一分位数; 50%:二分之一分位数; 75%:四分之三分位数; max:最大值; mean:均值
 98     print(df.describe())  99 
100     # ------------------------------- 数据清洗 --------------------------------- #
101     # 注入“脏数据”
102     # 变量status_account 随机加入特殊字符
103     # apply函数主要用于对DataFrame中的行或者列进行特定的函数计算。
104     df.status_account = df.status_account.apply(add_str) 105     # 添加两列时间格式的数据
106     df['apply_time'] = add_time(df.shape[0], "%Y-%m-%d") 107     df['job_time'] = add_time(df.shape[0], "%Y/%m/%d %H:%M:%S") 108     # 添加行冗余数据,即复制了几行的数据
109     df_temp = add_row(df, 10) 110     # pandas.concat()通常用来连接DataFrame对象
111     df = pd.concat([df, df_temp], axis=0, ignore_index=True) 112 
113     # Pandas读取数据之后使用pandas的head()函数的时候,来观察一下读取的数据。head()函数只能读取前五行数据
114     print(df.head()) 115 
116     # 设置显示的最大列数,None表示列都要显示
117     pd.set_option('display.max_columns', 10) 118     print("---------------------------------------------") 119     print(df.head()) 120     pd.set_option('display.max_columns', None) 121     print("---------------------------------------------") 122     print(df.head()) 123 
124     # ------------------ 特殊字符清洗 -------------- #
125     # 离散变量先看一下范围
126     print("特殊字符清洗之前:", df.status_account.unique()) 127     df.status_account = df.status_account.apply( 128         lambda x: x.replace(' ', '').replace('%', '').replace('/t', '').replace('$', '').replace('@', '').replace(';', '')) 129     print("特殊字符清洗之后:", df.status_account.unique()) 130 
131     # ----------------- 时间格式统一 ----------------- #
132     # 统一为'%Y-%m-%d格式
133     df['job_time'] = df['job_time'].apply(lambda x: x.split(' ')[0].replace('/', '-')) 134     # 时间为字符串格式转为时间格式
135     df['job_time'] = df['job_time'].apply(lambda x: datetime.datetime.strptime(x, '%Y-%m-%d')) 136     df['apply_time'] = df['apply_time'].apply(lambda x: datetime.datetime.strptime(x, '%Y-%m-%d')) 137 
138     # 样本去冗余
139     print("样本去冗余之前的形状 df.shape = ", df.shape) 140     # pandas中的drop_duplicates()函数是根据指定的字段对数据集进行去重处理
141     # 参数:subset根据指定的列名进行去重,默认整个数据集
142     # keep 可选{‘first’, ‘last’, False},默认first,即默认保留第一次出现的重复值,并删去其他重复的数据,False是指删去所有重复数据。
143     # inplace 是否对数据集本身进行修改,默认False布尔值参数,默认为 False 表示删除重复项后返回一个副本,若为 Ture 则表示直接在原数据上删除重复项。
144     df.drop_duplicates(subset=None, keep='first', inplace=True) 145     print("样本去冗余之后的形状 df.shape = ", df.shape) 146 
147     # 假设订单id出现重复则去除冗余实例
148     print("按照订单id去除冗余之前 df.shape = ", df.shape) 149     df['order_id'] = np.random.randint(low=0, high=df.shape[0] - 1, size=df.shape[0]) 150     df.drop_duplicates(subset=['order_id'], keep='first', inplace=True) 151     print("按照订单id去除冗余之后 df.shape = ", df.shape) 152 
153     # 探索性分析
154     print(df[numerical_var].describe()) 155 
156     # --------------------- 添加缺失值 ------------------- #
157     # 数据清洗时,会将带某行删除,此时DataFrame或Series类型的数据不再是连续的索引,可以使用reset_index()重置索引。
158     # 在获得新的index,原来的index变成数据列,保留下来。不想保留原来的index,使用参数 drop=True
159     df.reset_index(drop=True, inplace=True) 160     print(df) 161     var_name = categorical_var + numerical_var 162     for i in var_name: 163         num = np.random.randint(low=0, high=df.shape[0] - 1) 164         # 从num中随机生成num个数
165         index_1 = np.random.randint(low=0, high=df.shape[0] - 1, size=num) 166         print("=============================================") 167         print("去重之前的 len(index_1) = ", len(index_1)) 168         # 对数据进行去重
169         index_1 = np.unique(index_1) 170         print("去重之后的 len(index_1) = ", len(index_1)) 171         df[i].loc[index_1] = np.nan 172 
173     # 缺失值绘图
174     # 数据分析之前首先要保证数据集的质量,missingno库提供了一个灵活易用的可视化工具来观察数据缺失情况
175     # msno.bar是按列对空值进行简单可视化
176     msno.bar(df, labels=True, figsize=(10, 6), fontsize=10) 177 
178     # !!!! 注意,绘制以下三种图都需要现剔除缺失值再绘制箱线图
179     # 对于连续数据绘制箱线图,观察是否有异常值
180     plt.figure(figsize=(10, 6))  # 设置图形尺寸大小
181     for j in range(1, len(numerical_var) + 1): 182         plt.subplot(2, 4, j) 183         df_temp = df[numerical_var[j - 1]][~df[numerical_var[j - 1]].isnull()] 184         # 绘制箱线图函数
185         # 箱线图在使用场景上最常见的是用于质量管理、人事测评、探索性数据分析等统计分析活动。
186         # (1)识别异常值。异常值是指数据中远离其他大部分值的数据。
187         # (2)判断偏态。偏态是指与正态分布相对,非对称分布的偏斜状态。若平均数大于众数,则为右偏态(正偏态);若平均数小于众数,则为左偏态(负偏态)。
188         # (3)评估数据集中程度。箱线图的宽度一定程度反映了数据的波动程度。因为箱线图包含中间50%的数据,若它越扁,则说明数据较为集中;若它越宽,则说明数据较为分散。
189         # 箱图是一中用于统计数据分布的统计图,也可以粗略地看出数据是否具有对称性,分布的分散程度等信息。箱图中的信息含义如下:
190         # 最下方的横线表示最小值, 最上方的横线表示最大值, 黑色空心圆圈表示异常值, 黑色实心圆圈表示极端值, 箱子由下四分位数、中值以及上四分位数组成
191  plt.boxplot( 192  df_temp, 193             notch=False,  # 中位线处不设置凹陷
194             widths=0.2,  # 设置箱体宽度
195             medianprops={'color': 'red'},  # 中位线设置为红色
196             boxprops=dict(color="blue"),  # 箱体边框设置为蓝色
197             labels=[numerical_var[j - 1]],  # 设置标签
198             whiskerprops={'color': "black"},  # 设置须的颜色,黑色
199             capprops={'color': "green"},  # 设置箱线图顶端和末端横线的属性,颜色为绿色
200             flierprops={'color': 'purple', 'markeredgecolor': "purple"}  # 异常值属性,这里没有异常值,所以没表现出来
201  ) 202  plt.show() 203 
204     # ------------------------------- 查看数据分布 ------------------------------ #
205     # -------- 连续变量不同类别下的分布 ---------- #
206     for i in numerical_var: 207         # i = 'duration'
208         # 取非缺失值的数据
209         # isnull()函数,它可以用来判断数据是否缺失值,这里是获取没有缺失值的数据
210         df_temp = df.loc[~df[i].isnull(), [i, 'target']] 211         df_good = df_temp[df_temp.target == 0] 212         df_bad = df_temp[df_temp.target == 1] 213         # 计算统计量
214         valid = round(df_temp.shape[0] / df.shape[0] * 100, 2) 215         Mean = round(df_temp[i].mean(), 2) 216         Std = round(df_temp[i].std(), 2) 217         Max = round(df_temp[i].max(), 2) 218         Min = round(df_temp[i].min(), 2) 219         # 绘图
220         plt.figure(figsize=(10, 6)) 221         fontsize_1 = 12
222         # 绘制直方图函数,其中 bins: 直方图的柱数,即要分的组数,默认为10;alpha: 透明度;
223         plt.hist(df_good[i], bins=20, alpha=0.5, label='好样本') 224         plt.hist(df_bad[i], bins=20, alpha=0.5, label='坏样本') 225         plt.ylabel(i, fontsize=fontsize_1) 226         plt.title('valid rate=' + str(valid) + '%, Mean=' + str(Mean) +', Std=' + str(Std) + ', Max=' + str(Max) + ', Min=' + str(Min)) 227  plt.legend() 228         # 保存图片
229         file = os.path.join(path_current, 'plot_num', i + '.png') 230  plt.savefig(file) 231         plt.close(1) 232 
233     # --------- 离散变量不同类别下的分布 --------- #
234     for i in categorical_var: 235         # i = 'status_account'
236         # 获取非缺失值数据
237         df_temp = df.loc[~df[i].isnull(), [i, 'target']] 238         df_bad = df_temp[df_temp.target == 1] 239         valid = round(df_temp.shape[0] / df.shape[0] * 100, 2) 240 
241         bad_rate = [] 242         bin_rate = [] 243         var_name = [] 244         for j in df[i].unique(): 245 
246             if pd.isnull(j): 247                 df_1 = df[df[i].isnull()] 248                 bad_rate.append(sum(df_1.target) / df_1.shape[0]) 249                 bin_rate.append(df_1.shape[0] / df.shape[0]) 250                 var_name.append('NA') 251             else: 252                 df_1 = df[df[i] == j] 253                 bad_rate.append(sum(df_1.target) / df_1.shape[0]) 254                 bin_rate.append(df_1.shape[0] / df.shape[0]) 255  var_name.append(j) 256         df_2 = pd.DataFrame({'var_name': var_name, 'bin_rate': bin_rate, 'bad_rate': bad_rate}) 257         # 绘图
258         plt.figure(figsize=(10, 6)) 259         fontsize_1 = 12
260         # 绘制柱状图
261         plt.bar(np.arange(1, df_2.shape[0] + 1), df_2.bin_rate, 0.1, color='black', alpha=0.5, label='占比') 262         plt.xticks(np.arange(1, df_2.shape[0] + 1), df_2.var_name) 263         # 绘制折线图
264         plt.plot(np.arange(1, df_2.shape[0] + 1), df_2.bad_rate, color='green', alpha=0.5, label='坏样本比率') 265 
266         plt.ylabel(i, fontsize=fontsize_1) 267         plt.title('valid rate=' + str(valid) + '%') 268  plt.legend() 269         # 保存图片
270         file = os.path.join(path_current, 'plot_cat', i + '.png') 271  plt.savefig(file) 272         plt.close(1)

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM