數據預處理是指因為算法或者分析需要,對經過數據質量檢查后的數據進行轉換、衍生、規約等操作的過程。整個數據預處理工作主要包括五個方面內容:簡單函數變換、標准化、衍生虛擬變量、離散化、降維。本篇文章將作展開介紹,並提供基於Python的代碼實現。
0. 示例數據集說明
/labcenter/python/dataset2.xlsx
import pandas as pd
import numpy as np
# 讀取數據
dataset = pd.read_excel("/labcenter/python/dataset2.xlsx")
# 打印數據集
dataset
Out[58]:
col1 col2 col3 col4 col5 col6 col7
0 101 96 aaa 3.85 2017-04-05 17:39:08 0-10 800
1 102 13 bbb 2.78 2017-04-04 03:00:14 10-20 1000
2 103 160 aaa 4.40 2017-04-03 14:45:29 10-20 600
3 104 128 ccc 2.49 2017-04-22 11:17:12 20-30 2400
4 105 10 ccc 3.70 2017-04-22 16:42:08 30-40 1300
5 106 16 bbb 2.78 2017-04-19 12:26:58 0-10 1500
6 107 -31 aaa 3.34 2017-04-04 12:50:28 40-50 700
7 108 87 ccc 5.69 2017-04-13 10:15:24 20-30 1200
8 109 -221 bbb 3.35 2017-04-28 13:32:30 30-40 1900
9 110 115 aaa 5.10 2017-04-24 22:28:55 10-20 2000
## 1. 簡單函數變換 **簡單函數變換**是指對原始數據直接使用某些數學函數進行轉換,主要用於**將不具有正態分布的數據變換成具有正態分布**,同時也可以用於**對數據進行壓縮**,比如$10^8和10^9$更關注的是相對差距而不是絕對差距,可以通過取對數變換實現。 常用的函數包括:$log(x)、x^k、e^x、\frac {1}{x}、\sqrt{x}、sinx$等。 簡單函數變換會改變的原始數據的分布特征,因此使用前必須深入了解數據特征變化是否會影響到后續的分析。
# 簡單函數變換
## 取10為底的對數
np.log10(dataset['col2'])
Out[72]:
0 1.982271
1 1.113943
2 2.204120
3 2.107210
4 1.000000
5 1.204120
6 NaN
7 1.939519
8 NaN
9 2.060698
Name: col2, dtype: float64
## 取e為的底的指數
np.exp(dataset['col2'])
Out[73]:
0 4.923458e+41
1 4.424134e+05
2 3.069850e+69
3 3.887708e+55
4 2.202647e+04
5 8.886111e+06
6 3.442477e-14
7 6.076030e+37
8 1.049348e-96
9 8.787502e+49
Name: col2, dtype: float64
## 取倒數
1 / dataset['col2']
Out[74]:
0 0.010417
1 0.076923
2 0.006250
3 0.007812
4 0.100000
5 0.062500
6 -0.032258
7 0.011494
8 -0.004525
9 0.008696
Name: col2, dtype: float64
## 開方
np.sqrt(dataset['col2'])
Out[75]:
0 9.797959
1 3.605551
2 12.649111
3 11.313708
4 3.162278
5 4.000000
6 NaN
7 9.327379
8 NaN
9 10.723805
Name: col2, dtype: float64
## 取正弦
np.sin(dataset['col2'])
Out[76]:
0 0.983588
1 0.420167
2 0.219425
3 0.721038
4 -0.544021
5 -0.287903
6 0.404038
7 -0.821818
8 -0.885939
9 0.945435
Name: col2, dtype: float64
## 2. 標准化 標准化,是為了處理**不同規模和量綱**的數據,使其縮放到相同的數據區間和范圍,以減少規模、量綱、分布差異等對分析建模的影響。**常用的標准化方法有以下三種**: ###2.1 離差標准化 離差標准化,又稱**最大值最小值標准化(Max-Min)**,即基於原始數據的最大值、最小值對數據進行線性變換,**變換后,數據完全落入[0,1]區間內**。 **公式:** $$x'= \frac {(x-min)}{(max-min)}$$ 其中,原始數據x,其最大值、最小值分別為max、min,轉換后數據為x'。 **優點:**能夠將數據歸一化,同時能較好的保持原始數據的分布結構; **缺點:**容易受極端值的影響,極端值會使大部分數據接近於0並且差距很小,同時在出現最值范圍以外的數據時變換結果會產生錯誤; **適用場景**:適合數據比較集中的情況。 ###2.2 標准差標准化 標准差標准化,即**Z-Score標准化**,即基於原始數據的均值和標准差對數據進行標准化,**標准化后,數據呈正態分布**。 **公式:** $$x'= \frac {(x-μ)}{σ}$$ 其中,原始數據x,其均值、標准差分別為μ、σ,轉換后數據為x'。 **缺點:**是一種中心化方法,會改變原始數據的分布結構。 **適用場景**:適合數據的最值未知,且可能出現離群點的情況。 ###2.3 絕對值最大標准化 絕對值最大標准化,即**MaxAbs標准化**,即基於原始數據絕對值的最大值對數據進行標准化,**變換后,數據完全落入[-1,1]區間內**。 **公式:** $$x'= \frac {x}{maxAbs}$$ 其中,原始數據x,其絕對值的最大值為maxAbs,轉換后數據為x'。 **優點:**不會破壞原始數據的分布結構; **適用場景:**可用於稀疏矩陣、稀疏數據。
# 標准化處理
## 方法1:使用numpy
### 離差標准化
(dataset['col2'] - dataset['col2'].min()) / (dataset['col2'].max() - dataset['col2'].min())
Out[77]:
0 0.832021
1 0.614173
2 1.000000
3 0.916010
4 0.606299
5 0.622047
6 0.498688
7 0.808399
8 0.000000
9 0.881890
Name: col2, dtype: float64
### 標准差標准化
(dataset['col2'] - dataset['col2'].mean()) / dataset['col2'].std()
Out[78]:
0 0.534846
1 -0.221410
2 1.117982
3 0.826414
4 -0.248744
5 -0.194075
6 -0.622316
7 0.452842
8 -2.353503
9 0.707964
Name: col2, dtype: float64
### 絕對值最大標准化
dataset['col2'] / np.abs(dataset['col2']).max()
Out[79]:
0 0.434389
1 0.058824
2 0.723982
3 0.579186
4 0.045249
5 0.072398
6 -0.140271
7 0.393665
8 -1.000000
9 0.520362
Name: col2, dtype: float64
## 方法2:使用sklearn
from sklearn import preprocessing
### 離差標准化
minmax_scaler = preprocessing.MinMaxScaler()
minmax_scaler.fit_transform(dataset['col2'])
Out[81]:
array([ 0.832021 , 0.61417323, 1. , 0.9160105 , 0.60629921,
0.62204724, 0.49868766, 0.80839895, 0. , 0.88188976])
### 標准差標准化
zsocre_scaler = preprocessing.StandardScaler()
zsocre_scaler.fit_transform(dataset['col2'])
Out[82]:
array([ 0.56377684, -0.23338633, 1.17845688, 0.87111686, -0.26219945,
-0.2045732 , -0.65597885, 0.47733746, -2.4808102 , 0.74625998])
### 絕對值最大標准化
maxabs_scaler = preprocessing.MaxAbsScaler()
maxabs_scaler.fit_transform(dataset['col2'])
Out[83]:
array([ 0.43438914, 0.05882353, 0.7239819 , 0.57918552, 0.04524887,
0.07239819, -0.14027149, 0.39366516, -1. , 0.52036199])
## 3. 衍生虛擬變量 在數據建模過程中,很多算法都不能處理非數值型數據,必須首先將這些數據轉化為數值型。但是,即使轉化為數值型數據,也不能直接應用到算法計算中,為什么?這需要從離散型數據的分類說起。 **離散型數據也就是分類數據,主要分為兩類,一類是無序分類,一類是有序分類。** **無序分類**,是指各個類別之間沒有明顯的高、低、大、小等包含等級、順序、優劣、好壞等邏輯的划分,只是用來區分兩個或多個具有相同或相當價值的屬性。例如性別的男和女,顏色的紅、黃、藍等等。 **有序分類**,是指各個類別之間有一定的順序關系。例如用戶價值的高、中、低,學歷的博士、碩士、學士等。 對於無序分類來說,無論用什么數值來表示都無法表達出價值相等但有區分的屬性,比如性別的男和女,如果分別用1和2表示,那么1和2本身就已經帶有距離為1的差異,但實際上二者之間是沒有這種差異的,同樣,不論用任何數據都無法到達這種區分的目的。 而對於有序分類來說,無論用什么有序的數字序列都無法准確表達出有序類別之間的差異性,比如學歷的博士、碩士、學士,如果用3-2-1來表示這種順序關系,那這種差異為什么不能用30-20-10來表示呢? 所以,非數值型的離散型數據要想參與到算法計算中,不能簡單的認為轉化成用數值就表示就可以,而是必須使用其他的方法,這種方法就是**衍生虛擬變量**,也叫做**創建虛擬變量(dummy variable)、創建啞變量、創建名義變量、one-hot編碼(one-hot encoding)、N取一編碼(one-out-of encoding)**。它是指,將一個離散型變量衍生出多個真值變量(用0和1,或者True和False表示的變量),比如性別這一變量取值有男、女兩個,那么將衍生出性別是否男、性別是否女兩個變量;然后使用這些衍生出來的真值變量替換原始變量參與后續的計算。 
# 衍生虛擬變量
## 方法1:自定義函數
def dummyCreate(ser):
valueSet = ser.unique()
resDf = pd.DataFrame()
colName = ser.name
for value in valueSet:
colNameNew = colName + '_' + value
colDataTmp = ser.values
colData = (colDataTmp == value)
resDf[colNameNew] = colData
return resDf
dummyCreate(dataset['col3'])
Out[85]:
col3_aaa col3_bbb col3_ccc
0 True False False
1 False True False
2 True False False
3 False False True
4 False False True
5 False True False
6 True False False
7 False False True
8 False True False
9 True False False
## 方法2:使用sklearn
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
### 標准標簽化
labelEnc = LabelEncoder()
### one-hot編碼
ohEnc = OneHotEncoder()
### 將類別值用0.1.2……編碼
dataset['new_col'] = labelEnc.fit_transform(dataset['col3'])
### one-hot編碼
ohEncRes = ohEnc.fit_transform(dataset['new_col'].reshape(-1,1)).toarray()
### 合並結果
newDf = pd.concat((dataset, pd.DataFrame(ohEncRes)), axis=1)
### 重命名列名
dataset = newDf.rename(columns={0:'col3_aaa',1:'col3_bbb',2:'col3_ccc'})
### 打印數據集
dataset
Out[87]:
col1 col2 col3 col4 col5 col6 col7 new_col col3_aaa col3_bbb col3_ccc
0 101 96 aaa 3.85 2017-04-05 17:39:08 0-10 800 0 1.0 0.0 0.0
1 102 13 bbb 2.78 2017-04-04 03:00:14 10-20 1000 1 0.0 1.0 0.0
2 103 160 aaa 4.40 2017-04-03 14:45:29 10-20 600 0 1.0 0.0 0.0
3 104 128 ccc 2.49 2017-04-22 11:17:12 20-30 2400 2 0.0 0.0 1.0
4 105 10 ccc 3.70 2017-04-22 16:42:08 30-40 1300 2 0.0 0.0 1.0
5 106 16 bbb 2.78 2017-04-19 12:26:58 0-10 1500 1 0.0 1.0 0.0
6 107 -31 aaa 3.34 2017-04-04 12:50:28 40-50 700 0 1.0 0.0 0.0
7 108 87 ccc 5.69 2017-04-13 10:15:24 20-30 1200 2 0.0 0.0 1.0
8 109 -221 bbb 3.35 2017-04-28 13:32:30 30-40 1900 1 0.0 1.0 0.0
9 110 115 aaa 5.10 2017-04-24 22:28:55 10-20 2000 0 1.0 0.0 0.0
注意:創建虛擬變量和虛擬變量編碼(dummy encoding)是不同的概念,前者如上所述,如果一個離散變量有N個取值,就會衍生出N個真值變量,而后者——虛擬變量編碼,則是衍生出N-1個變量,它認為這個N-1個變量都為0時即表示原始數據取第N個值。
## 4. 離散化 **離散化就是將一份數據從細粒度轉化為粗粒度,實質就是數據的集中化。**主要包括**兩方面**:將連續型數據轉化成離散型數據、將離散型數據進行類別合並,具體又分為以下幾點: ###4.1 針對時間數據的離散化 針對時間數據的離散化主要是指對時間數據進行粒度上的上卷(反之是下鑽): 時間戳——>小時——>上午\下午 日期型——>星期——>周數——>季度——>年份
# 離散化
## 針對時間數據的離散化
colList = list(dataset.columns)
colList.extend(['weekday_col5','week_col5','year_col5','month_col5','day_col5','hour_col5'])
dataset = dataset.reindex(columns = colList)
for index,value in enumerate(dataset['col5']):
newValue = pd.to_datetime(value)
dataset['weekday_col5'][index] = newValue.weekday()
dataset['week_col5'][index] = newValue.week
dataset['year_col5'][index] = newValue.year
dataset['month_col5'][index] = newValue.month
dataset['day_col5'][index] = newValue.day
dataset['hour_col5'][index] = newValue.hour
### 打印數據集
dataset[['col5','weekday_col5','week_col5','year_col5','month_col5','day_col5','hour_col5']]
Out[101]:
col5 weekday_col5 week_col5 year_col5 month_col5 day_col5 hour_col5
0 2017-04-05 17:39:08 2.0 14.0 2017.0 4.0 5.0 17.0
1 2017-04-19 12:26:58 2.0 16.0 2017.0 4.0 19.0 12.0
2 2017-04-04 03:00:14 1.0 14.0 2017.0 4.0 4.0 3.0
3 2017-04-03 14:45:29 0.0 14.0 2017.0 4.0 3.0 14.0
4 2017-04-24 22:28:55 0.0 17.0 2017.0 4.0 24.0 22.0
5 2017-04-22 11:17:12 5.0 16.0 2017.0 4.0 22.0 11.0
6 2017-04-13 10:15:24 3.0 15.0 2017.0 4.0 13.0 10.0
7 2017-04-22 16:42:08 5.0 16.0 2017.0 4.0 22.0 16.0
8 2017-04-28 13:32:30 4.0 17.0 2017.0 4.0 28.0 13.0
9 2017-04-04 12:50:28 1.0 14.0 2017.0 4.0 4.0 12.0
4.2 針對類別數據的離散化
針對類別數據的離散化主要是指將多個類別進行合並而產生新的類別划分。
比如將年齡區間(0-10,10-20,20-30,30-40,40-50)合並為年齡區間(0-20,20-30,30-50)。
## 針對類別數據的離散化
mapDf = pd.DataFrame([['0-10','0-20'],['10-20','0-20'],['20-30','20-30'],['30-40','30-50'],['40-50','30-50']],columns=['col6','new_col2'])
dataset = dataset.merge(mapDf, left_on = 'col6', right_on = 'col6', how = 'inner')
### 打印
dataset[['col6','new_col2']]
Out[102]:
col6 new_col2
0 0-10 0-20
1 0-10 0-20
2 10-20 0-20
3 10-20 0-20
4 10-20 0-20
5 20-30 20-30
6 20-30 20-30
7 30-40 30-50
8 30-40 30-50
9 40-50 30-50
4.3 針對連續數據的離散化
離散化中主要的工作是針對連續數據的離散化,包括兩方面:將連續數據分成多個區間、將連續數據划分成特定的類。前者通常被稱為分箱。具體有以下幾種實現方法:
(1)自定義分箱
自定義分箱,是指根據業務經驗或者常識等自行設定划分的區間,然后將原始數據歸類到各個區間中。
(2)等寬分箱
等寬分箱,是指划分的各個區間的寬度(或稱為距離)相等,也稱為等深分箱。
(3)等頻分箱
等頻分箱,是指划分的各個區間包含的數據點的個數相等,也稱為分位數分箱。
(4)聚類法
聚類法,是指使用聚類算法自動將原始數據分成多個類別。
(5)二值化
二值化,是指設置一個閾值,將每個數據點與這個閾值進行比較,大於(或等於)閾值取某一固定值A,小於(或等於)閾值則取另一固定值B,從而將原始數據轉換為兩個取值的離散數據。
## 針對連續數據的離散化
### (1)自定義分箱
#### 定義邊界點
points = [0,800,1500,2000,2500]
#### 未設置標簽
pd.cut(dataset['col7'], points)
Out[91]:
0 (0, 800]
1 (800, 1500]
2 (800, 1500]
3 (0, 800]
4 (1500, 2000]
5 (2000, 2500]
6 (800, 1500]
7 (800, 1500]
8 (1500, 2000]
9 (0, 800]
Name: col7, dtype: category
Categories (4, object): [(0, 800] < (800, 1500] < (1500, 2000] < (2000, 2500]]
#### 設置標簽
pd.cut(dataset['col7'], points, labels = ['a','b','c','d'])
Out[92]:
0 a
1 b
2 b
3 a
4 c
5 d
6 b
7 b
8 c
9 a
Name: col7, dtype: category
Categories (4, object): [a < b < c < d]
### (2)等寬分箱
#### 箱數
bins = 4
#### 未設置標簽
pd.cut(dataset['col7'], bins)
Out[94]:
0 (598.2, 1050]
1 (1050, 1500]
2 (598.2, 1050]
3 (598.2, 1050]
4 (1950, 2400]
5 (1950, 2400]
6 (1050, 1500]
7 (1050, 1500]
8 (1500, 1950]
9 (598.2, 1050]
Name: col7, dtype: category
Categories (4, object): [(598.2, 1050] < (1050, 1500] < (1500, 1950] < (1950, 2400]]
#### 設置標簽
pd.cut(dataset['col7'], bins, labels = ['a','b','c','d'])
Out[95]:
0 a
1 b
2 a
3 a
4 d
5 d
6 b
7 b
8 c
9 a
Name: col7, dtype: category
Categories (4, object): [a < b < c < d]
### (3)等頻分箱
#### 箱數
bins = 4
#### 未設置標簽
pd.qcut(dataset['col7'], bins)
Out[97]:
0 [600, 850]
1 (1250, 1800]
2 (850, 1250]
3 [600, 850]
4 (1800, 2400]
5 (1800, 2400]
6 (850, 1250]
7 (1250, 1800]
8 (1800, 2400]
9 [600, 850]
Name: col7, dtype: category
Categories (4, object): [[600, 850] < (850, 1250] < (1250, 1800] < (1800, 2400]]
#### 設置標簽
pd.qcut(dataset['col7'], bins, labels = ['a','b','c','d'])
Out[98]:
0 a
1 c
2 b
3 a
4 d
5 d
6 b
7 c
8 d
9 a
Name: col7, dtype: category
Categories (4, object): [a < b < c < d]
### (4)聚類法
from sklearn.cluster import KMeans
data = dataset['col7']
new_data = data.reshape((data.shape[0], 1))
#### 使用K-Means聚類
kmeans = KMeans(n_clusters=4, random_state=0)
keames_result = kmeans.fit_predict(new_data)
keames_result
Out[99]: array([1, 2, 1, 1, 3, 0, 2, 2, 3, 1], dtype=int32)
### (5)二值法
from sklearn import preprocessing
#### 使用均值作為分割閾值
binarizer_scaler = preprocessing.Binarizer(threshold = dataset['col7'].mean())
temp = binarizer_scaler.fit_transform(dataset['col7'])
temp.resize(dataset['col7'].shape)
temp
Out[100]: array([0, 1, 0, 0, 1, 1, 0, 0, 1, 0])
## 5. 降維 **降維**是指降低數據集的維度數量,從而降低模型的計算工作量,減少模型的運行時間,減弱噪聲變量(無關變量)對模型結果可能產生的影響。
5.1 基於特征選擇的降維
基於特征選擇的降維,通俗的說,就是特征篩選,或者叫變量篩選。從多個變量中篩選出更有用的較少個變量,從而降低數據集的維度。特征篩選的主要方法包括:基於經驗的方法(比如專家法)、基於統計的方法(比如區分度、信息增益)和基於機器學習的方法(比如決策樹算法)。
基於特征選擇的降維的好處是,既保留了原有維度的特征,同時又完成了降維的目的。
由於特征篩選是數據准備工作的一個重要環節,因此會單獨拿出來進行總結論述,此處不做詳細討論。
5.2 基於維度轉換的降維
基於維度轉換的降維,是指按照一定的數學變換方法,把給定的一組(相關的)變量,通過數學模型從高維空間映射到低維度空間中,然后利用映射后產生的新變量來表示原有變量的總體特征,轉換后產生的新特征是多個原始特征的綜合。
具體的方法分為線性降維和非線性降維,常用的算法有主成分分析、因子分析、線性判別分析等。
關於具體的算法原理,后續將在統計學或機器學習系列中進行總結論述,此處不做討論。
基於維度轉換的降維將使用轉換后產生的新變量參與后續的建模分析,因此直接影響到模型的可解釋性和可理解性,在不要求對模型進行解釋說明時可以使用,否則,建議使用基於特征選擇的降維。
## 6. 參考與感謝 \[1] [數據挖掘概念與技術](https://book.douban.com/subject/2038599/) \[2] [數據挖掘導論](https://book.douban.com/subject/5377669/) \[3] [Python機器學習基礎教程](https://book.douban.com/subject/30147778/) \[4] [Python數據分析與數據化運營](https://book.douban.com/subject/27608466/) \[5] [Python數據分析與數據挖掘實戰](https://book.douban.com/subject/26677686/)