數據清洗與預處理代碼詳解——德國信貸數據集(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