参考:https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch7.html
一、缺失值的统计和删除
1. 缺失信息的统计 isna 或 isnull
| 函数 | 例子 | 备注 |
|---|---|---|
| isna或isnull | df.isna().mean() # 查看缺失的比例 |
isna 或 isnull (两个函数没有区别) |
isna或者notna |
df[df.Height.isna()] | 应用于series,只能统计一列的缺失信息 |
isna,notna 和 any, all 的组合 |
sub_set = df[['Height', 'Weight', 'Transfer']]<br />df[sub_set.isna().all(1)]# 全部缺失<br />df[sub_set.isna().any(1)].head() # 至少有一个缺失<br />df[sub_set.notna().all(1)].head() # 没有缺失 | 同时对几个列,检索出全部为缺失或者至少有一个缺失或者没有缺失的行 |
2. 缺失信息的删除
| 函数 | 参数 | 例子 | 备注 |
|---|---|---|---|
| dropna | axis(默认为0,即删除行) | ||
how(删除方式)主要有 any 和 all 两种参数可以选择 |
df.dropna(how = 'any', subset = ['Height', 'Weight']) | 删除身高体重至少有一个缺失的行 | |
| thresh(删除的非缺失值个数阈值) | res = df.dropna(1, thresh=df.shape[0]-15) | 删除超过15个缺失值的列 | |
| subset(备选的删除子集) | res = df.loc[df[['Height', 'Weight']].notna().all(1)]<br />res = df.loc[:, ~(df.isna().sum()>15)] | 上述的两个操作,也可以使用布尔索引来完成 |
二、缺失值的填充和插值
1. 利用fillna进行填充
| 函数 | 参数 | 例子 |
|---|---|---|
| fillna | method:填充方法(ffill: 用缺失值前面的元素填充;bfill:用后面的元素填充) | s.fillna(method='ffill') # 用前面的值向后填充 |
| limit:连续缺失值的最大填充次数 | s.fillna(method='ffill', limit=1) # 连续出现的缺失,最多填充一次 | |
| value:填充值,可以是标量,也可以是索引到元素的字典映射 | s.fillna(s.mean()) # value为标量<br />s.fillna({'a': 100, 'd': 200}) # 通过索引映射填充的值 |
练一练
对一个序列以如下规则填充缺失值:如果单独出现的缺失值,就用前后均值填充,如果连续出现的缺失值就不填充,即序列[1, NaN, 3, NaN, NaN]填充后为[1, 2, 3, NaN, NaN],请利用
fillna函数实现。(提示:利用limit参数)
s = pd.Series([1,np.nan,3, np.nan, np.nan])
s1 = s.fillna(method='ffill',limit=1)
s2 = s.fillna(method='bfill',limit=1)
s = pd.Series(list(map(lambda x,y: (x+y)/2 if not np.isnan(x) and not np.isnan(y) else np.nan, s1,s2)))
s
'''
0 1.0
1 2.0
2 3.0
3 NaN
4 NaN
'''
2. 插值函数
这里只讨论比较常用且简单的三类情况,即线性插值、最近邻插值和索引插值。
| 函数 | 插值类型 | 参数 | 例子 | 备注 |
|---|---|---|---|---|
| interpolate | 线性插值 | limit_direction:控制方向,默认为 forward,还有backward或both(双向限制插值) |
s.interpolate(limit_direction='backward', limit=1) | |
| limit:控制最大连续缺失值插值个数 | ||||
| 最近邻插补 | method | s.interpolate('nearest').values | 缺失值的元素和离它最近的非缺失值元素一样 | |
| 索引插值 | s.interpolate(method='index') | 根据索引大小进行线性插值 |
三、Nullable类型
1. 缺失记号及其缺陷
| 缺失记号 | 说明 |
|---|---|
| None | 在 python 中的缺失值用 None 表示,该元素除了等于自己本身之外,与其他任何元素不相等 |
| np.nan | 和所有元素都不相等,包括自己;但在两个序列使用equals进行比较时,会跳过nan来比较 |
| pd.NaT | 时间序列对象的缺失值,作用和np.nan一样 |
np.nan的缺陷:np.nan属于浮点类型,和任何其他类型一列,这一列就会变成其他类型:
- 和整数一列:整列变成float类型
- 和bool一列:整列变成object类型
- 和datetime一列:整列变成object类型
所以出现了pd.NaT。1.0.0版本后,pandas设计了新的缺失类型pd.NA以及3种Nullable序列类型。
2. Nullable类型的性质
序列类型不受nan影响,在声明序列时指定序列的类型为Int, boolean 和 string
- pd.Series([np.nan, 1], dtype = 'Int64') # "i"是大写的
- pd.Series([np.nan, True], dtype = 'boolean')
- pd.Series([np.nan, 'my_str'], dtype = 'string')
| 类型 | 计算 | NA类型返回 | 序列类型 |
|---|---|---|---|
| Int | pd.Series([np.nan, 0], dtype = 'Int64') + 1 | NA | Int64 |
| pd.Series([np.nan, 0], dtype = 'Int64') == 0 | NA | boolean | |
| pd.Series([np.nan, 0], dtype = 'Int64') * 0.5 | NA | Float64 |
boolean和bool的区别:
s = pd.Series(['a', 'b'])
s_bool = pd.Series([True, np.nan])
s_boolean = pd.Series([True, np.nan]).astype('boolean')
| bool | bool结果 | boolean | boolean结果 |
|---|---|---|---|
| s[s_bool] | 报错; nan不能作为索引器的选择 | s[s_boolean] | 0 a <br />dtype: object 把nan当成False |
| s_bool & True | bool类型在缺失处返回的永远是False<br />0 True <br />1 False |
s_boolean & True |
boolean 会根据逻辑运算是否能确定唯一结果来返回相应的值<br />0 True <br />1 <NA> |
| s_bool | True | 0 True <br />1 False | s_boolean | True | 0 True <br />1 True |
| ~s_bool | 0 False <br />1 False | ~s_boolean | 0 False <br />1 <NA> |
一般在实际数据处理时,可以在数据集读入后,先通过 convert_dtypes 转为 Nullable 类型:
df = df.convert_dtypes()
3. 缺失数据的计算和分组
sum, prob 使用加法和乘法的时候,缺失数据等价于被分别视作0和1,即不改变原来的计算结果。当使用累计函数时,会自动跳过缺失值所处的位置。
单个标量运算:
| 运算 | 结果 |
|---|---|
| np.nan ** 0 | 1.0 |
| 1 ** np.nan | 1.0 |
| pd.NA ** 0 | 1 |
| 1 ** pd.NA | 1 |
| 其他所有涉及np.nan的运算 | False |
| 其他所有涉及pd.NA的运算 | NA |
对于一些函数而言,缺失可以作为一个类别处理,例如在 groupby, get_dummies 中可以设置相应的参数来进行增加缺失类别:
- df_nan.groupby('category', dropna=False)['value'].mean() # pandas版本大于1.1.0
- pd.get_dummies(df_nan.category, dummy_na=True)
四、练习
Ex1:缺失值与类别的相关性检验
统计学的概念不太记得了,题目不太读得懂。。。Orz
答案:
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) # X_2检验的p值 # 认为相关,保留
Ex2:用回归模型解决分类问题
对KNN的类不太了解,题目好难。。。Orz
- 对于回归问题而言,需要得到的是一个具体的数值,因此预测值由最近的 n 个样本对应的平均值获得。请把上面的这个分类问题转化为回归问题,仅使用
KNeighborsRegressor来完成上述的KNeighborsClassifier功能。
答案:
from sklearn.neighbors import KNeighborsRegressor
df = pd.read_excel('../data/color.xlsx')
df_dummies = pd.get_dummies(df.Color)
stack_list = []
for col in df_dummies.columns:
clf = KNeighborsRegressor(n_neighbors=6)
clf.fit(df.iloc[:,:2], df_dummies[col])
res = clf.predict([[0.8, -0.2]]).reshape(-1,1)
stack_list.append(res)
code_res = pd.Series(np.hstack(stack_list).argmax(1))
df_dummies.columns[code_res[0]]