資料分析--資料預處理

Pennaa發表於2023-12-14

本文主要是個人的學習筆記總結,資料預處理的基本思路和方法,包括一些方法的使用示例和引數解釋,具體的資料預處理案例case詳見其他文章。如有錯誤之處還請指正!


資料在進行建模和分析前,對其進行資料處理是必不可少的,這樣做能幫助我們從根本上保證資料質量,提高資料的準確性和可靠性。主要包括資料清洗、資料轉換、資料整合、資料降維等過程。

資料的質量評定

從五個維度來對資料質量進行評定

維度 說明
有效性
Validity
資料的有效性源於統計學概念,即符合資料收集時所制定的規則及約束條件的資料。
精確度
Accuracy
精確度是衡量資料質量的另一個重要條件。
一般情況下,透過資料清理很難改善資料的準確度,這就對資料來源質量提出了較高的要求。
完整度
Completeness
如果在採集資料的過程中造成了資料丟失,也就影響了其完整程度。
不完整的資料,勢必會對分析結論造成影響,這需要進一步採取措施來改善這種情況。
一致性
Consistency
原始資料中,資料可能會存在不一致性。
例如:客戶在兩個不同的系統中提供了兩個不一樣的家庭地址,而正確的地址只有一個。
那麼,就需要我們來判斷並消除這種不一致性。
均勻度
Uniformity
資料的均勻度可能來源於度量單位的不一致,這就需要我們透過換算來解決,使得最終資料統一均勻。

資料處理步驟

資料處理的常見四個步驟:

序號 步驟 說明
1 資料清理
Data Cleansing
資料清理大致包括對空缺鍵值進行填充操作,
對噪聲資料進行相應的平滑處理,對孤立點和異常點進行刪除處理等。
2 資料整合
Data Integration
將多個資料庫或者資料檔案裡麵包含的資料進行整合處理。
3 資料轉換
Data Transformation
將資料進行聚集或者規範化處理,從一種形式轉換成另外一種我們需要的形式。
4 資料規約
Data Reduction
對龐大的資料集進行壓縮處理,且儘量保證最終預測結果的一致性。

下面將詳細介紹常用的資料處理方法:

缺失值的處理

標記缺失值
# 生成包含缺資料的示例
import numpy as np
import pandas as pd

null_data = {'A': [10, np.nan, 25, np.nan, 42, np.nan],
             'B': [12, 15, np.nan, 14, 17, np.nan],
             'C': [10, 13, 27, 13, 19, 40]}

df = pd.DataFrame(null_data)
  • 空值也有不同型別

    數值型空值 :NaN (not a number)

    空字串 :None

  • 定位缺失資料:

    • 使用 isnUll() 返回 True 代表缺失值
    • notnull()則與之相反
刪除缺失值
  • 直接刪除
    • 缺失資料佔全部資料比例非常低,可忽略不計且刪除後不會對整體資料分佈產生影響
    • 缺失資料因為本身特性無法填充,比如對於某些檢測指標,填充數值會對結果產生影響的寧願刪除也不要進行處理
# 刪除缺失值
import pandas as pd

# 建立一個示例資料框
data = {'A': [1, 2, None, 4],
        'B': [5, None, 7, 8],
        'C': [None, 10, None, 12]}
df = pd.DataFrame(data)

# 刪除包含缺失值的行
cleaned_df1 = df.dropna()  # 預設刪除包含缺失值的行
cleaned_df2 = df.dropna(axis=1)  # 刪除包含缺失值的列
cleaned_df3 = df.dropna(how='all')  # 當所有值為缺失值時刪除行
cleaned_df4 = df.dropna(thresh=2)  # 至少要有 2 個非缺失值才保留行
cleaned_df5 = df.dropna(subset=['B', 'C'])  # 只有在 'B' 和 'C' 列中同時存在空值的情況下,對應的行才會被刪除

print(cleaned_df1)
print(cleaned_df2)
print(cleaned_df3)
print(cleaned_df4)
print(cleaned_df5)
填充缺失值
  • 固定值填充:人為指定,使用行/列均值、中位數等

    # Pandas 提供的 replace t填充固定值
    print(df.replace(np.nan,0)) # 缺失值填充為0
    # 專門用於填充缺失值的函式 fillna()
    print(df.fillna(0)) 
    # 使用各列的平均值填充
    print(df.fillna(df.mean()))
    # 使用格列中位數填充
    print(df.fillna(df.median()))
    
  • 臨近值填充:使用缺失值相臨近的數值填充

    # 使用後一個臨近的資料向前填充
    print(df.fillna(method='bfill'))
    # 使用前一個臨近的資料向後填充
    print(df.fillna(method='ffill'))
    
  • 數字填充:使用函式插值填充

    資料缺失但能看出資料的變化趨勢,更好的缺失值填充方法時是使用資料方法進行插值Pandas中的interpolate()可以快速應用一些常用的插值函式

    sample_data = {'A': [1, np.nan, 3, np.nan, 5, 6],
                   'B': [1, 4, np.nan, np.nan, 25, 36]}
    
    df = pd.DataFrame(sample_data)
    # 線性插值
    print(df.interpolate(method='linear'))
    # 2次函式插值
    print(df.interpolate(method='polynomial', order=2))
    

重複值處理

# 示例資料
df = pd.DataFrame({'name': ['amy', 'david'] * 3 +
                   ['jam'], 'class': [2, 2, 2, 4, 3, 2, 4]})
  • duplicated() 判斷是否存在重複值

    print(df.duplicated()) # 判斷重複值 存在則返回True
    print(df.duplicated().sum()) # 統計判斷重複值
    
  • dropduplicates() 去除重複值

df.drop_duplicatep() #去除全部重複值
df.drop_duplicates(['name']) #去除name列的重複值
df.drop_duplicates(keep='last') # 預設保留重複項前面的值,而去除後面的值,‘last’則為保留最後一個

異常值的處理

當涉及 Pandas 應用數學與統計學領域中的異常值檢測時,有一些比較深入的方法,包括機率方法,矩陣分解,以及神經網路等。下面是一些具體示例展示:

  1. 機率方法 - 一元正態分佈檢測異常值:

    import pandas as pd
    import numpy as np
    
    # 建立一個示例資料集
    data = pd.DataFrame({'value': [1, 2, 3, 4, 5, 1000]})
    
    # 計算均值和標準差
    mean = data['value'].mean()
    std = data['value'].std()
    
    # 設定異常值閾值,例如均值加減3倍標準差
    threshold = 3 * std
    
    # 使用一元正態分佈方法檢測異常值
    data['is_outlier'] = np.abs(data['value'] - mean) > threshold
    print(data)
    
  2. 機率方法 - 多元高斯方法檢測異常值:

    from scipy import stats
    
    # 建立一個示例資料集
    data = pd.DataFrame({
        'feature1': [1, 2, 3, 4, 5],
        'feature2': [2, 4, 6, 8, 10]
    })
    
    # 計算多元高斯分佈機率密度
    multivariate_dist = stats.multivariate_normal(mean=data.mean(), cov=data.cov())
    
    # 設定異常值閾值
    threshold = 0.01  # 例如設定一個較小的閾值
    
    # 使用多元高斯方法檢測異常值
    data['is_outlier'] = multivariate_dist.pdf(data) < threshold
    print(data)
    
  3. 矩陣分解方法檢測異常值:

    from sklearn.decomposition import PCA
    
    # 建立一個示例資料集
    data = pd.DataFrame({
        'feature1': [1, 2, 3, 4, 5],
        'feature2': [2, 4, 6, 8, 100]
    })
    
    # 使用主成分分析(PCA)進行矩陣分解
    pca = PCA(n_components=2)
    pca.fit(data)
    
    # 計算重構誤差
    reconstruction_error = np.sum((data - pca.inverse_transform(pca.transform(data))) ** 2, axis=1)
    
    # 設定異常值閾值
    threshold = 20  # 例如設定一個閾值
    
    # 使用矩陣分解方法檢測異常值
    data['is_outlier'] = reconstruction_error > threshold
    print(data)
    
  4. 神經網路方法檢測異常值:

    from sklearn.neighbors import LocalOutlierFactor
    
    # 建立一個示例資料集
    data = pd.DataFrame({'value': [1, 2, 3, 4, 5, 1000]})
    
    # 使用區域性異常因子(Local Outlier Factor)進行異常值檢測
    lof = LocalOutlierFactor(n_neighbors=2, contamination=0.1)  # 設定引數
    data['is_outlier'] = lof.fit_predict(data[['value']])
    
    print(data)
    

資料集合並

這裡主要是用pandas中的常見方法進行資料集的連線合並。

pandas.DataFrame.concat()方法合併:
pandas.concat(
    objs,              # 接受一個列表或字典,表示要連線的 pandas 物件(Series 或 DataFrame)
    axis=0,            # 沿指定軸進行連線,0 表示沿第一個軸(行方向)連線,1 表示沿第二個軸(列方向)連線
    join='outer',      # 指定連線的方式,'outer'表示並集(union),'inner'表示交集(intersection)
    ignore_index=False,# 如果為 True,將忽略原始索引並生成一個新的整數索引
    keys=None,         # 建立層次化索引,用於識別每個片段
    levels=None,       # 指定多級索引的級別(通常自動推斷)
    names=None,        # 指定多級索引的級別名稱
    verify_integrity=False,  # 如果為 True,在連線操作之前驗證軸是否包含重複項
    sort=False         # 如果為 True,對非連線軸上的索引進行排序
)

以下是對各個引數的詳細解釋:

  • objs:要連線的 pandas 物件列表。可接受一個列表(或字典),其中包含要連線的 DataFrame 或 Series 物件。
  • axis:指定連線的軸方向。0 表示在行方向上連線,1 表示在列方向上連線。
  • join:指定連線的方式。預設為 'outer',表示取並集進行連線,也可以選擇 'inner',表示取交集進行連線。
  • ignore_index:如果為 True,將忽略原始索引並生成一個新的整數索引。
  • keys:當陣列沿著連線軸堆疊時,可以用 keys 引數建立一個層次化索引(MultiIndex),以便識別每個片段。
  • levels:指定鍵的層次化級別,通常不需要手動指定,會根據 keys 推斷。
  • names:指定多級索引的級別名稱。
  • verify_integrity:如果為 True,在連線操作之前驗證軸是否包含重複項,如果包含重複項則會丟擲 ValueError 異常。
  • sort:如果為 True,在連線操作之後對非連線軸上的索引進行排序。

pandas.DataFrame.merge() 方法合併
pandas.merge(
    left,                     # 左側的 DataFrame 物件
    right,                    # 右側的 DataFrame 物件
    how='inner',              # 合併方式,預設為 'inner',表示取兩個 DataFrame 的交集
    on=None,                  # 指定列名或索引級別作為合併的鍵,預設為 None,表示自動根據列名的交集進行合併
    left_on=None,             # 指定左側 DataFrame 中的列名或索引級別作為合併的鍵
    right_on=None,            # 指定右側 DataFrame 中的列名或索引級別作為合併的鍵
    left_index=False,         # 如果為 True,在左側 DataFrame 中使用索引作為合併鍵
    right_index=False,        # 如果為 True,在右側 DataFrame 中使用索引作為合併鍵
    sort=False,               # 如果為 True,根據合併鍵對結果進行排序
    suffixes=('_left', '_right'),  # 如果列名衝突,為列名新增字尾來區分,預設為 '_left' 和 '_right'
    copy=True                 # 如果為 True,在執行合併操作時複製資料
)

以下是對各個引數的詳細解釋:

  • left:左側的 DataFrame 物件。
  • right:右側的 DataFrame 物件。
  • how:指定合併的方式,預設為 'inner',即取兩個 DataFrame 的交集。還可以選擇 'outer',表示取兩個 DataFrame 的並集;'left',表示以左側 DataFrame 的鍵為基準進行合併;'right',表示以右側 DataFrame 的鍵為基準進行合併。
  • on:指定列名或索引級別作為合併的鍵。預設為 None,表示自動根據列名的交集進行合併。
  • left_on:指定左側 DataFrame 中的列名或索引級別作為合併的鍵。
  • right_on:指定右側 DataFrame 中的列名或索引級別作為合併的鍵。
  • left_index:如果為 True,在左側 DataFrame 中使用索引作為合併鍵。
  • right_index:如果為 True,在右側 DataFrame 中使用索引作為合併鍵。
  • sort:如果為 True,根據合併鍵對結果進行排序。
  • suffixes:如果列名衝突,為列名新增字尾來區分,預設為 ('_left', '_right')
  • copy:如果為 True,在執行合併操作時複製資料。
pandas.DataFrame.join() 方法:
DataFrame.join(
    other,                     # 合併的另一個 DataFrame 物件
    on=None,                   # 指定列名或索引級別作為合併的鍵,預設為 None,表示根據索引進行合併
    how='left',                # 合併方式,預設為 'left',表示以左側 DataFrame 為基準進行左外連線
    lsuffix='',                # 左側 DataFrame 列名相同時的字尾,預設為 '',不新增任何字尾
    rsuffix='',                # 右側 DataFrame 列名相同時的字尾,預設為 '',不新增任何字尾
    sort=False                 # 如果為 True,根據合併鍵對結果進行排序
)

以下是對各個引數的詳細解釋:

  • other:合併的另一個 DataFrame 物件。
  • on:指定列名或索引級別作為合併的鍵。預設為 None,表示根據索引進行合併。
  • how:指定合併的方式,預設為 'left',即以左側 DataFrame 為基準進行左外連線。還可以選擇 'inner',表示取兩個 DataFrame 的交集;'outer',表示取兩個 DataFrame 的並集;'right',表示以右側 DataFrame 為基準進行右外連線。
  • lsuffix:表示左側 DataFrame 中列名相同時的字尾,預設為 '',即不新增任何字尾。
  • rsuffix:表示右側 DataFrame 中列名相同時的字尾,預設為 '',即不新增任何字尾。
  • sort:如果為 True,根據合併鍵對結果進行排序。
資料對映 map()方法完成
df = pd.DataFrame({'name': ['amy', 'david', 'jam'], 'age': [14, 13, 12]})
name_to_gender = {'amy': 'girl', 'david': 'boy', 'jam': 'boy'}  # 建立對映字典
df['gender'] = df['name'].map(name_to_gender)

分組聚合

pandas.DataFrame.groupby()對資料集進行分組聚合

df = pd.DataFrame({'key1': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'key2': ['X', 'Y', 'X', 'Y', 'X', 'Y'],
                   'data': [1, 2, 3, 4, 5, 6]})
df.groupby(by='key1').sum()
df.groupby(by=['key1', 'key2']).mean() # 替換為count().mean()等其它函式


資料轉換

不僅是資料格式或型別的轉換,更多的是透過一些統計學方法對資料進行標準化或離散化處理。

特徵工程:需要去設計資料特徵,以幫助訓練得到效能更加優異的模型。

標準化 Normalization(無量綱化)是資料預處理中的常用手段。標準化的目的主要是消除不同特徵之間的量綱和取值範圍不同造成的差異。這些差異,不僅會造成資料偏重不均,還會在視覺化方面造成困擾

import numpy as np
import pandas as pd
%matplotlib inline

np.random.seed(10)  # 隨機數種子
df = pd.DataFrame({'A': np.random.random(
    20), 'B': np.random.random(20) * 10000})
print(df.plot())  # 繪圖

此時,B列資料太大,A列已經無法看出趨勢,近似一條直線.

Z-Score 標準化

Z-Score 標準化 是常用的標準化手段之一,其公式為:

\[z = \frac{x - \mu}{\sigma} \]

其中, 𝜇

為樣本資料的均值, 𝜎

為樣本資料的標準差。Z-Score 標準化之後的資料的均值為 0,方差為 1。

透過Z-Score 標準化,可以使不同資料集之間的資料具有可比性,同時可以減小異常值對資料分析和模型建立的影響。

# 使用Z-Score標準化對上面的DataFrame進行標準化處理並繪圖
df_z_score = (df - df.mean()) / df.std()  # Z-Score 標準化
df_z_score.plot()

Z-Score 標準化方法在 Scipy 中有一個對應的APIscipy.stats.zscore 可以使用

from scipy import stats
stats.zscore(df)

也可以將 DataFrame 處理從 NumPy 陣列再運算

(df.values - df.values.mean(axis=0)) / df.values.std(axis=0)  #  NumPy 陣列運算

除了 SciPyscikit-learn 也提供了 Z-Score 標準化API sklearn.preprocessing.StandardScaler()

Min-Max 標準化

該方法可以將資料轉化到一個特定的區間範圍內,通常是[0, 1]或者[-1, 1]之間。這種標準化方法適用於以下場景:

  1. 資料需要落入特定區間範圍內:例如神經網路的輸入層,很多情況下都要求輸入資料的範圍在[0, 1]或者[-1, 1]之間。

  2. 需要保留原始資料的相對性質:Min-Max 標準化會保持原始資料中不同數值之間的相對大小關係,不會改變資料的分佈形態。

  3. 異常值較少:Min-Max 標準化對異常值比較敏感,如果資料中存在較多的異常值,可能會導致標準化後的資料集不夠均衡。

公式為:

\[\hat x=\frac{x-x_{min}}{x_{max}-x_{min}} \]

其中, 𝑥𝑚𝑎𝑥為樣本資料的最大值, 𝑥𝑚𝑖𝑛為樣本資料的最小值, 𝑥𝑚𝑎𝑥−𝑥𝑚𝑖𝑛為極差。

# 使用Min-Max標準化對上面的DataFrame進行標準化處理
df_min_max = (df - df.min()) / (df.max() - df.min())  # Min-Max 標準化
df_min_max.plot()

同樣,scikit-learn 也提供了 Min-Max 標準化的 API sklearn.preprocessing.MinMaxScaler(),使用方法如下:

from sklearn.preprocessing import MinMaxScaler
MinMaxScaler().fit_transform(df)
獨熱編碼

在對資料的預處理過程中,我們會遇到有一些特徵列中的樣本並不是連續存在的,而是以分類形式存在的情況。例如,某一裝置的狀態有三種情況,分別為:正常、機械故障、電路故障。如果我們要將這些資料運用到後續的預測分析中,就需要對文字狀態進行轉換。一般情況下,可以用 0 表示正常,1 代表機械故障,2 代表電路故障。

所以,對於以分類形式存在的特徵變數,我們會採用一種叫 獨熱編碼 One-Hot Encoding 的方式將其轉換成二元特徵編碼,進一步對特徵進行了稀疏處理。獨熱編碼採用位狀態暫存器來對個狀態進行編碼,每個狀態都由它獨立的暫存器位,並且在任意時候只有一位有效。

# Pandas 中,可以使用 get_dummies 很方便地完成獨熱編碼。
df = pd.DataFrame({'fruits': ['apple', 'banana', 'pineapple']*2})  # 示例裝置狀態表
pd.get_dummies(df)  # 獨熱編碼
資料離散化

資料離散化有時候是為了演演算法實施需要,也有可能離散資料更適合資料的資訊表達。當我們對連續資料進行按區間離散化時,你可以透過編寫程式碼實現。不過,這裡介紹 Pandas 中一個非常方便的區間離散化方法 pd.cut(適用於等寬離散化)

# 將陣列等間距分割為3部分
pd.cut(np.array([1, 2, 7, 8, 5, 4, 12, 6, 3]), bins=3)  # bins 指定劃分數量

此時,如果我們按照 3 個區間對資料新增類別標籤 "small", "medium", "large",只需要指定 labels= 引數即可

pd.cut(np.array([1, 2, 7, 8, 5, 4, 12, 6, 3]),
       bins=3, labels=["small", "medium", "large"])

一般情況下,區間返回會以最大值為準,向最小值方向擴充套件 0.1% 以保證元素被有效分割。所以上面的區間不是以最小值 1 開始,而是 0.989。其中

\[1 - 0.989 = (12 - 1) * 0.1\% \]

當然,也可以自行指定劃分割槽間

pd.cut(np.array([1, 2, 7, 8, 5, 4, 12, 6, 3]), 
       bins=[0, 5, 10, 15], labels=["small", "medium", "large"])

資料規約

主成成分分析(Principal Components Analysis)

透過對協方差矩陣進行特徵分解,從而得出主成分(特徵向量)與對應的權值(特徵值)。然後剔除那些較小特徵值(較小權值)對應的特徵,從而達到降低資料維數的目的。

PCA最便捷方式透過scikit-learn完成

下面以常見的鳶尾花示例資料集作為示例:

# 載入鳶尾花例項資料集
import pandas as pd
from sklearn.datasets import load_iris
from matplotlib import pyplot as plt
%matplotlib inline

iris = load_iris()  # 載入原始資料
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)  # 處理為 DataFrame

資料集包含有4列,代表鳶尾花的4個特徵。若想將上面的資料繪製成資料點圖(平面散點圖),就無法實現,此時就需要對資料進行降維處理

介紹以下 方法中的幾個引數:

  • n_components= 表示需要保留主成分(特徵)的數量。
  • copy= 表示針對原始資料降維還是針對原始資料副本降維。當引數為 False 時,降維後的原始資料會發生改變,這裡預設為 True。
  • whiten= 白化表示將特徵之間的相關性降低,並使得每個特徵具有相同的方差。
  • svd_solver= 表示奇異值分解 SVD 的方法。有 4 引數,分別是:auto, full, arpack, randomized
# 續上段程式碼, 進行PCA
iris_pca = PCA(n_components=2).fit_transform(iris_df)  # PCA 降 2 維
iris_pca = pd.DataFrame(iris_pca, columns=['pca_x', 'pca_y'])  # 整理 DataFrame
iris_pca.plot.scatter(x='pca_x', y='pca_y')  # 繪製資料點
iris_pca.plot.scatter(x='pca_x', y='pca_y',
                      c=iris.target, cmap='plasma')  # 資料點著色

PCA在很多情況下是非常有用的, 上述是從視覺化角度進行,以下是常見使用:

  1. 降低資料維度:高維資料集會增加分析資料的複雜性,而PCA可以將高維資料投影到低維空間中,同時保留了大部分資料的變異性。這有助於簡化資料集、減少儲存空間和計算成本。

  2. 消除資料間的相關性:在許多資料集中,不同特徵之間存在相關性。PCA可以消除或減輕這些特徵之間的相關性,從而更好地反映資料之間的獨立性,減少重複資訊。

  3. 提高模型的效能:在一些機器學習任務中,高維資料可能會導致過擬合,而PCA可以有效減少資料的維度,幫助改善模型的泛化能力,並提高預測的準確性。

  4. 資料視覺化:透過PCA技術,可以將高維資料轉換為二維或三維,從而更好地將資料視覺化展現,以便更好地理解資料的結構和特徵。

  5. 去除噪聲:在某些資料集中可能會存在噪聲或不重要的資訊,而PCA可以幫助排除這些噪聲,提取出資料中最重要的訊號和特徵。

  6. 特徵提取:PCA可以幫助識別和提取主要的特徵,從而更好地描述資料的內在結構,有助於模式識別、資料探勘和預測任務。

線性判別分析

線性判別分析(Linear Discriminant Analysis,英文:LDA)同樣可以用於特徵降維。LDA 本來是一種分類模型,它試圖找到兩類物體或事件的特徵的一個線性組合,以便能夠特徵化或區分它們。

LDA 和 PCA 的本質相似,都會將原始的樣本對映到維度更低的樣本空間中。不過,PCA 對映後的樣本具有更大的發散性,而 LDA 對映後的樣本具有更明顯的類別區分

scikit-learn 同樣提供了可以用於 LDA 處理的 API:sklearn.discriminant_analysis.LinearDiscriminantAnalysis,使用方法如下:

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

iris_lda = LinearDiscriminantAnalysis(
    n_components=2).fit_transform(iris_df, iris.target)  # lda 降 2 維

iris_lda = pd.DataFrame(iris_lda, columns=['lda_x', 'lda_y'])  # 整理 DataFrame

透過繪製子圖來對比PCA和LDA兩種方法降維之後的資料分佈:

fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(12, 3))
iris_lda.plot.scatter(x='lda_x', y='lda_y', c=iris.target,
                        cmap='plasma', ax=axes[0])  # 資料點著色
iris_pca.plot.scatter(x='pca_x', y='pca_y', c=iris.target,
                        cmap='plasma', ax=axes[1])  # 資料點著色

可以從上圖看出二者的區別。PCA 和 LDA 是兩種不同的降維方法,沒有方法的好壞之說。一般情況下,PCA 會使用多一些,你可以看到 LDA 需要輸入目標值,而 PCA 則無需這一點

皮爾遜相關係數

在統計學中, 皮爾遜積矩相關係數(英語:Pearson product-moment correlation coefficient)用於度量兩個變數 𝑋 和 𝑌 之間的相關(線性相關),其值介於 -1 與 1 之間。在自然科學領域中,該係數廣泛用於度量兩個變數之間的相關程度。它是由卡爾·皮爾遜從弗朗西斯·高爾頓在 19 世紀 80 年代提出的一個相似卻又稍有不同的想法演變而來。這個相關係數也稱作「皮爾遜相關係數」。

兩個變數之間的皮爾遜相關係數定義為兩個變數之間的協方差和標準差的商:

\[{\displaystyle \rho _{X,Y}={\mathrm {cov} (X,Y) \over \sigma _{X}\sigma _{Y}}={E[(X-\mu _{X})(Y-\mu _{Y})] \over \sigma _{X}\sigma _{Y}}} \]

有了皮爾遜相關性係數,我們就可以評估不同特徵與目標值直接的相關性,從而剔除那些相關性弱的特徵,達到特徵壓縮的目的。接下來,我們使用 SciPy 提供的皮爾遜相關性係數計算方法 scipy.stats.pearsonr 來求解 iris 示例資料集各特徵與目標值之間的相關係數。

from scipy.stats import pearsonr

for i in range(4):
    p = pearsonr(iris_df.iloc[:, i], iris.target)[0]  # 求解每個特徵與目標值的相關性
    print("{}: {}".format(iris.feature_names[i], p))  # 輸出

上文說過,皮爾遜相關係數介於 -1 與 1 之間,越接近 1 則代表越正相關。所以,iris 示例資料集中與目標值更為相關的特徵是 sepal lengthpetal lengthpetal width

這裡再補充一種計算資料集特徵和目標之間皮爾遜相關性係數的方法,你可以直接在 DataFrame 後新增 corr() 屬性獲得。更為常用的是透過 Seaborn 視覺化工具繪製熱圖。

import seaborn as sns

# 得到特徵和目標拼合後的 DataFrame
iris_full_df = pd.concat([pd.DataFrame(iris.data, columns=iris.feature_names),
                            pd.DataFrame(iris.target, columns=['iris_target'])], axis=1)

sns.heatmap(iris_full_df.corr(), square=True, annot=True)  # corr() 函式計算皮爾遜相關係數

區別於手動計算各特徵和目標之間的相關係數,上方熱圖還計算了特徵之間的相關係數。觀察熱圖最後一行,不難發現與透過 scipy.stats.pearsonr 計算的結果一致。

卡方檢驗

在 1900 年,皮爾遜發表了著名的關於 卡方檢驗( Chi-Squared Test)的文章,該文章被認為是現代統計學的基石之一。簡單來講,實際觀測值與理論推斷值之間的偏離程度就決定卡方值的大小。若卡方值越小,代表偏差越小,越趨於符合

scikit-learn 提供了 可以返回 k 個最佳特徵,不過我們需要使用 來計算卡方值。接下來,同樣使用 iris 資料集來進行卡方檢驗。

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

kafang = SelectKBest(score_func=chi2, k=3)  # 保留 3 個最佳特徵,並使用卡方檢驗
kafang.fit_transform(iris_df, iris.target)

transformer 輸出了需要保留的最佳特徵,與 iris_df 對比後你會發現,它保留了與皮爾遜相關係數結果一致的 sepal lengthpetal lengthpetal width 特徵。你可以透過 scores_ 屬性輸出卡方值的計算結果。

kafang.scores_  # 各特徵與目標值之間的卡方值

這裡要補充一點是,實際使用中卡方檢驗和皮爾遜相關係數的評估結果並不會都像 IRIS 資料集上一致,往往會得到不同的結果。這其實也就反映出選取最好的特徵往往取決於評估指標,而沒有唯一答案。

上面我們介紹依據皮爾遜相關係數和卡方檢驗進行特徵選擇的方法在特徵工程中又被歸類於 Filter 過濾法,它主要側重於單個特徵跟目標變數的相關性。這種方法的優點在於計算速度快,且有較高的魯棒性。但是,Filter 方法的缺點在於不考慮特徵之間的相關性。因為,有可能某一個特徵自身不具備代表性,但是它和某些其它特徵組合起來會使得模型會得到不錯的效果。這一點就是 Filter 方法無法考慮到的了。

除此之外,從特徵工程的角度來講,還可以使用 Wrapper 封裝法和 Embeded 整合方法來完成特徵選擇。

資料抽樣

資料抽樣是透過減少樣本而非特徵的資料來達到資料規約的效果。資料抽樣透過從原始資料集中隨機採集樣本構成子集,從而降低資料規模。

最簡單的抽樣當然就是「隨機抽樣」了,我們可以生成一組隨機索引,然後從資料集中抽取到相應的資料。這裡,我們使用上方的 IRIS 資料集來完成。

import numpy as np

chosen_idx = np.random.choice(
    len(iris_df), replace=False, size=10)  # 從 IRIS 資料集中抽取 10 條資料
iris_df.iloc[chosen_idx]  # 抽取相應索引的資料

還可以直接使用 Pandas 提供的 pandas.DataFrame.sample 方法完成隨機抽樣過程,其中只需要指定抽樣數目即可。

iris_df.sample(n=10) 

注意:資料抽樣雖然在這裡被歸入資料規約,但實際上更多用於前面的環節中。一般情況下,會從清洗後的資料中抽樣,使用抽樣資料來完成特徵工程和建模,以探索方法實施的可能性。最終再使用完整資料集進行建模分析

相關文章