#从清华镜像拉装1.0.5版本的Pandas
!pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas==1.0.5
import pandas as pd
import numpy as np
df = pd.read_csv('data/table_missing.csv')
print(df.head())
可以看到这个数据表里有多列、多行都有缺失数据的情况
School Class ID Gender Address Height Weight Math Physics
0 S_1 C_1 NaN M street_1 173 NaN 34.0 A+
1 S_1 C_1 NaN F street_2 192 NaN 32.5 B+
2 S_1 C_1 1103.0 M street_2 186 NaN 87.2 B+
3 S_1 NaN NaN F street_2 167 81.0 80.4 NaN
4 S_1 C_1 1105.0 NaN street_4 159 64.0 84.8 A-
对Series使用isna和notna方法可以返回是否缺失的布尔列表,对DataFrame使用可以返回是否缺失的返回布尔表
print(df['Physics'].isna().head())
print(df['Physics'].notna().head())
print(df.isna().head())
一般情况下,拿到手的数据还是要先整体观测是否有缺失情况
print(df.isna().sum())
print(df.info())
School 0
Class 4
ID 6
Gender 7
Address 0
Height 0
Weight 13
Math 5
Physics 4
dtype: int64
RangeIndex: 35 entries, 0 to 34
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 School 35 non-null object
1 Class 31 non-null object
2 ID 29 non-null float64
3 Gender 28 non-null object
4 Address 35 non-null object
5 Height 35 non-null int64
6 Weight 22 non-null float64
7 Math 30 non-null float64
8 Physics 31 non-null object
dtypes: float64(3), int64(1), object(5)
memory usage: 2.6+ KB
None
挑选出所有无缺失值的行:使用all就是全部非缺失值df[df.notna().all(1)]
,如果是any就是至少有一个不是缺失值df[df.notna().any(1)]
。
查看有缺失值的行:以Physics
列为例,挑出Physics
列缺失值的行df[df['Physics'].isna()]
,挑出Physics
列不缺失值的行df[df['Physics'].notna()]
。
np.nan是常见的缺失数据表示,它不等于0也不等于None,甚至不等于自己。不过在用equals函数比较时,df.equals(df)
equals函数会自动略过两侧全是np.nan的单元格,因此结果不会影响True
。
print(np.nan == np.nan,np.nan == 0,np.nan == None)
False False False
np.nan在numpy中的类型为浮点,由此导致数据集读入时,即使原来是整数的列,只要有缺失值就会变为浮点型。在所有的表格读取后,无论列是存放什么类型的数据,默认的缺失值全为np.nan类型,故而整型列转为浮点;而字符由于无法转化为浮点,因此只能归并为object类型(‘O’);原来是浮点型的则类型不变。
print(type(np.nan),pd.Series([1,2,3]).dtype,pd.Series([1,np.nan,3]).dtype)
print(df['ID'].dtype,df['Math'].dtype,df['Class'].dtype)
int64 float64
float64 float64 object
None会等于自身,其布尔值为False。在传入数值类型后,会自动变为np.nan。只有当传入object类型是保持不动,几乎可以认为,除非人工命名None,它基本不会自动出现在Pandas中。在使用equals函数时不会被略过,因此pd.Series([None]).equals(pd.Series([np.nan]))
情况下返回False
。
print(None == None,pd.Series([None],dtype='bool'),type(pd.Series([1,None])[1]),type(pd.Series([1,None],dtype='object')[1]))
True 0 False
dtype: bool
NaT是针对时间序列的缺失值,是Pandas的内置类型,可以完全看做时序版本的np.nan,与自己不等,且使用equals是也会被跳过。
s_time = pd.Series([pd.Timestamp('20120101')]*5)
print(s_time)
s_time[2] = None
s_time[3] = np.nan
s_time[4] = pd.NaT
print('\n',s_time,type(s_time[2]),type(s_time[3]),type(s_time[4]))
print(s_time[2] == s_time[2],s_time.equals(s_time))
0 2012-01-01
1 2012-01-01
2 2012-01-01
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
0 2012-01-01
1 2012-01-01
2 NaT
3 NaT
4 NaT
dtype: datetime64[ns]
False True
这是Pandas在1.0新版本中引入的重大改变,其目的就是为了(在若干版本后)解决之前出现的混乱局面,统一缺失值处理方法
“The goal of pd.NA is provide a “missing” indicator that can be used consistently across data types (instead of np.nan, None or pd.NaT depending on the data type).”——User Guide for Pandas v-1.0
官方鼓励用户使用新的数据类型和缺失类型pd.NA
Nullable整形与原来标记int上的符号区别在于首字母大写:‘Int’。它的好处就在于,前面提到的三种缺失值np.nan、None、NaT都会被替换为统一的NA符号,且不改变数据类型。
s_original = pd.Series([1, 2, 3, 4], dtype="int64")
s_new = pd.Series([1, 2, 3, 4], dtype="Int64")
s_original[1] = np.nan
print(s_original)
s_new[1] = np.nan
s_new[2] = None
s_new[3] = pd.NaT
print(s_new)
0 1.0
1 NaN
2 3.0
3 4.0
dtype: float64
0 1
1
2
3
dtype: Int64
Nullable布尔作用与上面的类似,记号为boolean。需要注意的是,含有pd.NA的布尔列表在1.0.2之前的版本作为索引时会报错,这是一个之前的bug,现已经修复。
s_original = pd.Series([1, 1, 0, 1], dtype="bool")
s_new = pd.Series([1, 0, 0, 1], dtype="boolean")
s_original[1] = None
print(s_original)
s_new[1] = np.nan
s_new[2] = None
s_new[3] = pd.NaT
print(s_new)
0 True
1 False
2 False
3 True
dtype: bool
0 True
1
2
3
dtype: boolean
s = pd.Series(['dog','cat','fish','mouse'])
s[s_new]
0 dog
dtype: object
string类型是1.0的一大创新,目的之一就是为了区分开原本含糊不清的object类型,这里将简要地提及string,因为它是第7章的主题内容它本质上也属于Nullable类型,因为并不会因为含有缺失而改变类型。此外,和object类型的一点重要区别就在于,在调用字符方法后,string类型返回的是Nullable类型,object则会根据缺失类型和数据类型而改变。
s = pd.Series(['dog','cat','fish','mouse'],dtype='string')
s[s_new]
0 dog
dtype: string
s = pd.Series(["a", None, "b"], dtype="string")
print(s.str.count('a'))
print(s.str.isdigit())
0 1
1
2 0
dtype: Int64
0 False
1
2 False
dtype: boolean
s2 = pd.Series(["a", None, "b"], dtype="object")
print(s2.str.count("a"))
print(s2.str.isdigit())
0 1.0
1 NaN
2 0.0
dtype: float64
0 False
1 None
2 False
dtype: object
NA参与逻辑运算时只需看该逻辑运算的结果是否依赖pd.NA的取值,如果依赖,则结果还是NA,如果不依赖,则直接计算结果。
print(True | pd.NA, False | pd.NA, False & pd.NA, True & pd.NA)
True False
NA参与算术运算和比较运算时只需记住除了两类情况,其他结果都是NA。
print(pd.NA ** 0, 1 ** pd.NA)
print(pd.NA + 1, "a" * pd.NA, pd.NA == pd.NA, pd.NA < 2.5, np.log(pd.NA), np.add(pd.NA, 1))
1 1
convert_dtypes函数的功能往往就是在读取数据时,就把数据列转为Nullable类型,是1.0的新函数。
print(pd.read_csv('data/table_missing.csv').dtypes)
print(pd.read_csv('data/table_missing.csv').convert_dtypes().dtypes)
School object
Class object
ID float64
Gender object
Address object
Height int64
Weight float64
Math float64
Physics object
dtype: object
School string
Class string
ID Int64
Gender string
Address string
Height Int64
Weight Int64
Math float64
Physics string
dtype: object
使用加法时,缺失值为0;使用乘法时,缺失值为1;使用累计函数时,缺失值自动略过。
s = pd.Series([2,3,np.nan,4])
print(s.sum(),s.prod(),s.cumsum(),s.cumprod(),s.pct_change())
9.0 24.0 0 2.0
1 5.0
2 NaN
3 9.0
dtype: float64 0 2.0
1 6.0
2 NaN
3 24.0
dtype: float64 0 NaN
1 0.500000
2 0.000000
3 0.333333
dtype: float64
groupby方法中自动忽略为缺失值的组。
df_g = pd.DataFrame({'one':['A','B','C','D',np.nan],'two':np.random.randn(5)})
print(df_g,'\n',df_g.groupby('one').groups)
one two
0 A -0.180930
1 B -0.454848
2 C 1.324243
3 D 0.994247
4 NaN 1.246457
{'A': Int64Index([0], dtype='int64'), 'B': Int64Index([1], dtype='int64'), 'C': Int64Index([2], dtype='int64'), 'D': Int64Index([3], dtype='int64')}
fillna方直接进行值填充,也可以分别使用ffill方法和bfill方法前后向填充。
print(df['Physics'].fillna('missing').head())
print(df['Physics'].fillna(method='ffill').head())
print(df['Physics'].fillna(method='backfill').head())
0 A+
1 B+
2 B+
3 missing
4 A-
Name: Physics, dtype: object
0 A+
1 B+
2 B+
3 B+
4 A-
Name: Physics, dtype: object
0 A+
1 B+
2 B+
3 A-
4 A-
Name: Physics, dtype: object
填充中的对齐特性具体例子是返回的结果中没有C,根据对齐特点不会被填充。
df_f = pd.DataFrame({'A':[1,3,np.nan],'B':[2,4,np.nan],'C':[3,5,np.nan]})
print(df_f.fillna(df_f.mean()))
print(df_f.fillna(df_f.mean()[['A','B']]))
A B C
0 1.0 2.0 3.0
1 3.0 4.0 5.0
2 2.0 3.0 4.0
A B C
0 1.0 2.0 3.0
1 3.0 4.0 5.0
2 2.0 3.0 NaN
dropna方法中axis参数选择行(0)列(1),how参数(可以选all或者any,表示去除全为缺失的和去除存在缺失的),subset参数(即在某一组列范围中搜索缺失值)。
df_d = pd.DataFrame({'A':[np.nan,np.nan,np.nan],'B':[np.nan,3,2],'C':[3,2,1]})
print(df_d.dropna(axis=0))
print('\n',df_d.dropna(axis=1))
print(df_d.dropna(axis=1,how='all'))
print(df_d.dropna(axis=0,subset=['B','C']))
Empty DataFrame
Columns: [A, B, C]
Index: []
C
0 3
1 2
2 1
B C
0 NaN 3
1 3.0 2
2 2.0 1
A B C
1 NaN 3.0 2
2 NaN 2.0 1
默认状态下,interpolate会对缺失的值进行线性插值(与索引无关)。
s = pd.Series([1,10,15,-5,-2,np.nan,np.nan,28])
print(s.interpolate())
print(s.interpolate().plot())
0 1.0
1 10.0
2 15.0
3 -5.0
4 -2.0
5 8.0
6 18.0
7 28.0
dtype: float64
method中的index和time选项可以使插值线性地依赖索引,即插值为索引的线性函数。比如s.interpolate(method='index')
。如果索引是时间,那么可以(如下)按照时间长短插值.
s_t = pd.Series([0,np.nan,10],index=[pd.Timestamp('2012-05-01'),pd.Timestamp('2012-05-07'),pd.Timestamp('2012-06-03')])
print(s_t)
print(s_t.interpolate())
print(s_t.interpolate(method='time'))
2012-05-01 0.0
2012-05-07 NaN
2012-06-03 10.0
dtype: float64
2012-05-01 0.0
2012-05-07 5.0
2012-06-03 10.0
dtype: float64
2012-05-01 0.000000
2012-05-07 1.818182
2012-06-03 10.000000
dtype: float64
interpolate中有一些限制参数,limit表示最多插入多少个。limit_direction表示插值方向,可选forward,backward,both,默认前向。imit_area表示插值区域,可选inside,outside,默认None。
s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
print(s.interpolate(limit=2))
print(s.interpolate(limit_direction='backward'))
print(s.interpolate(limit_area='inside'))
print(s.interpolate(limit_area='outside'))
0 NaN
1 NaN
2 1.0
3 2.0
4 3.0
5 NaN
6 5.0
7 5.0
8 5.0
dtype: float64
0 1.0
1 1.0
2 1.0
3 2.0
4 3.0
5 4.0
6 5.0
7 NaN
8 NaN
dtype: float64
0 NaN
1 NaN
2 1.0
3 2.0
4 3.0
5 4.0
6 5.0
7 NaN
8 NaN
dtype: float64
0 NaN
1 NaN
2 1.0
3 NaN
4 NaN
5 NaN
6 5.0
7 5.0
8 5.0
dtype: float64
例如样条插值、多项式插值等(需要安装Scipy),方法详情请看此处
【问题一】 如何删除缺失值占比超过25%的列?
s = pd.DataFrame([[0,1,2,3],[0,None,2,3],[0,None,2,3],[0,None,2,3],[0,1,2,3],[0,1,2,3]])
print(s)
print(s.drop(s.columns[s.isna().sum()/s.shape[0]>0.25],axis=1))
0 1 2 3
0 0 1.0 2 3
1 0 NaN 2 3
2 0 NaN 2 3
3 0 NaN 2 3
4 0 1.0 2 3
5 0 1.0 2 3
0 2 3
0 0 2 3
1 0 2 3
2 0 2 3
3 0 2 3
4 0 2 3
5 0 2 3
【问题二】 什么是Nullable类型?请谈谈为什么要引入这个设计?
这是Pandas在1.0新版本中引入的重大改变,其目的就是为了(在若干版本后)解决之前出现的混乱局面,统一缺失值处理方法。
【问题三】 对于一份有缺失值的数据,可以采取哪些策略或方法深化对它的了解?
练习一
现有一份虚拟数据集Missing_data_one.csv
,列类型分别为string/浮点/整型,请解决如下问题:
(a)请以列类型读入数据,并选出C为缺失值的行。
(b)现需要将A中的部分单元转为缺失值,单元格中的最小转换概率为25%,且概率大小与所在行B列单元的值成正比。
ex1 = pd.read_csv('data/Missing_data_one.csv').convert_dtypes()
print('(a)以列类型读入数据,并选出C为缺失值的行')
print(ex1[ex1['C'].isna()])
print('(b)将A中的部分单元转为缺失值,单元格中的最小转换概率为25%,且概率大小与所在行B列单元的值成正比')
ex1['A'] = pd.Series(list(zip(ex1['A'].values,ex1['B'].values))).apply(lambda x:x[0] if 0.25*x[1]/ex1['B'].min()<np.random.rand() else np.nan)
print(ex1.head())
(a)以列类型读入数据,并选出C为缺失值的行
A B C
1 not_NaN 0.700
5 not_NaN 0.972
11 not_NaN 0.736
19 not_NaN 0.684
21 not_NaN 0.913
(b)将A中的部分单元转为缺失值,单元格中的最小转换概率为25%,且概率大小与所在行B列单元的值成正比
A B C
0 not_NaN 0.922 4
1 not_NaN 0.700
2 not_NaN 0.503 8
3 NaN 0.938 4
4 not_NaN 0.952 10
练习二
现有一份缺失的数据集Missing_data_two.csv
,记录了36个人来自的地区、身高、体重、年龄和工资,请解决如下问题:
(a)统计各列缺失的比例并选出在后三列中至少有两个非缺失值的行。
(b)请结合身高列和地区列中的数据,对体重进行合理插值。
ex2 = pd.read_csv('data/Missing_data_two.csv').convert_dtypes()
print('(a)统计各列缺失的比例并选出在后三列中至少有两个非缺失值的行')
print(ex2.isna().sum()/ex2.shape[0])
print(ex2[ex2[['体重','年龄','工资']].isna().sum(1)<=1])
print('(b)结合身高列和地区列中的数据,对体重进行合理插值')
ex2_1 = ex2.copy()
for part,group in ex2_1.groupby('地区'):
ex2_1.loc[group.index,'体重'] = group[['身高','体重']].sort_values(by='身高').interpolate()['体重']
print(ex2_1)
(a)统计各列缺失的比例并选出在后三列中至少有两个非缺失值的行
编号 0.000000
地区 0.000000
身高 0.000000
体重 0.222222
年龄 0.250000
工资 0.222222
dtype: float64
编号 地区 身高 体重 年龄 工资
0 1 A 157.50 NaN 47 15905
1 2 B 202.00 91.80 25
3 4 A 166.61 59.95 77 5434
4 5 B 185.19 NaN 62 4242
5 6 A 187.13 78.42 55 13959
6 7 C 163.81 57.43 43 6533
7 8 A 183.80 75.42 48 19779
8 9 B 179.67 71.70 65 8608
9 10 C 186.08 77.47 65 12433
10 11 B 163.41 57.07 6495
13 14 B 175.99 68.39 13130
15 16 A 165.68 NaN 46 13683
16 17 B 166.48 59.83 31 17673
17 18 C 191.62 82.46 12447
18 19 A 172.83 65.55 23 13768
19 20 B 156.99 51.29 62 3054
20 21 C 200.22 90.20 41
21 22 A 154.63 49.17 35 14559
22 23 B 157.87 52.08 67 7398
23 24 A 165.55 NaN 66 19890
24 25 C 181.78 73.60 63 11383
25 26 A 164.43 57.99 34 19899
27 28 C 172.39 65.15 43 10362
28 29 B 162.12 55.91 13362
29 30 A 183.73 75.36 58 8270
30 31 C 181.19 NaN 41 12616
31 32 B 167.28 60.55 64 18317
34 35 B 170.12 63.11 77 7398
35 36 C 180.47 72.42 78 9554
(b)结合身高列和地区列中的数据,对体重进行合理插值
编号 地区 身高 体重 年龄 工资
0 1 A 157.50 53.580000 47 15905
1 2 B 202.00 91.800000 25
2 3 C 169.09 62.180000
3 4 A 166.61 59.950000 77 5434
4 5 B 185.19 81.750000 62 4242
5 6 A 187.13 78.420000 55 13959
6 7 C 163.81 57.430000 43 6533
7 8 A 183.80 75.420000 48 19779
8 9 B 179.67 71.700000 65 8608
9 10 C 186.08 77.470000 65 12433
10 11 B 163.41 57.070000 6495
11 12 A 202.56 92.300000
12 13 C 177.37 68.785000 79
13 14 B 175.99 68.390000 13130
14 15 C 199.11 89.200000
15 16 A 165.68 59.296667 46 13683
16 17 B 166.48 59.830000 31 17673
17 18 C 191.62 82.460000 12447
18 19 A 172.83 65.550000 23 13768
19 20 B 156.99 51.290000 62 3054
20 21 C 200.22 90.200000 41
21 22 A 154.63 49.170000 35 14559
22 23 B 157.87 52.080000 67 7398
23 24 A 165.55 58.643333 66 19890
24 25 C 181.78 73.600000 63 11383
25 26 A 164.43 57.990000 34 19899
26 27 B 158.28 53.995000 51
27 28 C 172.39 65.150000 43 10362
28 29 B 162.12 55.910000 13362
29 30 A 183.73 75.360000 58 8270
30 31 C 181.19 73.206667 41 12616
31 32 B 167.28 60.550000 64 18317
32 33 C 181.01 72.813333 13021
33 34 A 196.67 87.000000
34 35 B 170.12 63.110000 77 7398
35 36 C 180.47 72.420000 78 9554