一 、缺失值的统计和删除
通过isna或isnull和sum的组合可以计算出每列缺失值的比例
df = pd.read_csv('../data/learn_pandas.csv', usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight', 'Transfer'])
df.isna().head()
df.isna().sum()/df.shape[0] # 查看缺失的比例
通过dropna函数对缺失值进行删除
res = df.dropna(how = 'any', subset = ['Height', 'Weight'])
res = df.dropna(axis=1, thresh=df.shape[0]-15) # 身高被删除
二、缺失值的填充和插值
使用fillna可以对缺失值进行填充,可以通过method参数指定填充方法,有前向填充可后向填充,分别指是用当前位置前面的值进行填充和使用当前位置后面的值进行填充。还可以通过limit参数指定连续缺失值的最大填充次数。也可以进行分组后在进行填充操作。
s.fillna(method='ffill') # 用前面的值向后填充
s.fillna(method='ffill', limit=1) # 连续出现的缺失,最多填充一次
s.fillna(s.mean()) # value为标量
s.fillna({'a': 100, 'd': 200}) # 通过索引映射填充的值
df.groupby('Grade')['Height'].transform(lambda x: x.fillna(x.mean())).head()
【练一练】
对一个序列以如下规则填充缺失值:如果单独出现的缺失值,就用前后均值填充,如果连续出现的缺失值就不填充,即序列[1, NaN, 3, NaN, NaN]
填充后为[1, 2, 3, NaN, NaN]
,请利用fillna
函数实现。(提示:利用`limit``参数)
s = pd.Series([1, np.nan, 3, np.nan, np.nan], index =[1,2,3,4,5])
s.fillna(s.mean(), limit=1)
interpolate函数主要有两个常用参数,一个是控制方向的limit_direction,一个是控制最大连续缺失值插值个数的limit
res = s.interpolate(limit_direction='backward', limit=1)
res = s.interpolate(limit_direction='both', limit=1)
# 最近邻插值,即缺失值的元素和离他最近的非缺失值元素一样
s.interpolate('nearest').values
s.interpolate() # 默认的线性插值,等价于计算中点的值
s.interpolate(method='index') # 和索引有关的线性插值,计算相应索引大小对应的值
三、Nullable类型
- 在python中,缺失值使用None表示,该元素除了与自己相等之外,和其他任何元素不相等。
- 在numpy中使用np.nan来表示缺失元素,该元素除了和其他元素不相等之外,和自己也不相等。
- 在对缺失序列进行元素比较时,nan的对应位置会返回False,但在使用equals函数进行两张表或序列的相同性检验时,会自动跳过两侧表都是缺失值的位置,并返回当前位置的比较结果为True
s1 = pd.Series([1, np.nan])
s2 = pd.Series([1, 2])
s3 = pd.Series([1, np.nan])
s1 == 1
- 在时间序列对象中,pandas使用pd.NaT来指代缺失值,其作用和nan一样。但不同的是,因为nan本身是一种浮点类型,在浮点类型和时间类型混合存储时,如不设计新的内置缺失类型来处理,Series的类型就会变成含糊不清的object
- 以下三个Nullable类型中存储的缺失值,都将会转化成pandas内置的pd.NA
pd.Series([np.nan, 1], dtype = 'Int64') # "i"是大写的
pd.Series([np.nan, True], dtype = 'boolean')
pd.Series([np.nan, 'my_str'], dtype = 'string')
当调用函数sum和prob使用加法和乘法的时候,缺失数据等价于0和1,不会改变原来的计算结果
四、 练习
Ex1:缺失值与类别的相关性检验
在数据处理中,含有过多缺失值的列往往会被删除,除非缺失情况与标签强相关。下面有一份关于二分类问题的数据集,其中X_1, X_2
为特征变量,y
为二分类标签。
事实上,有时缺失值出现或者不出现本身就是一种特征,并且在一些场合下可能与标签的正负是相关的。关于缺失出现与否和标签的正负性,在统计学中可以利用卡方检验来断言它们是否存在相关性。按照特征缺失的正例、特征缺失的负例、特征不缺失的正例、特征不缺失的负例,可以分为四种情况,设它们分别对应的样例数为。假若它们是不相关的,那么特征缺失中正例的理论值,就应该接近于特征缺失总数总体正例的比例,即:
其他的三种情况同理。现将实际值和理论值分别记作,那么希望下面的统计量越小越好,即代表实际值接近不相关情况的理论值:
可以证明上面的统计量近似服从自由度为的卡方分布,即。因此,可通过计算的概率来进行相关性的判别,一般认为当此概率小于时缺失情况与标签正负存在相关关系,即不相关条件下的理论值与实际值相差较大。
上面所说的概率即为统计学上关于列联表检验问题的值, 它可以通过scipy.stats.chi2(S, 1)
得到。请根据上面的材料,分别对X_1, X_2
列进行检验。
df = pd.read_csv('../data/missing_chi.csv')
cat_1 = df.X_1.fillna('NaN').mask(df.X_1.notna()).fillna("NotNaN")
cat_2 = df.X_2.fillna('NaN').mask(df.X_2.notna()).fillna("NotNaN")
df_1 = pd.crosstab(cat_1, df.y, margins=True)
df_2 = pd.crosstab(cat_2, df.y, margins=True)
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)
from scipy.stats import chi2
chi2.sf(res1, 1) # X_1检验的p值 # 不能认为相关,剔除
chi2.sf(res2, 1) # 认为相关
Ex2:用回归模型解决分类问题
KNN
是一种监督式学习模型,既可以解决回归问题,又可以解决分类问题。对于分类变量,利用KNN
分类模型可以实现其缺失值的插补,思路是度量缺失样本的特征与所有其他样本特征的距离,当给定了模型参数n_neighbors=n
时,计算离该样本距离最近的个样本点中最多的那个类别,并把这个类别作为该样本的缺失预测类别,具体如下图所示,未知的类别被预测为黄色:
上面有色点的特征数据提供如下:
已知待预测的样本点为,那么预测类别可以如下写出:
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=6)
clf.fit(df.iloc[:,:2], df.Color)
clf.predict([[0.8, -0.2]])
- 对于回归问题而言,需要得到的是一个具体的数值,因此预测值由最近的个样本对应的平均值获得。请把上面的这个分类问题转化为回归问题,仅使用
KNeighborsRegressor
来完成上述的KNeighborsClassifier
功能。 - 请根据第1问中的方法,对
audit
数据集中的Employment
变量进行缺失值插补。
color2int = {'Blue':1, 'Yellow':2, 'Green':3}
int2color = {1:'Blue', 2:'Yellow', 3:'Green'}
# 将str转换成int
df['Color'] = df['Color'].apply(lambda x: color2int[x])
# 仅使用KNeighborsRegressor完成
from sklearn.neighbors import KNeighborsRegressor
clf = KNeighborsRegressor(n_neighbors=6)
clf.fit(df.iloc[:,:2], df.Color)
# 预测均值,并通过四舍五入的方式确定值
int2color[np.around(clf.predict([[0.8, -0.2]])).astype(np.int)[0]]
res_df = df.copy()
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.query('Employment.notna()')
X_test = df.query('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))
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()