Pandas 数据处理
1、安装 Pandas
import pandas
print(pandas.__version__) # 0.23.4
一般导入 Pandas 时我们使用别名 pd:
import pandas as pd
2、Pandas 对象简介
Pandas 的三个基本数据结构:Series、DataFrame 和 Index。
2.1、Pandas 的 Series 对象
Pandas 的 Series 对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series 对象。
data = pd.Series([0.25,0.5,0.75,1.0])
print(data)
'''
0 0.25
1 0.50
2 0.75
3 1.00
dtype: float64
'''
你会发现 Series 对象将一组数据和一组索引绑定在一起,我们可以通过 values 属性和 index 属性获取数据。values 属性返回的结果与 NumPy 数组类似:
print(data.values) # [0.25 0.5 0.75 1. ]
index 属性返回的结果是一个类型为 pd.Index 的类型数组对象:
print(data.index) # RangeIndex(start=0, stop=4, step=1)
和 NumPy 一样,数据可以通过 Python 的中括号索引标签获取:
print(data[1]) # 0.5
print(data[1:3])
'''
1 0.50
2 0.75
dtype: float64
'''
-
Serise 是通用的 NumPy 数组
Serise 对象与一维的 NumPy 数组基本可以等价交换,但是两者间的本质差异其实就是索引:NumPy 数组通过隐式定义的整数索引获取数值,而 Pandas 的 Series 对象用一种显式定义的索引与数组关联。
显式索引的定义让 Series 对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以是字符串:
data = pd.Series([0.25,0.5,0.75,1.0], index=['a','b','c','d']) print(data) ''' a 0.25 b 0.50 c 0.75 d 1.00 dtype: float64 '''
获取数值的方式与之前的一样:
print(data['b']) # 0.5
也可以使用不按顺序或不连续的索引。
-
Series 是特殊的字典
字典是一种将任意键映射到一组任意值的数据结构,而 Series 对象其实是一种将类型键映射到一组类型值的数据结构。类型至关重要:就像 NumPy 数值背后特定类型的经过编译的代码使得它在某些操作上比普通的 Python 列表更加高效一样,Pandas Series 的类型信息使得它在某些操作上比 Python 的字典更高效。
我们可以直接使用 Python 的字典创建一个 Series 对象,让 Series 对象与字典的类比更加清晰:
population_dict = {'California':38332521, 'Texas':26448193, 'New York':19651127, 'Florida':19552860, 'Illinois':12882135} population = pd.Series(population_dict) print(population) ''' California 38332521 Texas 26448193 New York 19651127 Florida 19552860 Illinois 12882135 dtype: int64 '''
典型的字典数值获取方式仍然有效:
print(population['California']) # 38332521
和字典不同,Series 对象还支持数组形式的操作,比如切片:
print(population['California':'Florida']) ''' California 38332521 Texas 26448193 New York 19651127 Florida 19552860 dtype: int64 '''
-
创建 Series 对象
一般的形式:
pd.Series(data,index=index)
其中,index 是一个可选参数,data 参数支持多种数据类型。
例如,data 可以是列表或 NumPy 数组,这时 index 默认值为整数序列:
print(pd.Series([2,4,6])) ''' 0 2 1 4 2 6 dtype: int64 '''
data 可以是一个标量,创建 Series 对象会重复填充到每个索引上:
print(pd.Series(5,index=[100,200,300])) ''' 100 5 200 5 300 5 dtype: int64 '''
data 还可以是一个字典,index 默认是排序的字典键:
print(pd.Series({2:'a',1:'b',3:'c'})) ''' 2 a 1 b 3 c dtype: object '''
每一种形式都可以通过显式指定索引筛选需要的结果:
print(pd.Series({2:'a',1:'b',3:'c'},index=[3,2])) ''' 3 c 2 a dtype: object '''
注意:Series 对象只会保留显式定义的键值对。
2.2、Pandas 的 DataFrame 对象
DataFrame 既可以作为一个通用的 NumPy 数组,也可以看作是特殊的 Python 字典。
-
DataFrame 是通用的 NumPy 数组
如果把 Series 类比为带灵活索引的一维数组,那么 DataFrame 就可以看做是一种既有灵活的行索引,又有灵活列名的二维数组。你可以把 DataFrame 看成是有序排列的若干个 Series 对象,这里的 “排列” 指的是它们拥有共同的索引。
area_dict = {'California':423967,'Texas':695662,'New York':141297,'Florida':170312,'Illinois':149995} area = pd.Series(area_dict) print(area) ''' California 423967 Texas 695662 New York 141297 Florida 170312 Illinois 149995 dtype: int64 '''
再结合之前创建的 population 的 Series 对象,用一个字典创建一个包含这些信息的二维对象:
states = pd.DataFrame({'population':population, 'area':area}) print(states) ''' population area California 38332521 423967 Texas 26448193 695662 New York 19651127 141297 Florida 19552860 170312 Illinois 12882135 149995 '''
和 Series 对象一样,DataFrame 也有一个 index 属性可以获取行索引标签:
print(states.index) # Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')
另外,DataFrame 还有一个 columns 属性,是存放列标签的 Index 对象:
print(states.columns) # Index(['population', 'area'], dtype='object')
因此,DataFrame 的数值可以通过行列索引来获取,可以看做是一个二维数组。
-
DataFrame 是特殊的字典
字典是一个键映射到一个值,而 DataFrame 是一列映射一个 Series 的数据。
print(states['area']) ''' California 423967 Texas 695662 New York 141297 Florida 170312 Illinois 149995 Name: area, dtype: int64 '''
注意:在 NumPy 的二维数组中,data[0] 返回的是第一行;而在 DataFrame 中,data[‘col0’] 返回的是第一列。
-
创建 DataFrame 对象
-
通过单个 Series 对象创建。DataFrame 是一组 Series 对象的集合,可以用单个 Series 创建一个单列的 DataFrame:
print(pd.DataFrame(population,columns=['population'])) ''' population California 38332521 Texas 26448193 New York 19651127 Florida 19552860 Illinois 12882135 '''
-
通过字典列表创建。任何元素是字典的列表都可以变成 DataFrame。用一个简单的列表综合来创建一些数据:
data = [{'a':i,'b':2*i} for i in range(3)] print(pd.DataFrame(data)) ''' a b 0 0 0 1 1 2 2 2 4 '''
即使字典中有些键不存在,Pandas 也会用缺失值 NaN 来表示:
print(pd.DataFrame([{'a':1,'b':2},{'b':3,'c':4}])) ''' a b c 0 1.0 2 NaN 1 NaN 3 4.0 '''
-
通过 Series 对象字典创建。
print(pd.DataFrame({'population':population,'area':area})) ''' population area California 38332521 423967 Texas 26448193 695662 New York 19651127 141297 Florida 19552860 170312 Illinois 12882135 149995 '''
-
通过 NumPy 二维数组创建。假如有一个二维数组,就可以创建一个可以指定行列索引值的 DataFrame。如果不指定行列索引值,那么行列默认都是整数索引值:
import numpy as np print(pd.DataFrame(np.random.rand(3,2), columns=['foo','bar'], index=['a','b','c'])) ''' foo bar a 0.966568 0.012753 b 0.143990 0.916020 c 0.446891 0.402528 '''
-
通过 NumPy 结构化数组创建。
A = np.zeros(3,dtype=[('A','i8'),('B','f8')]) print(A) # [(0, 0.) (0, 0.) (0, 0.)] print(pd.DataFrame(A)) ''' A B 0 0 0.0 1 0 0.0 2 0 0.0 '''
-
2.3、Pandas 的 Index 对象
Series 和 DataFrame 对象都使用便于引用和调整的显式索引。Pandas 的 Index 对象是一个很有趣的数据结构,可以将它看作是一个不可变数组或有序集合(实际上是一个多集,因为 Index 对象可能会包含重复值)。
ind = pd.Index([2,3,5,7,11])
print(ind) # Int64Index([2, 3, 5, 7, 11], dtype='int64')
-
将 Index 看作不可变数组
Index 对象的许多操作都像数组。例如,可以通过标准 Python 的取值方法获取数值,也可以通过切片获取数值。
print(ind[1]) # 3 print(ind[::2]) # Int64Index([2, 5, 11], dtype='int64')
Index 对象还有许多与 NumPy 数组相似的属性:
print(ind.size,ind.shape,ind.ndim,ind.dtype) # 5 (5,) 1 int64
Index 对象与 NumPy 数组之间的不同在于,Index 对象的索引是不可变的,也就是说不能通过通常的方式来调整。
-
将 Index 看作有序集合
Pandas 对象被设计用于实现许多操作,如连接(join)数据集,其中涉及许多集合操作。Index 对象遵循 Python 标准库的集合(Set)数据结构的许多习惯用法,包括并集、交集、差集等:
indA = pd.Index([1,3,5,7,9]) indB = pd.Index([2,3,5,7,11]) print(indA & indB) # Int64Index([3, 5, 7], dtype='int64') print(indA | indB) # Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64') print(indA ^ indB) # Int64Index([1, 2, 9, 11], dtype='int64')
3、数据取值与选择
3.1、Series 数据选择方法
Series 对象与一维 NumPy 数组和标准的 Python 字典在许多方面都一样。
-
将 Series 看作字典
和字典一样,Series 对象提供了键值对的映射:
data = pd.Series([0.25,0.5,0.75,1.0], index=['a','b','c','d']) print(data) ''' a 0.25 b 0.50 c 0.75 d 1.00 dtype: float64 ''' print(data['b']) # 0.5
我们还可以用 Python 字典的表达式和方法来检测键/索引和值:
print('a' in data) # True print(data.keys()) # Index(['a', 'b', 'c', 'd'], dtype='object') print(list(data.items())) # [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
Series 对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series:
data['e'] = 1.25 print(data) ''' a 0.25 b 0.50 c 0.75 d 1.00 e 1.25 dtype: float64 '''
Series 对象的可变性是一个非常方便的特性。
-
将 Series 看作一维数组
Series 不仅有着和字典一样的接口,而且还具备和 NumPy 数组一样的数组数据选择功能,包括索引、掩码、花哨的索引等操作:
#将显示索引作为切片 print(data['a':'c']) ''' a 0.25 b 0.50 c 0.75 dtype: float64 '''
#将隐式整数索引作为切片 print(data[0:2]) ''' a 0.25 b 0.50 dtype: float64 '''
#掩码 print(data[(data > 0.3) & (data < 0.8)]) ''' b 0.50 c 0.75 dtype: float64 '''
#花哨的索引 print(data[['a','e']]) ''' a 0.25 e 1.25 dtype: float64 '''
注意:当使用显示索引(即 data[‘a’:’c’] 作切片时,结果包含最后一个索引);而当使用隐式索引(即 data[0:2])作切片时,结果不包含最后一个索引。
-
索引器:loc、iloc 和ix
由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器属性来作为取值的方法。它不是 Series 对象的函数方法,而是暴露切片接口的属性。
第一种索引器是 loc 属性,表示取值和切片都是显示的;
第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引;
第三种取值属性是 ix,它是前两种索引器的混合形式。
3.2、DataFrame 数据选择方法
-
将 DataFrame 看作字典
第一种类比是把 DataFrame 当作一个由若干个 Series 对象构成的字典。
area = pd.Series({'California':423967,'Texas':695662, 'New York':141297,'Florida':170312, 'Illinois':149995}) pop = pd.Series({'California':38332521,'Texas':26448193, 'New York':19651127,'Florida':19552860, 'Illinois':12882135}) data = pd.DataFrame({'area':area,'pop':pop}) print(data) ''' area pop California 423967 38332521 Texas 695662 26448193 New York 141297 19651127 Florida 170312 19552860 Illinois 149995 12882135 '''
两个 Series 分别构成 DataFrame 的一列,可以通过对列名进行字典形式的取值获取数据:
print(data['area']) ''' California 423967 Texas 695662 New York 141297 Florida 170312 Illinois 149995 Name: area, dtype: int64 '''
同样,也可以用属性形式选择纯字符串列名的数据:
print(data.area) ''' California 423967 Texas 695662 New York 141297 Florida 170312 Illinois 149995 Name: area, dtype: int64 '''
对同一个对象进行属性形式与字典形式的列数据,结果是相同的:
print(data.area is data['area']) # True
显然,属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果用 data.pop 就不会获取 ‘pop’ 列,而是显示为方法:
print(data.pop is data['pop']) # False
和 Series 对象一样,还可以用字典形式的语法调整对象,如果要增加一列可以这样做:
data['density'] = data['pop']/data['area'] print(data) ''' area pop density California 423967 38332521 90.413926 Texas 695662 26448193 38.018740 New York 141297 19651127 139.076746 Florida 170312 19552860 114.806121 Illinois 149995 12882135 85.883763 '''
-
将 DataFrame 看作二维数组
可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据:
print(data.values) ''' [[4.23967000e+05 3.83325210e+07 9.04139261e+01] [6.95662000e+05 2.64481930e+07 3.80187404e+01] [1.41297000e+05 1.96511270e+07 1.39076746e+02] [1.70312000e+05 1.95528600e+07 1.14806121e+02] [1.49995000e+05 1.28821350e+07 8.58837628e+01]] '''
理解了这一点,就可以把许多数组的操作方式用在 DataFrame 上。例如,可以对 DataFrame 进行行列转置:
print(data.T) ''' California Texas ... Florida Illinois area 4.239670e+05 6.956620e+05 ... 1.703120e+05 1.499950e+05 pop 3.833252e+07 2.644819e+07 ... 1.955286e+07 1.288214e+07 density 9.041393e+01 3.801874e+01 ... 1.148061e+02 8.588376e+01 [3 rows x 5 columns] '''
通过字典形式对列进行取值显然会限制我们把 DataFrame 作为 NumPy 数组可以获得的能力,尤其是当我们在 DataFrame 数组中使用单个行索引获取一行数据时:
print(data.values[0]) # [4.23967000e+05 3.83325210e+07 9.04139261e+01]
而获取一列数据就需要向 DataFrame 传递单个列索引:
print(data['area']) ''' California 423967 Texas 695662 New York 141297 Florida 170312 Illinois 149995 Name: area, dtype: int64 '''
通过 iloc 索引器,我们就可以像对待 NumPy 数组一样索引 Pandas 的底层数组(Python 的隐式索引),DataFrame 的行列标签会自动保留在结果中:
print(data.iloc[:3,:2]) ''' area pop California 423967 38332521 Texas 695662 26448193 New York 141297 19651127 '''
print(data.loc[:'Illinois',:'pop']) ''' area pop California 423967 38332521 Texas 695662 26448193 New York 141297 19651127 Florida 170312 19552860 Illinois 149995 12882135 '''
任何用于处理 NumPy 形式数据的方法都可以用于这些索引器。例如,可以在 loc 索引器中结合使用掩码与花哨的索引方法:
print(data.loc[data.density > 100,['pop','density']]) ''' pop density New York 19651127 139.076746 Florida 19552860 114.806121 '''
任何一种取值方法都可以用于调整数据,这一点和 NumPy 的常用方法是相同的:
data.iloc[0,2] = 90 print(data) ''' area pop density California 423967 38332521 90.000000 Texas 695662 26448193 38.018740 New York 141297 19651127 139.076746 Florida 170312 19552860 114.806121 Illinois 149995 12882135 85.883763 '''
-
其他取值方法
如果对单个标签取值就选择列,而对多个标签用切片就选择行:
print(data['Florida':'Illinois']) ''' area pop density Florida 170312 19552860 114.806121 Illinois 149995 12882135 85.883763 '''
切片也可以不用索引,而直接用行数来实现:
print(data[1:3]) ''' area pop density Texas 695662 26448193 38.018740 New York 141297 19651127 139.076746 '''
与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器:
print(data[data.density > 100]) ''' area pop density New York 141297 19651127 139.076746 Florida 170312 19552860 114.806121 '''
4、Pandas 数值运算方法
NumPy 的基本功能之一是快速对每个元素进行运算,既包括基本算术运算(加、减、乘、除),也包括更复杂的运算(三角函数、指数函数和对数函数等)。
但是 Pandas 也实现了一些高效技巧:对于一元运算(像函数与三角函数),这些通用函数将在输出结果中保留索引和列标签;而对于二元运算(如加法和乘法),Pandas 在传递通用函数时会自动对齐索引进行计算。这意味着,保留数据内容与组合不同来源的数据——两处在 NumPy 数组中都容易出错的地方——变成了 Pandas 的杀手锏。
4.1、通用函数:保留索引
因为 Pandas 是建立在 NumPy 基础之上的,所以 NumPy 的通用函数同样适用于 Pandas 的 Series 和 DataFrame 对象。
rng = np.random.RandomState(42)
ser = pd.Series(rng.randint(0,10,4))
print(ser)
'''
0 6
1 3
2 7
3 4
dtype: int32
'''
df = pd.DataFrame(rng.randint(0,10,(3,4)),
columns=['A','B','C','D'])
print(df)
'''
A B C D
0 6 9 2 6
1 7 4 3 7
2 7 2 5 4
'''
如果对这两个对象的其中一个使用 NumPy 通用函数,生成的结果是另一个保留索引的 Pandas 对象:
print(np.exp(ser))
'''
0 403.428793
1 20.085537
2 1096.633158
3 54.598150
dtype: float64
'''
或者再做一个比较复杂的运算:
print(np.sin(df * np.pi / 4))
'''
A B C D
0 -1.000000 7.071068e-01 1.000000 -1.000000e+00
1 -0.707107 1.224647e-16 0.707107 -7.071068e-01
2 -0.707107 1.000000e+00 -0.707107 1.224647e-16
'''
4.2、通用函数:索引对齐
4.2.1、Series 索引对齐
假如你要整合两个数据源的数据:
area = pd.Series({'Alaska':123337,'Texas':695662,
'California':423967},name='area')
population = pd.Series({'California':38332521,'Texas':26448193,
'New York':19651127},name='population')
print(population / area)
'''
Alaska NaN
California 90.413926
New York NaN
Texas 38.018740
dtype: float64
'''
结果数组的索引是两个输入数组索引的并集。我们也可以用 Python 标准库的集合运算法则来获取这个索引:
print(area.index | population.index)
# Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')
对于缺失位置的数据,Pandas 会用 NaN 填充,表示 “此处无数”。这是 Pandas 表示缺失值的方法。这种索引对齐方式是通过 Python 内置的集合运算规则来实现的,任何缺失值默认都用 NaN 填充:
A = pd.Series([2,4,6],index=[0,1,2])
B = pd.Series([1,3,5],index=[1,2,3])
print(A + B)
'''
0 NaN
1 5.0
2 9.0
3 NaN
dtype: float64
'''
如果用 NaN 值不是我们想要的结果,那么可以用适当的对象方法替代运算符。例如,A.add(B) 等价于 A + B,也可以设置参数自定义 A 或 B 缺失的数据:
print(A.add(B,fill_value=0))
'''
0 2.0
1 5.0
2 9.0
3 5.0
dtype: float64
'''
4.2.2、DataFrame 索引对齐
在计算两个 DataFrame 时,类似的索引对齐规则也同样会出现在共同(并集)列中:
A = pd.DataFrame(rng.randint(0,20,(2,2)),
columns=list('AB'))
print(A)
'''
A B
0 1 11
1 5 1
'''
B = pd.DataFrame(rng.randint(0,10,(3,3)),
columns=list('ABC'))
print(B)
'''
A B C
0 4 0 9
1 5 8 0
2 9 2 6
'''
print(A + B)
'''
A B C
0 5.0 11.0 NaN
1 10.0 9.0 NaN
2 NaN NaN NaN
'''
你会发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按顺序排列。在 Series 中,我们可以通过运算符方法的 fill_value 参数自定义缺失值。
这里,我们将用 A 中所有值的均值来填充缺失值(计算 A 的均值需要用 stack 将二维数组压缩成一维数组):
fill = A.stack().mean()
print(A.add(B,fill_value=fill()))
'''
A B C
0 5.0 11.0 NaN
1 10.0 9.0 NaN
2 NaN NaN NaN
'''
Python 运算符与相应的 Pandas 对应方法:
Python 运算符 | Pandas 方法 |
---|---|
+ | add() |
- | sub()、subtract() |
* | mul()、multiply() |
/ | truediv()、div()、divide() |
// | floordiv() |
% | mod() |
** | pow() |
4.2.3、通用函数:DataFrame 与 Series 的运算
DataFrame 与 Series 的运算规则,与 NumPy 中二维数组与一维数组的运算规则是一样的。一个例子,让一个二维数组减去自身的一行数据:
A = rng.randint(10,size=(3,4))
print(A)
'''
[[3 8 2 4]
[2 6 4 8]
[6 1 3 8]]
'''
print(A - A[0])
'''
[[ 0 0 0 0]
[-1 -2 2 4]
[ 3 -7 1 4]]
'''
根据 NumPy 的广播规则,让二维数组减自身的一行数据会按行计算。
在 Pandas 里默认也是按行计算的:
df = pd.DataFrame(A,columns=list('QRST'))
print(df - df.iloc[0])
'''
Q R S T
0 0 0 0 0
1 -1 -2 2 4
2 3 -7 1 4
'''
如果你想按列计算,那么就需要利用前面介绍过的运算符方法,通过 axis 参数设置:
print(df.subtract(df['R'],axis=0))
'''
Q R S T
0 -5 0 -6 -4
1 -4 0 -2 2
2 5 0 2 7
'''
结果的索引都会自动对齐:
halfrow = df.iloc[0,::2]
print(halfrow)
'''
Q 3
S 2
Name: 0, dtype: int32
'''
print(df - halfrow)
'''
Q R S T
0 0.0 NaN 0.0 NaN
1 -1.0 NaN 2.0 NaN
2 3.0 NaN 1.0 NaN
'''
5、处理缺失值
5.1、选择处理缺失值的方法
识别缺失值的方法一般分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个标签值表示缺失值。
在掩码方法中,掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(0 或 1)表示有缺失值的局部状态。
在标签方法中,标签值可能是具体的数据,也可能是一些极少出现的形式。另外,标签值还可能是更全局的值,比如用 NaN(不是一个数)表示缺失的浮点数,它是 IEEE 浮点数规范中指定的特殊字符。
使用单独的掩码数组会额外出现一个布尔类型数组,从而增加存储与计算的负担;而标签值方法缩小了可以被表示为有效值的范围,可能需要在 CPU 或 GPU 算术逻辑单元中增加额外的计算逻辑。
5.2、Pandas 的缺失值
-
None:Python 对象类型的缺失值
它是一个 Python 单体对象。由于 None 是一个 Python 对象,所以不能作为任何 NumPy/Pandas 数组类型的缺失值,只能用于 ‘Object’ 数组类型(即由 Python 对象构成的数组):
vals1 = np.array([1,None,3,4]) print(vals1) # [1 None 3 4]
虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在 Python 层面完成,但是在进行常见的快速操作时,这种类型比其他原生类型数组要消耗更多资源。
-
NaN:数值类型的缺失值
另一种缺失值的标签是 NaN(全称 Not a Number,不是一个数字),是一种按照 IEEE 浮点数标准设计、在任何系统中都兼容的特殊浮点数:
vals2 = np.array([1,np.nan,3,4]) print(vals2) # [ 1. nan 3. 4.]
NumPy 会为这个数组选择一个原生浮点类型,这意味着和之前的 object 类型数组不同,这个数组会被编译成 C 代码从而实现快速操作。你可以把 NaN 看作是一个数据类病毒——它会将与它接触过的数据同化。无论和 NaN 进行任何操作,最终结果都是 NaN:
print(1 + np.nan) # nan print(0 * np.nan) # nan
虽然这些累计操作的结果定义是合理的(即不会抛出异常),但是并非总是有效的:
print(vals2.sum()) # nan print(vals2.min()) # nan print(vals2.max()) # nan
NumPy 也提供了一些特殊的累计函数,它们可以忽略缺失值的影响:
print(np.nansum(vals2)) # 8.0 print(np.nanmin(vals2)) # 1.0 print(np.nanmax(vals2)) # 4.0
谨记,NaN 是一种特殊的浮点数。
-
Pandas 中 NaN 与 None 的差异
虽然 NaN 与 None 各有各的用处,但是 Pandas 把它们看成是可以等价交换的,在适当的时候会将两者进行替换:
print(pd.Series([1,np.nan,2,None])) ''' 0 1.0 1 NaN 2 2.0 3 NaN dtype: float64 '''
Pandas 会将没有标签值的数据类型自动转换为 NA。例如,当我们将整型数组中的一个值设置为 np.nan 时,这个值就会强制转换成浮点数缺失值 NA。
x = pd.Series(range(2)) print(x) ''' 0 0 1 1 dtype: int64 ''' x[0] = None print(x) ''' 0 NaN 1 1.0 dtype: float64 '''
Pandas 对 NA 缺失值进行强制转换的规则如下:
类型 缺失值转换规则 NA 标签值 floating 浮点型 无变化 np.nan object 对象类型 无变化 None 或 np.nan integer 整数类型 强制转换为 float64 np.nan boolean 布尔类型 强制转换为 float64 None 或 np.nan 需要注意的是,Pandas 中字符串类型的数据通常是用 object 类型存储的。
5.3、处理缺失值
-
发现缺失值
两种方法:isnull() 和 notnull()。每种方法返回布尔类型的掩码数据。
data = pd.Series([1,np.nan,'hello',None]) print(data.isnull()) ''' 0 False 1 True 2 False 3 True dtype: bool '''
布尔类型的掩码数组可以直接作为 Series 或 DataFrame 的索引使用:
print(data[data.notnull()]) ''' 0 1 2 hello dtype: object '''
同样适用于 DataFrame。
-
剔除缺失值
剔除缺失值:dropna();填充缺失值:fillna()。
print(data.dropna()) ''' 0 1 2 hello dtype: object '''
而在 DataFrame 上使用它们时需要设置一些参数,例如:
df = pd.DataFrame([[1,np.nan,2], [2,3,5], [np.nan,4,6]]) print(df) ''' 0 1 2 0 1.0 NaN 2 1 2.0 3.0 5 2 NaN 4.0 6 '''
我们没法从 DataFrame 中单独剔除一个值,要么是剔除缺失值所在的行,要么是整列。
默认情况下,dropna() 会剔除任何包含缺失值的整行数据:
print(df.dropna()) ''' 0 1 2 1 2.0 3.0 5 '''
可以设置按不同的坐标轴剔除缺失值,比如 axis = 1(或 axis = ‘columns’)会剔除任何包含缺失值的整列数据:
print(df.dropna(axis='columns')) ''' 2 0 2 1 5 2 6 '''
但是这么做也会把非缺失值一并删除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置 how 或 thresh 参数来满足,它们可以设置剔除行或列缺失值的数量阈值。
默认设置是 how = ‘any’,也就是说只要有缺失值就剔除整行或整列(通过 axis 设置坐标轴)。你还可以设置 how = ‘all’,这样就只会剔除全部是缺失值的行或列了:
df[3] = np.nan print(df) ''' 0 1 2 3 0 1.0 NaN 2 NaN 1 2.0 3.0 5 NaN 2 NaN 4.0 6 NaN ''' print(df.dropna(axis='columns',how='all')) ''' 0 1 2 0 1.0 NaN 2 1 2.0 3.0 5 2 NaN 4.0 6 '''
还可以通过 thresh 参数设置行或列中非缺失值的最小数量,从而实现更加个性化的配置。
print(df.dropna(axis='rows',thresh=3)) ''' 0 1 2 3 1 2.0 3.0 5 NaN '''
-
填充缺失值
fillna() 方法填充缺失值。
data = pd.Series([1,np.nan,2,None,3],index=list('abcde')) print(data) ''' a 1.0 b NaN c 2.0 d NaN e 3.0 dtype: float64 '''
我们将用一个单独的值来填充缺失值,例如用 0:
print(data.fillna(0)) ''' a 1.0 b 0.0 c 2.0 d 0.0 e 3.0 dtype: float64 '''
可以用缺失值前面的有效值从前往后填充:
#从前往后填充 print(data.fillna(method='ffill')) ''' a 1.0 b 1.0 c 2.0 d 2.0 e 3.0 dtype: float64 '''
也可以用缺失值后面的有效值从后往前填充:
#从后往前填充 print(data.fillna(method='bfill')) ''' a 1.0 b 2.0 c 2.0 d 3.0 e 3.0 dtype: float64 '''
DataFrame 的操作方法与 Series 类似,只是在填充时需要设置坐标轴参数 axis:
print(df) ''' 0 1 2 3 0 1.0 NaN 2 NaN 1 2.0 3.0 5 NaN 2 NaN 4.0 6 NaN ''' print(df.fillna(method='ffill',axis=1)) ''' 0 1 2 3 0 1.0 1.0 2.0 2.0 1 2.0 3.0 5.0 5.0 2 NaN 4.0 6.0 6.0 '''
需要注意的是,假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值。
6、层级索引
存储多维数据的需求,数据索引超过一两个键。因此,Pandas 提供了 Panel 和 Panel4D 对象解决三维数据和四维数据。而在实践中,更直观的形式是通过层级索引,也被称为多级索引,配合多个有不同等级的一级索引一起使用,这样就可以将高维数组转换成类似一维 Series 和二维 DataFrame 对象的形式。
6.1、多级索引 Series
如何使用一维的 Series 对象表示二维数组——用一系列包含特征与数值的数据点来简单演示。
-
笨方法
两个不同年份的数据比较:
index = [('California',2000),('California',2010), ('New York',2000),('New York',2010), ('Texas',2000),('Texas',2010)] population = [33871648,37253956, 18976457,19378102, 20851820,25145561] pop = pd.Series(population,index=index) print(pop) ''' (California, 2000) 33871648 (California, 2010) 37253956 (New York, 2000) 18976457 (New York, 2010) 19378102 (Texas, 2000) 20851820 (Texas, 2010) 25145561 dtype: int64 '''
通过元组构成的多级索引,你可以直接在 Series 上取值或用切片查询数据:
print(pop[('California',2010):('Texas',2000)]) ''' (California, 2010) 37253956 (New York, 2000) 18976457 (New York, 2010) 19378102 (Texas, 2000) 20851820 dtype: int64 '''
-
好方法:Pandas 多级索引
用元组表示索引其实是多级索引的基础,Pandas 的 MultiIndex 类型提供了更丰富的操作方法。
index = pd.MultiIndex.from_tuples(index) print(index) ''' MultiIndex(levels=[['California', 'New York', 'Texas'], [2000, 2010]], labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]]) '''
你会发现 MultiIndex 里面有一个 levels 属性表示索引的等级——这样做可以将州名和年份作为每个数据点的不同标签。
如果将前面创建的 pop 的索引重置为 MultiIndex,就会看到层级索引:
pop = pop.reindex(index) print(pop) ''' California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 dtype: int64 '''
前两列表示 Series 的多级索引值,第三列是数据。
现在可以直接使用第二个索引值获取 2010 年的全部数据,与 Pandas 的切片查询用法一致:
print(pop[:,2010]) ''' California 37253956 New York 19378102 Texas 25145561 dtype: int64 '''
结果是单索引的数组。
-
高维数据的多级索引
我们其实完全可以用一个带行列索引的简单 DataFrame 代替前面的多级索引。unstack() 方法可以快速将一个多级索引的 Series 转化为普通索引的 DataFrame:
pop_df = pop.unstack() print(pop_df) ''' 2000 2010 California 33871648 37253956 New York 18976457 19378102 Texas 20851820 25145561 '''
当然,也有 stack() 方法实现了相反的效果:
print(pop_df.stack()) ''' California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 dtype: int64 '''
为什么研究层级索引:如果我们可以用含多级索引的一维 Series 数据表示二维数据,那么我们就可以用 Series 或 DataFrame 表示三维甚至更高维的数据。多级索引每增加一级,就表示数据增加一维,利用这一特性就可以轻松表示任意维度的数据了。
pop_df = pd.DataFrame({'total':pop, 'under18':[9267089,9284094, 4687374,4318033, 5906301,6879014]}) print(pop_df) ''' total under18 California 2000 33871648 9267089 2010 37253956 9284094 New York 2000 18976457 4687374 2010 19378102 4318033 Texas 2000 20851820 5906301 2010 25145561 6879014 '''
上面介绍的通用函数和其他功能也同样适用于层级索引。
f_u18 = pop_df['under18'] / pop_df['total'] print(f_u18.unstack()) ''' 2000 2010 California 0.273594 0.249211 New York 0.247010 0.222831 Texas 0.283251 0.273568 '''
6.2、多级索引的创建方法
为 Series 或 DataFrame 创建多级索引最直接的办法就是将 index 参数设置为至少二维的索引数组,如下:
df = pd.DataFrame(np.random.rand(4,2),
index=[['a','a','b','b'],[1,2,1,2]],
columns=['data1','data2'])
print(df)
'''
data1 data2
a 1 0.107431 0.859198
2 0.627187 0.297819
b 1 0.668505 0.335400
2 0.506130 0.572006
'''
MultiIndex 的创建工作将在后台进行。
同理,如果你把元组作为键的字典传递给 Pandas,Pandas 也会默认转换为 MultiIndex:
data = {('California',2000):33871648,
('California',2010):372539556,
('Texas',2000):20851820,
('Texas',2010):25145561,
('New York',2000):18976457,
('New York',2010):19378102}
print(pd.Series(data))
'''
California 2000 33871648
2010 372539556
Texas 2000 20851820
2010 25145561
New York 2000 18976457
2010 19378102
dtype: int64
'''
但是有时候显式地创建 MultiIndex 也是很有用的。
-
显式地创建多级索引
你可以用 pd.MultiIndex 中的类方法更加灵活地构建多级索引。例如,就像前面介绍的,你可以通过一个有不同等级的若干简单数组组成的列表来构建 MultiIndex:
print(pd.MultiIndex.from_arrays([['a','a','b','b'],[1,2,1,2]])) ''' MultiIndex(levels=[['a', 'b'], [1, 2]], labels=[[0, 0, 1, 1], [0, 1, 0, 1]]) '''
也可以通过包含多个索引值的元组构成的列表创建 MultiIndex:
print(pd.MultiIndex.from_tuples([('a',1),('a',2),('b',1),('b',2)])) ''' MultiIndex(levels=[['a', 'b'], [1, 2]], labels=[[0, 0, 1, 1], [0, 1, 0, 1]]) '''
还可以用两个索引的笛卡尔积创建 MultiIndex:
print(pd.MultiIndex.from_product([['a','b'],[1,2]])) ''' MultiIndex(levels=[['a', 'b'], [1, 2]], labels=[[0, 0, 1, 1], [0, 1, 0, 1]]) '''
更可以直接提供 levels(包含每个等级的索引值列表的列表)和 labels(包含每个索引值标签列表的列表)创建 MultiIndex:
print(pd.MultiIndex(levels=[['a','b'],[1,2]], labels=[[0,0,1,1],[0,1,0,1]])) ''' MultiIndex(levels=[['a', 'b'], [1, 2]], labels=[[0, 0, 1, 1], [0, 1, 0, 1]]) '''
在创建 Sereis 或 DataFrame 时,可以将这些对象作为 index 参数,或者通过 reindex 方法更新 Sereis 或 DataFrame 的索引。
-
多级索引的等级名称
给 MultiIndex 的等级加上名称会为一些操作提供便利。你可以在前面任何一个 MultiIndex 构造器中通过 names 参数设置等级名称,也可以在创建之后通过索引的 names 属性来修改名称:
pop.index.names = ['state','year'] print(pop) ''' state year California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 dtype: int64 '''
-
多级索引列
每个 DataFrame 的行与列都是对称的,也就是说既然有多级行索引,那么同样可以有多级列索引。
#多级行列索引 index = pd.MultiIndex.from_product([[2013,2014],[1,2]], names=['year','visit']) colnums = pd.MultiIndex.from_product([['Bob','Guido','Sue'],['HR','Temp']], names=['subject','type']) #模拟数据 data = np.round(np.random.randn(4,6),1) data[:,::2] *= 10 data += 37 #创建 DataFrame health_data = pd.DataFrame(data,index=index,columns=colnums) print(health_data) ''' subject Bob Guido Sue type HR Temp HR Temp HR Temp year visit 2013 1 37.0 38.4 40.0 35.9 31.0 38.7 2 33.0 37.3 40.0 37.8 33.0 37.7 2014 1 34.0 36.4 33.0 37.4 31.0 37.4 2 41.0 37.9 33.0 36.4 47.0 37.2 '''
多级行列索引的创建非常简单。上面创建了一个简易的四维数据,四个维度分别为被检查人的姓名、检查项目、检查年份和检查次数。可以在列索引的第一级查询姓名,从而获取包含一个人全部检查信息的 DataFrame:
print(health_data['Guido']) ''' type HR Temp year visit 2013 1 35.0 37.7 2 30.0 37.0 2014 1 41.0 38.6 2 33.0 37.8 '''
6.3、多级索引的取值与切片
-
Series 多级索引
print(pop) ''' state year California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 dtype: int64 '''
可以通过对多个级别索引值获取单个元素:
print(pop['California',2000]) # 33871648
MultiIndex 也支持局部取值,即只取索引值的某一个层级。假如只取最高级的索引,获得的结果是一个新的 Series,未被选中的低层索引值会被保留:
print(pop['California']) ''' year 2000 33871648 2010 37253956 dtype: int64 '''
类似的还有局部切片,不过要求 MultiIndex 是按顺序排列的:
print(pop['California':'New York']) ''' state year California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 dtype: int64 '''
如果索引已经排好序,那么可以用较低层级的索引取值,第一层级的索引可以用空切片:
print(pop[:,2000]) ''' state California 33871648 New York 18976457 Texas 20851820 dtype: int64 '''
其他取值与数据选择的方法也都起作用。下面的例子是通过布尔掩码选择数据的:
print(pop[pop > 22000000]) ''' state year California 2000 33871648 2010 37253956 Texas 2010 25145561 dtype: int64 '''
也可以用花哨的索引选择数据:
print(pop[['California','Texas']]) ''' state year California 2000 33871648 2010 37253956 Texas 2000 20851820 2010 25145561 dtype: int64 '''
-
DataFrame 多级索引
DataFrame 多级索引的用法与 Series 类似。
print(health_data) ''' subject Bob Guido Sue type HR Temp HR Temp HR Temp year visit 2013 1 42.0 37.2 37.0 35.3 33.0 36.5 2 10.0 35.9 38.0 35.4 2.0 36.3 2014 1 39.0 37.2 21.0 36.2 32.0 37.5 2 37.0 35.8 35.0 36.7 54.0 36.8 '''
由于 DataFrame 的基本索引是列索引,因此 Series 中多级索引的用法到了 DataFrame 中就应用到了列上。
print(health_data['Guido','HR']) ''' year visit 2013 1 43.0 2 42.0 2014 1 39.0 2 47.0 Name: (Guido, HR), dtype: float64 '''
与单索引类似,前面的 loc、iloc 和 ix 索引器都可以使用,例如:
print(health_data.iloc[:2,:2]) ''' subject Bob type HR Temp year visit 2013 1 34.0 36.4 2 37.0 36.2 '''
虽然这些索引器将多维数据当做二维数据处理,但是在 loc 和 iloc 中可以传递多个层级的索引元组,例如:
print(health_data.loc[:,('Bob','HR')]) ''' year visit 2013 1 43.0 2 25.0 2014 1 36.0 2 51.0 Name: (Bob, HR), dtype: float64 '''
这种索引元组的用法不是很方便,如果在元组中使用切片还会导致语法错误。
虽然你可以用 Python 内置的 slice() 函数获取想要的切片,但是还有一种更好的办法,就是使用 IndexSlice 对象。
idx = pd.IndexSlice print(health_data.loc[idx[:,1],idx[:,'HR']]) ''' subject Bob Guido Sue type HR HR HR year visit 2013 1 35.0 52.0 38.0 2014 1 52.0 19.0 36.0 '''
6.4、多级索引行列转换
-
有序的索引和无序的索引
如果 MultiIndex 不是有序的索引,那么大多数切片操作都会失败。
首先创建一个不按字典顺序排列的多级索引 Series:
index = pd.MultiIndex.from_product([['a','c','b'],[1,2]]) data = pd.Series(np.random.rand(6),index=index) data.index.names = ['char','int'] print(data) ''' char int a 1 0.844849 2 0.650447 c 1 0.903680 2 0.804958 b 1 0.930337 2 0.893623 dtype: float64 '''
如果想对索引使用局部切片,那么错误就会出现:
try: data['a':'b'] except KeyError as e: print(type(e)) print(e) ''' <class 'pandas.errors.UnsortedIndexError'> 'Key length (1) was greater than MultiIndex lexsort depth (0)' '''
尽管从错误信息里面看不出具体细节,但问题是出在 MultiIndex 无序排列上。局部切片和许多其他相似的操作都要求 MultiIndex 的各级索引是有序的(即按照字典顺序由 A 至 Z)。为此,Pandas 提供了许多便捷的操作完成排序,如 sort_index() 和 sortlevel() 方法。
data = data.sort_index() print(data) ''' char int a 1 0.087304 2 0.197809 b 1 0.387641 2 0.016840 c 1 0.360499 2 0.237509 dtype: float64 '''
索引排序后,局部切片就可以正常使用了:
print(data['a':'b']) ''' char int a 1 0.088690 2 0.718980 b 1 0.323797 2 0.798816 dtype: float64 '''
-
索引 stack 和 unstack
我们可以将一个多级索引数据集转换成简单的二维形式,可以通过 level 参数设置转换的索引层级:
print(pop.unstack(level=0)) ''' state California New York Texas year 2000 33871648 18976457 20851820 2010 37253956 19378102 25145561 '''
print(pop.unstack(level=1)) ''' year 2000 2010 state California 33871648 37253956 New York 18976457 19378102 Texas 20851820 25145561 '''
unstack() 是 stack() 的逆操作,同时使用这两种方法让数据保持不变:
print(pop.unstack().stack()) ''' state year California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 dtype: int64 '''
-
索引的设置与重置
层级数据维度转换的另一种方法是行列标签转换,可以通过 reset_index 方法实现。如果在上面的人口数据 Series 中使用该方法,则会生成一个列标签中包含之前行索引标签 state 和 year 的 DataFrame。也可以用 name 属性为列设置名称:
pop_flat = pop.reset_index(name='population') print(pop_flat) ''' state year population 0 California 2000 33871648 1 California 2010 37253956 2 New York 2000 18976457 3 New York 2010 19378102 4 Texas 2000 20851820 5 Texas 2010 25145561 '''
在解决实际问题的时候,如果能将类似这样的原始输入数据的列直接转换成 MultiIndex,通常大有裨益。其实可以通过 DataFrame 的 set_index 方法实现,返回结果就会是一个带多级索引的 DataFrame:
print(pop_flat.set_index(['state','year'])) ''' population state year California 2000 33871648 2010 37253956 New York 2000 18976457 2010 19378102 Texas 2000 20851820 2010 25145561 '''
6.5、多级索引的数据累计方法
对于层级索引数据,可以设置参数 level 实现对数据子集的累计操作。
print(health_data)
'''
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 28.0 36.5 30.0 37.3 34.0 36.7
2 18.0 37.6 29.0 38.2 24.0 37.6
2014 1 17.0 36.2 48.0 37.0 40.0 35.5
2 49.0 37.4 21.0 36.9 45.0 37.8
'''
如果你需要计算每一年各项指标的平均值,那么可以将参数 level 设置为索引 year:
data_mean = health_data.mean(level='year')
print(data_mean)
'''
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year
2013 32.0 36.85 49.0 36.65 43.5 36.75
2014 40.5 36.85 48.5 37.55 48.0 37.45
'''
如果再设置 axis 参数,就可以对列索引进行类似的累计操作了:
print(data_mean.mean(axis=1,level='type'))
'''
type HR Temp
year
2013 39.166667 36.966667
2014 42.666667 36.300000
'''
这种语法其实是 GroupBy 功能的快捷方式。