數據預處理
數據可劃分爲結構化數據與非結構化數據,定義如下:
- 結構化數據
代表性的有數值型、字符串型數據 - 非結構化數據
代表的有文本型、圖像型、視頻型以及語音型數據
結構化數據預處理
預處理一般可分爲缺失值處理、離羣值(異常值)處理以及數據變換
缺失值處理
一般來說,未經處理的原始數據中通常會存在缺失值、離羣值等,因此在建模訓練之前需要處理好缺失值。
缺失值處理方法一般可分爲:刪除、統計值填充、統一值填充、前後向值填充、插值法填充、建模預測填充和具體分析7種方法。
直接刪除
缺失值最簡單的處理方法是刪除,所謂刪除就是刪除屬性或者刪除樣本,刪除一般可分爲兩種情況:
-
刪除屬性(特徵)
如果某一個特徵中存在大量的缺失值(缺失量大於總數據量的40%~50%及以上),
那麼我們可以認爲這個特徵提供的信息量非常有限,這個時候可以選擇刪除掉這一維特徵。 -
刪除樣本
如果整個數據集中缺失值較少或者缺失值數量對於整個數據集來說可以忽略不計的情況下,
那麼可以直接刪除含有缺失值的樣本記錄。
注意事項:
如果數據集本身數據量就很少的情況下,不建議直接刪除缺失值。
代碼實現
構造假數據做演示,就上面兩種情況進行代碼實現刪除
import numpy as np
import pandas as pd
# 構造數據
def dataset():
col1 = [1, 2, 3, 4, 5, 6, 7, 8, 9,10]
col2 = [3, 1, 7, np.nan, 4, 0, 5, 7, 12, np.nan]
col3 = [3, np.nan, np.nan, np.nan, 9, np.nan, 10, np.nan, 4, np.nan]
y = [10, 15, 8, 12, 17, 9, 7, 14, 16, 20]
data = {'feature1':col1, 'feature2':col2, 'feature3':col3, 'label':y}
df = pd.DataFrame(data)
return df
data = dataset()
print(data)
# 刪除屬性
def delete_feature(df):
N = df.shape[0] # 樣本數
no_nan_count = df.count().to_frame().T # 每一維特徵非缺失值的數量
del_feature, save_feature = [], []
for col in no_nan_count.columns.tolist():
loss_rate = (N - no_nan_count[col].values[0])/N # 缺失率
# print(loss_rate)
if loss_rate > 0.5: # 缺失率大於 50% 時,將這一維特徵刪除
del_feature.append(col)
else:
save_feature.append(col)
return del_feature, df[save_feature]
del_feature, df11 = delete_feature(data)
print(del_feature)
# 刪除樣本
# 從上面可以看出,feature2 的缺失值較少,
# 可以採取直接刪除措施
def delete_sample(df):
df_ = df.dropna()
return df_
delete_sample(df11)
統計值填充
-
對於特徵的缺失值,可以根據缺失值所對應的那一維特徵的統計值來進行填充。
-
統計值一般泛指平均值、中位數、衆數、最大值、最小值等,具體使用哪一種統計值要根據具體問題具體分析。
注意事項:當特徵之間存在很強的類別信息時,需要進行類內統計,效果比直接處理會更好。比如在填充身高時,需要先對男女進行分組聚合之後再進行統計值填充處理(男士的一般平均身高1.70,女士一般1.60)。
代碼實現
使用上面數據幀 df11 作爲演示數據集,分別實現使用各個統計值填充缺失值。
# 均值填充
print(df11.mean())
df11.fillna(df11.mean())
# 中位數填充
print(df11.median())
df11.fillna(df11.median())
# 衆數填充
# print(df11.mode())
# 由於衆數可能會存在多個,因此返回的是序列而不是一個值
# 所以在填充衆數的時候,我們可以 df11['feature'].mode()[0],可以取第一個衆數作爲填充值
def mode_fill(df):
for col in df.columns.tolist():
if df[col].isnull().sum() > 0: # 有缺失值就進行衆數填充
df_ = df.fillna(df11[col].mode()[0])
return df_
mode_fill(df11)
# 最大值/最小值填充
df11.fillna(df11.max())
df11.fillna(df11.min())
統一值填充
- 對於缺失值,把所有缺失值都使用統一值作爲填充詞,所謂統一值是指自定義指定的某一個常數。
- 常用的統一值有:空值、0、正無窮、負無窮或者自定義的其他值
注意事項:當特徵之間存在很強的類別信息時,需要進行類內統計,效果比直接處理會更好。比如在填充身高時,需要先對男女進行分組聚合之後再進行統一值填充處理。(男士的身高缺失值使用統一填充值就自定爲常數1.70,女士自定義常數1.60)。
前後向值填充
- 前後向值填充是指使用缺失值的前一個或者後一個的值作爲填充值進行填充。
離羣值處理
標準差法
又稱爲拉依達準則(標準差法),適用於有較多組數據的時候。
工作原理:它是先假設一組檢測數據只含有隨機誤差,對其進行計算處理得到標準偏差,
按一定概率確定一個區間,認爲凡超過這個區間的誤差,就不屬於隨機誤差而是粗大誤差,
含有該誤差的數據應予以剔除。
標準差本身可以體現因子的離散程度,是基於因子的平均值μ而定的。在離羣值處理過程中,
可通過用μ±nσ來衡量因子與平均值的距離
公式:假設有近似服從正態分佈離散數據X=[x1,x2,…,xn],其均值μ與標準差σ分別爲:
# 標準差法
import seaborn as sns
import numpy as np
def std_(df):
item, N = 'sepal_length', df.shape[0]
M = np.sum(df[item])/N
assert (M == np.mean(df[item])), 'mean is error'
S = np.sqrt(np.sum((df[item]-M)**2)/N)
L, R = M-3*S, M+3*S
return '正常區間值爲 [%.4f, %.4f]' % (L, R)
df = sns.load_dataset('iris')
std_(df)
MAD法
概念:又稱爲絕對值差中位數法,是一種先需計算所有因子與中位數之間的距離總和來檢測離羣值的方法,適用大樣本數據
公式:設有平穩離散數據X=[x1,x2,…,xn],其數據中位數記:
# MAD法
def MAD(df):
item, N = 'sepal_length', df.shape[0]
M = np.median(df[item])
A = np.sqrt(np.sum((df[item]-M)**2)/N)
L, R = M-3*A, M+3*A
return '正常區間值爲 [%.4f, %.4f]' % (L, R)
MAD(df)
圖像對比法
- 所謂的圖像對比法是通過比較訓練集和測試集對應的特徵數據在某一區間是否存在較大的差距來判別這一區間的數據是不是屬於異常離羣值。
優缺點
- 優點:可以防止訓練集得到的模型不適合測試集預測的模型,從而減少二者之間的誤差。
應用場景及意義
- 意義:提高模型的可靠性和穩定性。
# 功能實現
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
# 構造一個演示數據
D1 = {'feature1':[1,4,3,4,6.3,6,7,8,8.3,10,9.5,12,11.2,14.5,17.8,15.3,17.3,17,19,18.8],
'feature2':[11,20,38,40,59,61,77,84,99,115,123,134,130,155,138,160,152,160,189,234],
'label':[1,5,9,4,12,6,17,25,19,10,31,11,13,21,15,28,35,24,19,20]}
D2= {'feature1':[1,3,3,6,5,6,7,10,9,10,13,12,16,14,15,16,14,21,19,20],
'feature2':[13,25,33,49,45,66,74,86,92,119,127,21,13,44,34,29,168,174,178,230]}
df_train = pd.DataFrame(data=D1)
df_test = pd.DataFrame(data=D2)
L = [df_train.iloc[:,1], df_test.iloc[:,1], 'train_feature2', 'test_feature2']
fig = plt.figure(figsize=(15,5))
X = list(range(df_train.shape[0]))
for i in range(2):
ax = fig.add_subplot(1,2,i+1)
ax.plot(X, L[i],label=L[i+2],color='red')
ax.legend()
ax.set_xlabel('Section')
結論:
-
從上面的的圖形對比,明顯發現在區間 [10,15] 之間訓練集 feature2 和測試集 feature2 的數據差距懸殊(嚴重突變),
因此區間 [10,15] 的數據可判定爲離羣異常值,應在訓練集和測試集中同時剔除掉,防止訓練集訓練的模型不適用於測試集的預測。 -
如果不進行剔除或其他處理,訓練模型在測試集預測會存在巨大的誤差。
無量綱化
極差標準化(Min-nax)
Min-max區間縮放法(極差標準化),將數值縮放到[0 1]區間
極大值標準化(Max-abs)
Max-abs (極大值標準化),標準化之後的每一維特徵最大要素爲1,其餘要素均小於1,理論公式如下:
標準差標準化(z-score)
z-score 標準化(標準差標準化)爲類似正態分佈,均值爲0,標準差爲1
歸一化——總和標準化
歸一化(總和標準化),歸一化的目的是將所有數據變換成和爲1的數據,常用於權重的處理,在不同數據比較中,常用到權重值來表示其重要性,往往也需要進行加權平均處理。
非線性歸一化
非線性歸一化:對於所屬範圍未知或者所屬範圍是全體實數,同時不服從正態分佈的數據,
對其作Min-max標準化、z-score標準化或者歸一化都是不合理的。
要使範圍爲R的數據映射到區間[0,1]內,需要作一個非線性映射。而常用的有sigmoid函數、arctan函數和tanh函數。
import seaborn as sns
import numpy as np
df = sns.load_dataset('iris')
print(df.shape)
df.head()
# 1.Min-max 區間縮放法-極差標準化
# 自己手寫理論公式來實現功能
# 縮放到區間 [0 1]
def Min_max(df):
x_minmax = []
for item in df.columns.tolist()[:4]:
MM = (df[item] - np.min(df[item]))/(np.max(df[item])-np.min(df[item]))
x_minmax.append(MM.values)
return np.array(np.matrix(x_minmax).T)[:5]
Min_max(df)
# 直接調用 sklearn 模塊的 API 接口
# 極差標準化(最大最小值標準化)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
x_minmax_scaler = mms.fit_transform(df.iloc[:, :4])
x_minmax_scaler[:5]
# 2.MaxAbs 極大值標準化
# 自己手寫理論公式來實現功能
def MaxAbs(df):
x_maxabs_scaler = []
for item in df.columns.tolist()[:4]:
Max = np.abs(np.max(df[item]))
MA = np.abs(df[item])/Max
x_maxabs_scaler.append(MA)
return np.array(np.matrix(x_maxabs_scaler).T)[:5]
MaxAbs(df)
# 直接調用 sklearn 模塊的 API 接口
# 極大值標準化
from sklearn.preprocessing import MaxAbsScaler
mas = MaxAbsScaler()
x_maxabs_scaler = mas.fit_transform(df.iloc[:, :4])
x_maxabs_scaler[:5]
# 3.z-score 標準差標準化
# 自己手寫理論公式來實現功能
# 標準化之後均值爲 0,標準差爲 1
def z_score(df):
N, x_z = df.shape[0], []
for item in df.columns.tolist()[:4]:
mean = np.sum(df[item])/N
std = np.sqrt(np.sum((df[item]-mean)**2)/N)
Z = (df[item] - mean)/std
x_z.append(Z)
return np.array(np.matrix(x_z).T)[:5]
z_score(df)
# 直接調用 sklearn 模塊的 API 接口
# 標準差標準化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
x_std_scaler = ss.fit_transform(df.iloc[:, :4])
x_std_scaler[:5]
# 4.歸一化---總和歸一化
# 自己手寫理論公式來實現功能
# 處理成所有數據和爲1,權重處理
def feature_importance(df):
x_sum_scaler = []
for item in df.columns.tolist()[:4]:
S = np.sum(df[item])
FI = df[item]/S
x_sum_scaler.append(FI)
return np.array(np.matrix(x_sum_scaler).T)[:5]
feature_importance(df)
# 5.非線性歸一化
# 自己手寫理論公式來實現功能
# sigmoid 函數歸一化
def sigmoid(df):
x_sigmoid = []
for item in df.columns.tolist()[:4]:
S = 1/(1+np.exp(-df[item]))
x_sigmoid.append(S)
return np.array(np.matrix(x_sigmoid).T)[:5]
sigmoid(df)
類別數據處理
很多算法模型不能直接處理字符串數據,因此需要將類別型數據轉換成數值型數據
序號編碼(Ordinal Encoding)
通常用來處理類別間具有大小關係的數據,比如成績(高中低)
假設有類別數據X=[x1,x2,…,xn],則序號編碼思想如下:
- (1)確定X中唯一值的個數K,將唯一值作爲關鍵字,即Key=[x1,x2,…,xk]
- (2)生成k個數字作爲鍵值,即Value=[0,1,2,…,k]
- (3)每一個唯一的類別型元素對應着一個數字,即鍵值對dict={key1:0, key2:1,…, keyk:k}
# 序號編碼
# 自己手寫理論公式實現功能(可優化)
import seaborn as sns
def LabelEncoding(df):
x, dfc = 'species', df
key = dfc[x].unique() # 將唯一值作爲關鍵字
value = [i for i in range(len(key))] # 鍵值
Dict = dict(zip(key, value)) # 字典,即鍵值對
for i in range(len(key)):
for j in range(dfc.shape[0]):
if key[i] == dfc[x][j]:
dfc[x][j] = Dict[key[i]]
dfc[x] = dfc[x].astype(np.float32)
return dfc[:5]
data = sns.load_dataset('iris')
le = LabelEncoding(data)
# 調用 sklearn 模塊的 API 接口
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
x_le = le.fit_transform(data['species'])
獨熱編碼(One-hot Encoding)
通常用於處理類別間不具有大小關係的特徵,比如血型(A型血、B型血、AB型血、O型血),獨熱編碼會把血型變成一個稀疏向量,A型血表示爲(1,0,0,0),B型血表示爲(0,1,0,0),AB型血表示爲(0,0,1,0),O型血表示爲(0,0,0,1)
- (1)在獨熱編碼下,特徵向量只有某一維取值爲1,其餘值均爲0,因此可以利用向量的稀疏來節省空間
- (2)如果類別型的唯一類別元素較多,可能會造成維度災難,因此需要利用特徵選擇來降低維度。
假設有類別數據X=[x1,x2,…,xn],則獨熱編碼思想如下:
- (1)確定X中唯一值的個數K,將唯一值作爲關鍵字,即Key=[x1,x2,…,xk]
- (2)生成k個數字爲1的一維數組作爲鍵值,即Value=[1,1,1,…,k]
- (3)每一個唯一的類別型元素對應着一個數字,即鍵值對dict={key1:0, key2:1,…, keyk:k}
- (4)創建一個空的數組v=V(n維 x k維)=np.zeros((n, k))
- (5)將數值對應的那一維爲1,其餘爲0,最後將V與原始數據合併即可
原來的數據:
dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')
dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')
dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')
dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')
df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
df.head()