Pandas在步入1.0后,对数据类型也做出了新的尝试,尤其是Nullable
类型和String
类型,了解这些可能在未来成为主流的新特性是必要的
>>> import pandas as pd
>>> import numpy as np
>>> df = pd.read_csv('data/table_missing.csv')
>>> 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-
一、缺失观测及其类型
1. 了解缺失信息
(a)isna和notna方法
对Series
使用会返回布尔列表
>>> df['Physics'].isna().head()
0 False
1 False
2 False
3 True
4 False
Name: Physics, dtype: bool
>>> df['Physics'].notna().head()
0 True
1 True
2 True
3 False
4 True
Name: Physics, dtype: bool
对DataFrame
使用会返回布尔表
>>> df.isna().head()
School Class ID Gender Address Height Weight Math Physics
0 False False True False False False True False False
1 False False True False False False True False False
2 False False False False False False True False False
3 False True True False False False False False True
4 False False False True False False False False False
但对于DataFrame
我们更关心到底每列有多少缺失值
>>> df.isna().sum()
School 0
Class 4
ID 6
Gender 7
Address 0
Height 0
Weight 13
Math 5
Physics 4
dtype: int64
此外,可以通过第1章中介绍的info
函数查看缺失信息
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
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
(b)查看缺失值的所以在行
以最后一列为例,挑出该列缺失值的行
>>> df[df['Physics'].isna()]
School Class ID Gender Address Height Weight Math Physics
3 S_1 NaN NaN F street_2 167 81.0 80.4 NaN
8 S_1 C_2 1204.0 F street_5 162 63.0 33.8 NaN
13 S_1 C_3 1304.0 NaN street_2 195 70.0 85.2 NaN
22 S_2 C_2 2203.0 M street_4 155 91.0 73.8 NaN
(c)挑选出所有非缺失值列
使用all
就是全部非缺失值,如果是any
就是至少有一个不是缺失值
>>> df[df.notna().all(1)]
School Class ID Gender Address Height Weight Math Physics
5 S_1 C_2 1201.0 M street_5 159 68.0 97.0 A-
6 S_1 C_2 1202.0 F street_4 176 94.0 63.5 B-
12 S_1 C_3 1303.0 M street_7 188 82.0 49.7 B
17 S_2 C_1 2103.0 M street_4 157 61.0 52.5 B-
21 S_2 C_2 2202.0 F street_7 194 77.0 68.5 B+
25 S_2 C_3 2301.0 F street_4 157 78.0 72.3 B+
27 S_2 C_3 2303.0 F street_7 190 99.0 65.9 C
28 S_2 C_3 2304.0 F street_6 164 81.0 95.5 A-
29 S_2 C_3 2305.0 M street_4 187 73.0 48.9 B
2. 三种缺失符号
(a)np.nan
np.nan
是一个麻烦的东西,首先它不等与任何东西,甚至不等于自己
>>> np.nan == np.nan
False
>>> np.nan == 0
False
>>> np.nan == None
False
在用equals
函数比较时,自动略过两侧全是np.nan
的单元格,因此结果不会影响
>>> df.equals(df)
True
其次,它在numpy
中的类型为浮点,由此导致数据集读入时,即使原来是整数的列,只要有缺失值就会变为浮点型
>>> type(np.nan)
<class 'float'>
>>> pd.Series([1,2,3]).dtype
dtype('int64')
>>> pd.Series([1,np.nan,3]).dtype
dtype('float64')
此外,对于布尔类型的列表,如果是np.nan
填充,那么它的值会自动变为True
而不是False
>>> pd.Series([1,np.nan,3],dtype='bool')
0 True
1 True
2 True
dtype: bool
但当修改一个布尔列表时,会改变列表类型,而不是赋值为True
>>> s = pd.Series([True,False],dtype='bool')
>>> s[1]=np.nan
>>> s
0 1.0
1 NaN
dtype: float64
在所有的表格读取后,无论列是存放什么类型的数据,默认的缺失值全为np.nan
类型
因此整型列转为浮点;而字符由于无法转化为浮点,因此只能归并为object
类型('O')
,原来是浮点型的则类型不变
>>> df['ID'].dtype
dtype('float64')
>>> df['Math'].dtype
dtype('float64')
>>> df['Class'].dtype
dtype('O')
(b)None
None
比前者稍微好些,至少它会等于自身
>>> None == None
True
它的布尔值为False
>>> pd.Series([None],dtype='bool')
0 False
dtype: bool
修改布尔列表不会改变数据类型
>>> s = pd.Series([True,False],dtype='bool')
>>> s[0]=None
>>> s
0 False
1 False
dtype: bool
>>> s = pd.Series([1,0],dtype='bool')
>>> s[0]=None
>>> s
0 False
1 False
dtype: bool
在传入数值类型后,会自动变为np.nan
>>> type(pd.Series([1,None])[1])
<class 'numpy.float64'>
只有当传入object
类型是保持不动,几乎可以认为,除非人工命名None
,它基本不会自动出现在Pandas
中
>>> type(pd.Series([1,None],dtype='O')[1])
<class 'NoneType'>
在使用equals
函数时不会被略过,因此下面的情况下返回False
>>> pd.Series([None]).equals(pd.Series([np.nan]))
False
(c)NaT
NaT
是针对时间序列的缺失值,是Pandas
的内置类型,可以完全看做时序版本的np.nan
,与自己不等,且使用equals
是也会被跳过
>>> s_time = pd.Series([pd.Timestamp('20120101')]*5)
>>> 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]
>>> s_time[2] = None
>>> s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
>>> s_time[2] = np.nan
>>> s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
>>> s_time[2] = pd.NaT
>>> s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
>>> type(s_time[2])
<class 'pandas._libs.tslibs.nattype.NaTType'>
>>> s_time[2] == s_time[2]
False
>>> s_time.equals(s_time)
True
>>> s = pd.Series([True,False],dtype='bool')
>>> s[1]=pd.NaT
>>> s
0 True
1 True
dtype: bool
3. Nullable类型与NA符号
这是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
(a)Nullable整形
对于该种类型而言,它与原来标记int
上的符号区别在于首字母大写:'Int'
>>> s_original = pd.Series([1, 2], dtype="int64")
>>> s_original
0 1
1 2
dtype: int64
>>> s_new = pd.Series([1, 2], dtype="Int64")
>>> s_new
0 1
1 2
dtype: Int64
它的好处就在于,其中前面提到的三种缺失值都会被替换为统一的NA
符号,且不改变数据类型
>>> s_original[1] = np.nan
>>> s_original
0 1.0
1 NaN
dtype: float64
>>> s_new[1] = np.nan
>>> s_new
0 1
1 <NA>
dtype: Int64
>>> s_new[1] = None
>>> s_new
0 1
1 <NA>
dtype: Int64
>>> s_new[1] = pd.NaT
>>> s_new
0 1
1 <NA>
dtype: Int64
(b)Nullable布尔
对于该种类型而言,作用与上面的类似,记号为boolean
>>> s_original = pd.Series([1, 0], dtype="bool")
>>> s_original
0 True
1 False
dtype: bool
>>> s_new = pd.Series([0, 1], dtype="boolean")
>>> s_new
0 False
1 True
dtype: boolean
>>> s_original[0] = np.nan
>>> s_original
0 NaN
1 0.0
dtype: float64
>>> s_original = pd.Series([1, 0], dtype="bool") #此处重新加一句是因为前面赋值改变了bool类型
>>> s_original[0] = None
>>> s_original
0 False
1 False
dtype: bool
>>> s_new[0] = np.nan
>>> s_new
0 <NA>
1 True
dtype: boolean
>>> s_new[0] = None
>>> s_new
0 <NA>
1 True
dtype: boolean
>>> s_new[0] = pd.NaT
>>> s_new
0 <NA>
1 True
dtype: boolean
需要注意的是,含有pd.NA
的布尔列表在1.0.2之前的版本作为索引时会报错,这是一个之前的bug,现已经修复
>>> s = pd.Series(['dog','cat'])
>>> s[s_new]
1 cat
dtype: object
(c)string类型
该类型是1.0的一大创新,目的之一就是为了区分开原本含糊不清的object
类型,这里将简要地提及string
,因为它是第7章的主题内容
它本质上也属于Nullable
类型,因为并不会因为含有缺失而改变类型
>>> s = pd.Series(['dog','cat'],dtype='string')
>>> s
0 dog
1 cat
dtype: string
>>> s[0] = np.nan
>>> s
0 <NA>
1 cat
dtype: string
>>> s[0] = None
>>> s
0 <NA>
1 cat
dtype: string
>>> s[0] = pd.NaT
>>> s
0 <NA>
1 cat
dtype: string
此外,和object
类型的一点重要区别就在于,在调用字符方法后,string
类型返回的是Nullable
类型,object
则会根据缺失类型和数据类型而改变
>>> s = pd.Series(["a", None, "b"], dtype="string")
>>> s.str.count('a')
0 1
1 <NA>
2 0
dtype: Int64
>>> s2 = pd.Series(["a", None, "b"], dtype="object")
>>> s2.str.count("a")
0 1.0
1 NaN
2 0.0
dtype: float64
>>> s.str.isdigit()
0 False
1 <NA>
2 False
dtype: boolean
>>> s2.str.isdigit()
0 False
1 None
2 False
dtype: object
4. NA的特性
(a)逻辑运算
只需看该逻辑运算的结果是否依赖pd.NA
的取值,如果依赖,则结果还是NA
,如果不依赖,则直接计算结果
>>> True | pd.NA
True
>>> pd.NA | True
True
>>> False | pd.NA
<NA>
>>> False & pd.NA
False
>>> True & pd.NA
<NA>
取值不明直接报错
>>> #bool(pd.NA)
(b)算术运算和比较运算
这里只需记住除了下面两类情况,其他结果都是NA
即可
>>> pd.NA ** 0
1
>>> 1 ** pd.NA
1
其他情况:
>>> pd.NA + 1
<NA>
>>> "a" * pd.NA
<NA>
>>> pd.NA == pd.NA
<NA>
>>> pd.NA < 2.5
<NA>
>>> np.log(pd.NA)
<NA>
>>> np.add(pd.NA, 1)
<NA>
5. convert_dtypes方法
这个函数的功能往往就是在读取数据时,就把数据列转为Nullable
类型,是1.0的新函数
>>> pd.read_csv('data/table_missing.csv').dtypes
School object
Class object
ID float64
Gender object
Address object
Height int64
Weight float64
Math float64
Physics object
dtype: object
>>> pd.read_csv('data/table_missing.csv').convert_dtypes().dtypes
School string
Class string
ID Int64
Gender string
Address string
Height Int64
Weight Int64
Math Float64
Physics string
dtype: object
二、缺失数据的运算与分组
1. 加号与乘号规则
使用加法时,缺失值为0
>>> s = pd.Series([2,3,np.nan,4])
>>> s.sum()
9.0
使用乘法时,缺失值为1
>>> s.prod()
24.0
使用累计函数时,缺失值自动略过
>>> s.cumsum()
0 2.0
1 5.0
2 NaN
3 9.0
dtype: float64
>>> s.cumprod()
0 2.0
1 6.0
2 NaN
3 24.0
dtype: float64
>>> s.pct_change()
0 NaN
1 0.500000
2 0.000000
3 0.333333
dtype: float64
2. groupby方法中的缺失值
自动忽略为缺失值的组
>>> df_g = pd.DataFrame({'one':['A','B','C','D',np.nan],'two':np.random.randn(5)})
>>> df_g
one two
0 A 0.284965
1 B 0.138844
2 C -1.036792
3 D 0.835194
4 NaN 0.984975
>>> df_g.groupby('one').groups
{'A': Int64Index([0], dtype='int64'),
'B': Int64Index([1], dtype='int64'),
'C': Int64Index([2], dtype='int64'),
'D': Int64Index([3], dtype='int64')}
三、填充与剔除
1. fillna方法
(a)值填充与前后向填充(分别与ffill方法和bfill方法等价)
>>> df['Physics'].fillna('missing').head()
0 A+
1 B+
2 B+
3 missing
4 A-
Name: Physics, dtype: object
>>> df['Physics'].fillna(method='ffill').head()
0 A+
1 B+
2 B+
3 B+
4 A-
Name: Physics, dtype: object
>>> df['Physics'].fillna(method='backfill').head()
0 A+
1 B+
2 B+
3 A-
4 A-
Name: Physics, dtype: object
(b)填充中的对齐特性
>>> df_f = pd.DataFrame({'A':[1,3,np.nan],'B':[2,4,np.nan],'C':[3,5,np.nan]})
>>> df_f.fillna(df_f.mean())
A B C
0 1.0 2.0 3.0
1 3.0 4.0 5.0
2 2.0 3.0 4.0
返回的结果中没有C,根据对齐特点不会被填充
>>> 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 NaN
2. dropna方法
(a)axis参数
>>> df_d = pd.DataFrame({'A':[np.nan,np.nan,np.nan],'B':[np.nan,3,2],'C':[3,2,1]})
>>> df_d
A B C
0 NaN NaN 3
1 NaN 3.0 2
2 NaN 2.0 1
>>> df_d.dropna(axis=0)
A B C
>>> df_d.dropna(axis=1)
C
0 3
1 2
2 1
(b)how参数(可以选all或者any,表示全为缺失去除和存在缺失去除)
>>> df_d.dropna(axis=1,how='all')
B C
0 NaN 3
1 3.0 2
2 2.0 1
(c)subset参数(即在某一组列范围中搜索缺失值)
>>> df_d.dropna(axis=0,subset=['B','C'])
A B C
1 NaN 3.0 2
2 NaN 2.0 1
四、插值(interpolation)
1. 线性插值
(a)索引无关的线性插值
默认状态下,interpolate
会对缺失的值进行线性插值
>>> s = pd.Series([1,10,15,-5,-2,np.nan,np.nan,28])
>>> s
0 1.0
1 10.0
2 15.0
3 -5.0
4 -2.0
5 NaN
6 NaN
7 28.0
dtype: float64
>>> s.interpolate()
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
>>> s.interpolate().plot()
此时的插值与索引无关
>>> s.index = np.sort(np.random.randint(50,300,8))
>>> s.interpolate()
>>> #值不变
69 1.0
71 10.0
84 15.0
117 -5.0
119 -2.0
171 8.0
219 18.0
236 28.0
dtype: float64
>>> s.interpolate().plot()
>>> #后面三个点不是线性的(如果几乎为线性函数,请重新运行上面的一个代码块,这是随机性导致的)
(b)与索引有关的插值
method
中的index
和time
选项可以使插值线性地依赖索引,即插值为索引的线性函数
>>> s.interpolate(method='index').plot()
>>> #可以看到与上面的区别
如果索引是时间,那么可以按照时间长短插值,对于时间序列将在第9章详细介绍
>>> s_t = pd.Series([0,np.nan,10],index=[pd.Timestamp('2012-05-01'),pd.Timestamp('2012-05-07'),pd.Timestamp('2012-06-03')])
>>> s_t
2012-05-01 0.0
2012-05-07 NaN
2012-06-03 10.0
dtype: float64
>>> s_t.interpolate().plot()
>>> s_t.interpolate(method='time').plot()
2. 高级插值方法
此处的高级指的是与线性插值相比较,例如样条插值、多项式插值、阿基玛插值等(需要安装Scipy
),方法详情请看这里
关于这部分仅给出一个官方的例子,因为插值方法是数值分析的内容,而不是Pandas
中的基本知识:
>>> ser = pd.Series(np.arange(1, 10.1, .25) ** 2 + np.random.randn(37))
>>> missing = np.array([4, 13, 14, 15, 16, 17, 18, 20, 29])
>>> ser[missing] = np.nan
>>> methods = ['linear', 'quadratic', 'cubic']
>>> df = pd.DataFrame({m: ser.interpolate(method=m) for m in methods})
>>> df.plot()
3. interpolate中的限制参数
(a)limit表示最多插入多少个
>>> s = pd.Series([1,np.nan,np.nan,np.nan,5])
>>> s.interpolate(limit=2)
0 1.0
1 2.0
2 3.0
3 NaN
4 5.0
dtype: float64
(b)limit_direction表示插值方向,可选forward,backward,both,默认前向
>>> s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
>>> s.interpolate(limit_direction='backward')
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
(c)limit_area表示插值区域,可选inside,outside,默认None
>>> s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
>>> s.interpolate(limit_area='inside')
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
>>> s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
>>> s.interpolate(limit_area='outside')
0 NaN
1 NaN
2 1.0
3 NaN
4 NaN
5 NaN
6 5.0
7 5.0
8 5.0
dtype: float64
相关文章
Python Pandas 第1章 基础
Python Pandas 第2章 索引
Python Pandas 第3章 分组
Python Pandas 第4章 变形
Python Pandas 第5章 合并
Python Pandas 第6章 缺少数据
Python Pandas 第7章 文本数据
Python Pandas 第8章 分类数据
Python Pandas 第9章 时序数据