【12月DW打卡】joyful-pandas - 07 - pandas缺失資料 (缺失值的統計刪除、填充插值、KNN的簡單使用) + 腦圖大綱
缺失資料
腦圖大綱
小結
- 原文指路:(joyful-pandas)[https://datawhalechina.github.io/joyful-pandas/build/html/目錄/ch7.html#id6]
- jupyter nbconvert --to markdown E:\PycharmProjects\TianChiProject\00_山楓葉紛飛\competitions\008_joyful-pandas\07_pandas缺失值.ipynb
import numpy as np
import pandas as pd
【7.2.1 練一練】
對一個序列以如下規則填充缺失值:如果單獨出現的缺失值,就用前後均值填充,如果連續出現的缺失值就不填充,即序列[1, NaN, 3, NaN, NaN]填充後為[1, 2, 3, NaN, NaN],請利用 fillna 函式實現。(提示:利用 limit 引數)
s = pd.Series([1, np.nan, 3, np.nan, np.nan],
list('abcde'))
# s.fillna(method='both', limit=1) 不支援
ValueError: Invalid fill method. Expecting pad (ffill) or backfill (bfill). Got both
(s.fillna(method='ffill', limit=1) + s.fillna(method='bfill', limit=1))/2
a 1.0 b 2.0 c 3.0 d NaN e NaN dtype: float64
# 類似的interpolate方法,向後也是不可以的!
s.interpolate(limit_direction='both', limit=1)
a 1.0
b 2.0
c 3.0
d 3.0
e NaN
dtype: float64
Ex1:缺失值與類別的相關性檢驗
在資料處理中,含有過多缺失值的列往往會被刪除,除非缺失情況與標籤強相關。下面有一份關於二分類問題的資料集,其中 X_1, X_2 為特徵變數, y 為二分類標籤。
df = pd.read_csv('E:\\PycharmProjects\\DatawhaleChina\\joyful-pandas\\data\\missing_chi.csv')
df.head()
X_1 | X_2 | y | |
---|---|---|---|
0 | NaN | NaN | 0 |
1 | NaN | NaN | 0 |
2 | NaN | NaN | 0 |
3 | 43.0 | NaN | 0 |
4 | NaN | NaN | 0 |
df.isna().mean()
X_1 0.855
X_2 0.894
y 0.000
dtype: float64
df.y.value_counts(normalize=True)
0 918
1 82
Name: y, dtype: int64
事實上,有時缺失值出現或者不出現本身就是一種特徵,並且在一些場合下可能與標籤的正負是相關的。關於缺失出現與否和標籤的正負性,在統計學中可以利用卡方檢驗來斷言它們是否存在相關性。按照特徵缺失的正例、特徵缺失的負例、特徵不缺失的正例、特徵不缺失的負例,可以分為四種情況,設它們分別對應的樣例數為\(n_{11}, n_{10}, n_{01}, n_{00}\)。假若它們是不相關的,那麼特徵缺失中正例的理論值,就應該接近於特徵缺失總數\(\times\)總體正例的比例,即:
\[E_{11} = n_{11} \approx (n_{11}+n_{10})\times\frac{n_{11}+n_{01}}{n_{11}+n_{10}+n_{01}+n_{00}} = F_{11} \]其他的三種情況同理。現將實際值和理論值分別記作\(E_{ij}, F_{ij}\),那麼希望下面的統計量越小越好,即代表實際值接近不相關情況的理論值:
\[S = \sum_{i\in \{0,1\}}\sum_{j\in \{0,1\}} \frac{(E_{ij}-F_{ij})^2}{F_{ij}} \]可以證明上面的統計量近似服從自由度為\(1\)的卡方分佈,即\(S\overset{\cdot}{\sim} \chi^2(1)\)。因此,可通過計算\(P(\chi^2(1)>S)\)的概率來進行相關性的判別,一般認為當此概率小於\(0.05\)時缺失情況與標籤正負存在相關關係,即不相關條件下的理論值與實際值相差較大。
上面所說的概率即為統計學上關於\(2\times2\)列聯表檢驗問題的\(p\)值, 它可以通過scipy.stats.chi2(S, 1)
得到。請根據上面的材料,分別對X_1, X_2
列進行檢驗。
"""
2×2 列聯表檢驗問題的 p 值:
特徵缺失的正例 1
特徵缺失的負例 0
特徵不缺失的正例 1
特徵不缺失的負例 0
"""
df = pd.read_csv('E:\\PycharmProjects\\DatawhaleChina\\joyful-pandas\\data\\missing_chi.csv')
# where 和 mask ,這兩個函式是完全對稱的: where 函式在傳入條件為 False 的對應行進行替換,而 mask 在傳入條件為 True 的對應行進行替換,當不指定替換值時,替換為缺失值。"
cat_1 = df.X_1.fillna('NaN').mask(df.X_1.notna(), "NotNaN")
cat_2 = df.X_2.fillna('NaN').mask(df.X_2.notna(), "NotNaN")
# pd.crosstab計算兩個(或多個)因子的簡單交叉表。預設情況下,除非傳遞值陣列和聚合函式,否則將計算因子的頻率表。
df_1 = pd.crosstab(cat_1, df.y, margins=True)
df_2 = pd.crosstab(cat_2, df.y, margins=True)
# 計算大S值
def compute_S(my_df):
S = []
for i in range(2):
for j in range(2):
E = my_df.iat[i, j]
F = my_df.iat[i, 2]*my_df.iat[2, j]/my_df.iat[2,2]
S.append((E-F)**2/F)
return sum(S)
# 分別計算
res1 = compute_S(df_1)
res2 = compute_S(df_2)
0.9712760884395901
from scipy.stats import chi2
chi2.sf(res1, 1) # X_1檢驗的p值 # 不能認為相關,剔除
0.9712760884395901
chi2.sf(res2, 1) # X_2檢驗的p值 # 認為相關,保留(為當此概率小於0.05即符合條件)
7.459641265637543e-166
結果與scipy.stats.chi2_contingency
在不使用\(Yates\)修正的情況下完全一致:
# 列聯表中變數獨立性的卡方檢驗。
from scipy.stats import chi2_contingency
chi2_contingency(pd.crosstab(cat_1, df.y), correction=False)[1]
0.9712760884395901
chi2_contingency(pd.crosstab(cat_2, df.y), correction=False)[1]
7.459641265637543e-166
Ex2:用迴歸模型解決分類問題
KNN
是一種監督式學習模型,既可以解決迴歸問題,又可以解決分類問題。對於分類變數,利用KNN
分類模型可以實現其缺失值的插補,思路是度量缺失樣本的特徵與所有其他樣本特徵的距離,當給定了模型引數n_neighbors=n
時,計算離該樣本距離最近的\(n\)個樣本點中最多的那個類別,並把這個類別作為該樣本的缺失預測類別,具體如下圖所示,未知的類別被預測為黃色:
上面有色點的特徵資料提供如下:
df = pd.read_excel('E:\\PycharmProjects\\DatawhaleChina\\joyful-pandas\\data\\color.xlsx')
df.head(3)
X1 | X2 | Color | |
---|---|---|---|
0 | -2.5 | 2.8 | Blue |
1 | -1.5 | 1.8 | Blue |
2 | -0.8 | 2.8 | Blue |
已知待預測的樣本點為\(X_1=0.8, X_2=-0.2\),那麼預測類別可以如下寫出:
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=6)
clf.fit(df.iloc[:,:2], df.Color)
clf.predict([[0.8, -0.2]])
array(['Yellow'], dtype=object)
- 對於迴歸問題而言,需要得到的是一個具體的數值,因此預測值由最近的\(n\)個樣本對應的平均值獲得。請把上面的這個分類問題轉化為迴歸問題,僅使用
KNeighborsRegressor
來完成上述的KNeighborsClassifier
功能。
翻到原始碼的Examples
--------
>>> X = [[0], [1], [2], [3]]
>>> y = [0, 0, 1, 1]
>>> from sklearn.neighbors import KNeighborsRegressor
>>> neigh = KNeighborsRegressor(n_neighbors=2)
>>> neigh.fit(X, y)
KNeighborsRegressor(...)
>>> print(neigh.predict([[1.5]]))
[0.5]
照葫蘆畫瓢~~~
df_dummies = pd.get_dummies(df.Color)
df_dummies.head()
from sklearn.neighbors import KNeighborsRegressor
X = df[['X1', 'X2']]
# y = [0, 0, 1, 1]
stack_list = []
for col in df_dummies.columns:
print(col)
neigh = KNeighborsRegressor(n_neighbors=6)
neigh.fit(X, df_dummies[col])
# 轉為單列資料
res = neigh.predict([[0.8, -0.2]]).reshape(-1,1)
print(neigh.predict([[0.8, -0.2]]))
stack_list.append(res)
stack_list
Blue
[0.16666667]
Green
[0.33333333]
Yellow
[0.5]
[array([[0.16666667]]), array([[0.33333333]]), array([[0.5]])]
由上可知: 預測成藍/綠/黃三個顏色的概率分別是0.16666667,33.3%和50%。
取最大的概率作為最終預測結果,該位置最終預測為黃色。
# np.hstack : 水平(按列)順序堆疊陣列。 [這裡返回一個單列陣列]
# argmax : Return indices of the maximum values along the given axis.
color_res = pd.Series(np.hstack(stack_list).argmax(axis=1))
df_dummies.columns[color_res[0]]
# 輸出為'Yellow'
'Yellow'
- 請根據第1問中的方法,對
audit
資料集中的Employment
變數進行缺失值插補。
df = pd.read_csv('E:\\PycharmProjects\\DatawhaleChina\\joyful-pandas\\data\\audit.csv')
df.head(3)
from sklearn.neighbors import KNeighborsRegressor
# df = pd.read_csv('data/audit.csv')
res_df = df.copy()
#將婚姻狀況、性別變成one-hot向量,和年齡、收入、時間、僱用拼接在一起
# 年齡、收入、時間這三個屬性還做了標準化處理
df = pd.concat([pd.get_dummies(df[['Marital', 'Gender']]),
df[['Age','Income','Hours']].apply(
lambda x:(x-x.min())/(x.max()-x.min())), df.Employment],1)
X_train = df[df.Employment.notna()]
X_test = df[df.Employment.isna()]
df_dummies = pd.get_dummies(X_train.Employment)
stack_list = []
for col in df_dummies.columns:
clf = KNeighborsRegressor(n_neighbors=6)
clf.fit(X_train.iloc[:,:-1], df_dummies[col])
res = clf.predict(X_test.iloc[:,:-1]).reshape(-1,1)
stack_list.append(res)
code_res = pd.Series(np.hstack(stack_list).argmax(1))
code_res
0 2
1 0
2 4
3 4
4 4
..
95 4
96 4
97 4
98 4
99 4
Length: 100, dtype: int64
cat_res = code_res.replace(
dict(
zip(list(range(df_dummies.shape[0])), df_dummies.columns))
)
res_df.loc[res_df.Employment.isna(), 'Employment'] = cat_res.values
res_df.isna().sum()
ID 0
Age 0
Employment 0
Marital 0
Income 0
Gender 0
Hours 0
dtype: int64