Pandas的使用(上)
在这一章,我们介绍Pandas这个库的使用,包括Pandas的基本情况,Series和DataFrame这两大数据结构,以及Pandas的文件操作。与NumPy一样,Pandas也是一系列数据分析功能的基础,很多数据分析的工具都是建立在Series和DataFrame格式的数据基础之上。
Pandas基本情况
Pandas是基于NumPy的一个库,是为了解决数据分析任务而创建的,含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。Pandas这个名字源于panel data(面板数据,这是结构化数据集在计量经济学中的术语)以及Python data analysis(Python数据分析)。
Pandas经常和其它工具一同使用,如数值计算工具NumPy和SciPy,统计分析库statsmodels,机器学习库scikit-learn和数据可视化库matplotlib。Pandas保留了NumPy中基于数组的函数和不使用for循环的数据处理。虽然Pandas采用了大量的NumPy编码风格,但二者最大的不同是Pandas是专门为处理表格和混杂数据设计的,而NumPy更适合处理统一的数值数组。
Pandas由Wes McKinney于2008年开发,当时他任职于AQR Capital Management这家量化投资管理公司。他发现有许多工作需求都不能用任何单一的工具解决:
- 有标签轴的数据结构,支持自动或清晰的数据对齐。这可以防止由于数据不对齐,或处理来源不同的索引不同的数据,所造成的错误;
- 集成时间序列功能;
- 相同的数据结构用于处理时间序列数据和非时间序列数据;
- 保存元数据的算术运算和压缩;
- 灵活处理缺失数据;
- 合并和其它流行数据库(例如基于SQL的数据库)的关系操作。
为此他开发了Pandas,来满足他在工作中的需求。自从2010年Pandas开源以来,Pandas逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了800个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。
Pandas使用方式
1.1 Pandas引入方式
Pandas惯用的缩写是pd:
import pandas as pd import numpy as np print(pd.__version__)
这里我使用的是0.24.2版本,不同版本的Pandas之间稍有差异。
1.2 Series
Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:
s= pd.Series([1, 2, 3]) print(s)
输出为:
0 1 1 2 2 3 dtype: int64
当我们打印Series变量时,可以看到左边有一列表示索引,右边有一列表示具体的数值,下面还有一行表示数值的数据类型。由于我们没有给数据指定索引,Series会自动创建一个从0到N-1(N是数据的长度)的整数型索引。我们也可以通过Series的values和index属性获取对应的ndarray数组和索引对象:
print(s.values) print(s.index)
输出为:
[1 2 3] RangeIndex(start=0, stop=3, step=1)
当我们需要指明索引时,可以在创建Series时指明index参数,但是需要注意索引的个数与元素的个数要相同:
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) print(s) print(s.index)
输出为:
a 1 b 2 c 3 dtype: int64 Index(['a', 'b', 'c'], dtype='object')
与NumPy数组相比,我们可以通过索引的方式选取Series中的单个或一组值(有点类似字典中按照key取value):
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) print(s['a']) # 取出对应索引a的数值1,结果是一个整数值 s['b'] = 5 # 修改索引b对应的值,从2改为5 print(s[['a', 'b', 'c']]) # 通过一个列表取出对应a,b和c这三个索引的元素值,结果仍然是一个Series
输出为:
1 a 1 b 5 c 3 dtype: int64
对一个Series变量使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值:
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) print(s[s > 2]) print(s * 2) print(np.exp(s))
输出为:
c 3 dtype: int64 a 2 b 4 c 6 dtype: int64 a 2.718282 b 7.389056 c 20.085537 dtype: float64
我们还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。我们可以把Series用到一些需要字典变量的函数中:
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) print('a' in s) # 判断a是否在s的索引中,有点类似判断字典是否有某一个key
输出为:
True
既然Series和字典这么类似,我们其实也可以从一个字典创建Series:
d = {'a': 1, 'b': 2, 'c': 3} s = pd.Series(d) print(s)
输出为:
a 1 b 2 c 3 dtype: int64
我们也可以用index参数指定顺序:
d = {'a': 1, 'b': 2, 'c': 3} s = pd.Series(d, index=['b', 'c', 'd']) # 这里index参数的值不一定要与d.keys()完全一致 print(s)
输出为:
b 2.0 c 3.0 d NaN dtype: float64
在上面的结果中a对应的值1没有被用到,而字典中并没有对应键d的值,因此在Series中表示为NaN, not a number,表示缺失值。在pandas中可以使用isnull或者notnull来判断是否有缺失值:
print(pd.isnull(s)) # 或者使用结果反过来的notnull # print(pd.notnull(s))
输出为:
b False c False d True dtype: bool
为什么要费力加上索引值呢?这样在数据运算过程中就可以自动对齐,如下例所示:
s1 = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']) s2 = pd.Series([5, 6, 7, 8], index=['d', 'c', 'b', 'a']) s3 = s1 + s2 print(s1) print(s2) print(s3)
输出为:
a 1 b 2 c 3 d 4 dtype: int64 d 5 c 6 b 7 a 8 dtype: int64 a 9 b 9 c 9 d 9 dtype: int64
Series对象本身及其索引都有一个name属性,在我们指明之前,这些属性都是None:
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']) print(s) print(s.name) print(s.index.name) print() s.name = 'test' s.index.name = 'key' print(s)
输出为:
a 1 b 2 c 3 d 4 dtype: int64 None None key a 1 b 2 c 3 d 4 Name: test, dtype: int64
我们也可以通过赋值的方式直接修改Series的索引:
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']) print(s) s.index = ['d', 'c', 'b', 'a'] print(s)
输出为:
a 1 b 2 c 3 d 4 dtype: int64 d 1 c 2 b 3 a 4 dtype: int64
1.3 DataFrame
DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。DataFrame中的数据在内存中是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。创建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典:
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} frame = pd.DataFrame(data) print(frame)
输出为:
state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 5 Nevada 2003 3.2
从上面的结果可以看到,跟Series一样,DataFrame会自动加上行索引。另一种常见的数据形式是嵌套字典。外层字典的键作为列索引,内层键则作为行索引:
pop = {'Nevada': {2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}} frame = pd.DataFrame(pop) print(frame)
输出为:
Nevada Ohio 2000 NaN 1.5 2001 2.4 1.7 2002 2.9 3.6
对于特别大的DataFrame,head函数默认会选取前五行,或者通过传入参数来指明选取的行数:
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} frame = pd.DataFrame(data) print(frame.head()) print() print(frame.head(3))
输出为:
state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6
如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print(frame)
输出为:
year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2
如果传入的列在原始数据中不存在,就会产生缺失值:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop', 'size']) print(frame)
输出为:
year state pop size 0 2000 Ohio 1.5 NaN 1 2001 Ohio 1.7 NaN 2 2002 Ohio 3.6 NaN 3 2001 Nevada 2.4 NaN 4 2002 Nevada 2.9 NaN 5 2003 Nevada 3.2 NaN
我们也可以指明行索引:
frame = pd.DataFrame(data, index=['a', 'b', 'c', 'd', 'e', 'f']) print(frame)
输出为:
state year pop a Ohio 2000 1.5 b Ohio 2001 1.7 c Ohio 2002 3.6 d Nevada 2001 2.4 e Nevada 2002 2.9 f Nevada 2003 3.2
当我们想要获取某一列数据的时候,有两种方法:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print(frame['state']) # 类似字典按键取值的方法,比较通用 print(frame.year) # 要求列名符合python的变量命名规则,即由字母、数字、下划线组成,不能以数字开头
输出为:
0 Ohio 1 Ohio 2 Ohio 3 Nevada 4 Nevada 5 Nevada Name: state, dtype: object 0 2000 1 2001 2 2002 3 2001 4 2002 5 2003 Name: year, dtype: int64
DataFrame的每一列都是一个Series,并且这个Series的name就是对应DataFrame中的列名,索引沿用了DataFrame的行索引。
当我们想要获取一行数据的时候,可以通过位置或名称的方式进行获取,比如用iloc属性:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print(frame.iloc[0]) # 下标从0开始计数
输出为:
year 2000 state Ohio pop 1.5 Name: 0, dtype: object
当我们想要修改DataFrame的一列数值时,可以直接采用赋值的方式将一列数据赋予一个值。如果要赋值的列原来不存在,DataFrame会新建一列:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print('---before---') print(frame) frame['year'] = 2019 # 把year那列所有值改成2019 frame['size'] = 111 # 新建一列size,所有值都是111,另外注意我们没有办法通过frame.size=111的方式新建一列 print('---after---') print(frame)
输出为:
---before--- year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2 ---after--- year state pop size 0 2019 Ohio 1.5 111 1 2019 Ohio 1.7 111 2 2019 Ohio 3.6 111 3 2019 Nevada 2.4 111 4 2019 Nevada 2.9 111 5 2019 Nevada 3.2 111
我们也可以通过一个列表或者一个Series更新某一列的值,但是在使用列表时,要求列表的元素个数与每一列包含的元素个数相同;而在使用Series赋值时,则会按照行索引和Series的索引进行对应,如果有缺失值,会表示为NaN:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print('---before---') print(frame) frame['year'] = [2019] * 6 frame['pop'] = pd.Series(np.arange(5)) # 这个Series只包含5个值,默认整数索引为0到4,因此行索引为5的那行数据表示为NaN print('---after---') print(frame)
输出为:
---before--- year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2 ---after--- year state pop 0 2019 Ohio 0.0 1 2019 Ohio 1.0 2 2019 Ohio 2.0 3 2019 Nevada 3.0 4 2019 Nevada 4.0 5 2019 Nevada NaN
del关键字可以用来删除某一列,这种删除会直接体现在我们原本的DataFrame上(而不是某一个副本上):
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print('---before---') print(frame) del frame['state'] print('---after---') print(frame)
输出为:
---before--- year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2 ---after--- year pop 0 2000 1.5 1 2001 1.7 2 2002 3.6 3 2001 2.4 4 2002 2.9 5 2003 3.2
类似NumPy的ndarray,我们也可以对DataFrame进行转置,这样行索引和列索引就会换过来:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop'], index=['one', 'two', 'three', 'four', 'five', 'six']) print('---before---') print(frame) print('---after---') print(frame.T)
输出为:
---before--- year state pop one 2000 Ohio 1.5 two 2001 Ohio 1.7 three 2002 Ohio 3.6 four 2001 Nevada 2.4 five 2002 Nevada 2.9 six 2003 Nevada 3.2 ---after--- one two three four five six year 2000 2001 2002 2001 2002 2003 state Ohio Ohio Ohio Nevada Nevada Nevada pop 1.5 1.7 3.6 2.4 2.9 3.2
跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据。如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型:
frame = pd.DataFrame(data, columns=['year', 'state', 'pop']) print(frame.values) print(frame.values.dtype)
输出为:
[[2000 'Ohio' 1.5] [2001 'Ohio' 1.7] [2002 'Ohio' 3.6] [2001 'Nevada' 2.4] [2002 'Nevada' 2.9] [2003 'Nevada' 3.2]] object
1.4 索引对象
pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:
obj = pd.Series(range(3), index=['a', 'b', 'c']) index = obj.index print(index) print(index[1:]) # Index支持类似列表的切片 # 与列表不同,Index是不可变的,这种不可变性可以使Index对象在多个数据结构之间安全共享 # index[1] = 'd' # 试图修改Index会报错
输出为:
Index(['a', 'b', 'c'], dtype='object') Index(['b', 'c'], dtype='object')
Pandas的Index支持重复值,即可以包含重复的标签,选择重复的标签,会显示所有的结果:
obj = pd.Series(range(3), index=['a', 'a', 'c']) print(obj['a'])
输出为:
a 0 a 1 dtype: int64
1.5 数据分析的基本功能
1.5.1 重新索引
pandas的Series和DataFrame的索引都可以都可以通过reindex函数进行重新索引。reindex函数会返回一个新的对象:
obj = pd.Series(range(3), index=['a', 'c', 'b']) obj1 = obj.reindex(index=['c', 'b', 'a', 'd']) # 或者直接写成reindex(['c', 'b', 'a', 'd']) print(obj) # obj没有被改动 print() print(obj1) # 如果某一个索引值不存在,会表示为缺失值
输出为:
a 0 c 1 b 2 dtype: int64 c 1.0 b 2.0 a 0.0 d NaN dtype: float64
为了避免NaN,我们也可以用参数method指明填充方法,例如ffill(forward fill):
obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4]) print(obj.reindex(range(6))) print() print(obj.reindex(range(6), method='ffill'))
输出为:
0 blue 1 NaN 2 purple 3 NaN 4 yellow 5 NaN dtype: object 0 blue 1 blue 2 purple 3 purple 4 yellow 5 yellow dtype: object
或者我们也可以用fill_value参数指明一个填充值:
obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4]) print(obj.reindex(range(6), fill_value='red'))
输出为:
0 blue 1 red 2 purple 3 red 4 yellow 5 red dtype: object
对DataFrame而言,我们既可以修改行索引,也可以修改列索引:
frame = pd.DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'], columns=['Ohio', 'Texas', 'California']) print(frame) print() frame1 = frame.reindex(index=['a', 'b', 'c', 'd'], columns=['Texas', 'Utah', 'California']) print(frame1)
输出为:
Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 Texas Utah California a 1.0 NaN 2.0 b NaN NaN NaN c 4.0 NaN 5.0 d 7.0 NaN 8.0
1.5.2 丢弃指定轴上的项
我们可以用drop函数删除某一行(列)或者某几行(列)的数据,它会返回一个新对象:
obj = pd.Series(range(3), index=['a', 'b', 'c']) obj1 = obj.drop('c') print(obj) # obj本身没有变化 print() print(obj1) print() del obj1['a'] # 回顾一下前面提到的del关键词,它会直接在obj1上进行操作 print(obj1)
输出为:
a 0 b 1 c 2 dtype: int64 a 0 b 1 dtype: int64 b 1 dtype: int64
对于DataFrame,我们可以用axis指明删除行还是列:
frame = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utah', 'New York'], columns=['one', 'two', 'three', 'four']) print(frame) print() frame1 = frame.drop(['Ohio', 'Utah']) # 默认axis=0,即删除一行 print(frame1) print() frame2 = frame.drop(['two', 'four'], axis=1) #或者指明axis='columns'来删除一列 print(frame2)
输出为:
one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 one two three four Colorado 4 5 6 7 New York 12 13 14 15 one three Ohio 0 2 Colorado 4 6 Utah 8 10 New York 12 14
如果我们不想创建一个新的对象作为删除后的结果,我们也可以用inplace=True来指明在原来的对象上“原地”操作:
frame.drop(['Ohio', 'Utah'], inplace=True) print(frame) # frame发生了变化,不过要小心这种方法对于原始数据的破坏
输出为:
one two three four Colorado 4 5 6 7 New York 12 13 14 15
1.5.3 索引、选取和过滤
Series索引(obj[...])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是代表第几行的整数,还可以是行索引值:
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd']) print(obj) print(obj[1]) print(obj['b']) # 上面这两行结果相同 print() print(obj[1:3]) print(obj['b':'d']) # 小心:这两行结果不一样,这是因为利用标签的切片运算与普通的Python切片运算不同,其末端元素是包含的 print() print(obj[obj < 2]) # 布尔型索引
输出为:
a 0.0 b 1.0 c 2.0 d 3.0 dtype: float64 1.0 1.0 b 1.0 c 2.0 dtype: float64 b 1.0 c 2.0 d 3.0 dtype: float64 a 0.0 b 1.0 dtype: float64
DataFrame的索引也是类似的:
frame = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utah', 'New York'], columns=['one', 'two', 'three', 'four']) print(frame[0:2]) print(frame['Ohio':'Utah']) # 同样要小心用整数和用行索引得到的结果相差一行 print() print(frame[['one', 'two']]) # 通过列索引
输出为:
one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 one two Ohio 0 1 Colorado 4 5 Utah 8 9 New York 12 13
1.5.4 用loc和iloc进行选取
对于DataFrame的标签索引,pandas的开发团队建议用loc和iloc运算符分别处理严格基于标签和整数的索引。
loc通过标签选择一行和多列:
frame = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utah', 'New York'], columns=['one', 'two', 'three', 'four']) print(frame) print() print(frame.loc['Colorado', ['two', 'three']]) # 通过标签选择一行和多列
输出为:
one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 two 5 three 6 Name: Colorado, dtype: int32
iloc用整数下标进行选取:
frame = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utah', 'New York'], columns=['one', 'two', 'three', 'four']) print(frame) print() print(frame.iloc[1, [1, 2]]) # 得到上面一样的结果
输出为:
one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 two 5 three 6 Name: Colorado, dtype: int32
这两个索引函数也适用于一个标签或多个标签的切片:
frame = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utah', 'New York'], columns=['one', 'two', 'three', 'four']) print(frame) print() print(frame.loc[:'Utah', 'two']) # 挑选到Utah的每一行(包括Utah),two对应的那一列 print() print(frame.iloc[:, :3][frame.three > 5]) # 先挑选每一行,前3列,然后再挑出第3列大于5的那些行
输出为:
one two three four Ohio 0 1 2 3 Colorado 4 5 6 7 Utah 8 9 10 11 New York 12 13 14 15 Ohio 1 Colorado 5 Utah 9 Name: two, dtype: int32 one two three Colorado 4 5 6 Utah 8 9 10 New York 12 13 14
1.5.5 算术运算和数据对齐
前面提到,两个Series相加,会按照索引值进行对齐。如果索引值不完全相同,运算的结果中就会包含缺失值,即NaN:
s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e']) s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g']) print(s1) print(s2) print() print(s1 + s2)
输出为:
a 7.3 c -2.5 d 3.4 e 1.5 dtype: float64 a -2.1 c 3.6 e -1.5 f 4.0 g 3.1 dtype: float64 a 5.2 c 1.1 d NaN e 0.0 f NaN g NaN dtype: float64
对于DataFrame,对齐操作会同时发生在行和列上:
df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), index=['Ohio', 'Texas', 'Colorado']) df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) print(df1) print(df2) print() print(df1 + df2)
输出为:
b c d Ohio 0.0 1.0 2.0 Texas 3.0 4.0 5.0 Colorado 6.0 7.0 8.0 b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 b c d e Colorado NaN NaN NaN NaN Ohio 3.0 NaN 6.0 NaN Oregon NaN NaN NaN NaN Texas 9.0 NaN 12.0 NaN Utah NaN NaN NaN NaN
我们可能会需要在出现NaN的地方填充一个默认值,例如0。那么我们可以利用add函数和fill_value参数:
a = pd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) b = pd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) print(a) # a缺失索引值为d和e的值 print(b) # b缺失索引值为b和e的值 print() print(a.add(b, fill_value=0)) # b和d的值会被补上0,但是对于a和b都缺失的e的值,在结果中仍然是缺失的
输出为:
a 1.0 b 1.0 c 1.0 d NaN dtype: float64 a 1.0 b NaN d 1.0 e NaN dtype: float64 a 2.0 b 1.0 c 1.0 d 1.0 e NaN dtype: float64
1.5.6 DataFrame和Series之间的运算
回顾之前不同维度的NumPy ndarray之间的运算,会采用广播的方式进行补全后再运算。DataFrame和Series之间的运算也是类似的。默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:
frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) series = frame.iloc[0] # 取出第一行作为Series print(frame) print() print(series) print() print(frame - series)
输出为:
b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 b 0.0 d 1.0 e 2.0 Name: Utah, dtype: float64 b d e Utah 0.0 0.0 0.0 Ohio 3.0 3.0 3.0 Texas 6.0 6.0 6.0 Oregon 9.0 9.0 9.0
如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:
frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) series2 = pd.Series(range(3), index=['b', 'e', 'f']) print(frame) print() print(series2) print() print(frame + series2)
输出为:
b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 b 0 e 1 f 2 dtype: int64 b d e f Utah 0.0 NaN 3.0 NaN Ohio 3.0 NaN 6.0 NaN Texas 6.0 NaN 9.0 NaN Oregon 9.0 NaN 12.0 NaN
如果我们希望匹配行且在列上广播,则必须使用算术运算函数,而不是简单的'+','-'等。例如:
frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) series3 = frame['d'] # 取出某一列作为Series print(frame) print() print(series3) print() print(frame.sub(series3, axis='index')) # 匹配DataFrame的行索引(axis='index' or axis=0)并进行广播
输出为:
b d e Utah 0.0 1.0 2.0 Ohio 3.0 4.0 5.0 Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 Utah 1.0 Ohio 4.0 Texas 7.0 Oregon 10.0 Name: d, dtype: float64 b d e Utah -1.0 0.0 1.0 Ohio -1.0 0.0 1.0 Texas -1.0 0.0 1.0 Oregon -1.0 0.0 1.0
1.5.7 函数应用和映射
NumPy的通用函数ufuncs(元素级数组方法)也可用于操作pandas对象:
frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) print(frame) print() print(np.abs(frame))
输出为:
b d e Utah -1.301935 0.034308 -0.818884 Ohio -0.355259 1.772152 -0.319957 Texas 0.085236 -1.096262 0.732062 Oregon 0.143712 0.136601 -0.217775 b d e Utah 1.301935 0.034308 0.818884 Ohio 0.355259 1.772152 0.319957 Texas 0.085236 1.096262 0.732062 Oregon 0.143712 0.136601 0.217775
另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能。在下面的例子中,函数f计算了一个Series的最大值和最小值的差,在frame的每列都执行了一次。结果是一个Series,使用frame的列作为索引。
f = lambda x: x.max() - x.min() print(frame.apply(f))
输出为:
b 1.445647 d 2.868414 e 1.550946 dtype: float64
如果传递参数axis='columns'到apply,这个函数会在每行执行:
print(frame.apply(f, axis='columns'))
输出为:
Utah 1.336243 Ohio 2.127410 Texas 1.828324 Oregon 0.361487 dtype: float64
传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:
def f(x): return pd.Series([x.min(), x.max()], index=['min', 'max']) print(frame.apply(f))
输出为:
b d e min -1.301935 -1.096262 -0.818884 max 0.143712 1.772152 0.732062
如果我们要对Series中的每个元素应用一个函数,可以用map函数:
format = lambda x: '%.2f' % x # 格式化字符串,取小数点后两位 print(frame['e'].map(format)) # 取frame的一列(即一个Series)应用map函数
输出为:
Utah -0.82 Ohio -0.32 Texas 0.73 Oregon -0.22 Name: e, dtype: object
如果我们要对DataFrame中的每个元素应用一个函数,可以用applymap函数:
print(frame.applymap(format))
输出为:
b d e Utah -1.30 0.03 -0.82 Ohio -0.36 1.77 -0.32 Texas 0.09 -1.10 0.73 Oregon 0.14 0.14 -0.22
1.5.8 排序和排名
如果我们要对Series和DataFrame按照索引排序,会用到sort_index函数,它会返回一个已经排好序的结果:
obj = pd.Series(range(4), index=['d', 'a', 'b', 'c']) print(obj.sort_index())
输出为:
a 1 b 2 c 3 d 0 dtype: int64
对于DataFrame,则可以根据任意一个轴上的索引进行排序:
frame = pd.DataFrame(np.arange(8).reshape((2, 4)), index=['three', 'one'], columns=['d', 'a', 'b', 'c']) print(frame.sort_index()) # 默认按照行索引排序 print() print(frame.sort_index(axis='columns')) # 或者axis=1, 指明按照列索引排序
输出为:
d a b c one 4 5 6 7 three 0 1 2 3 a b c d three 1 2 3 0 one 5 6 7 4
数据默认是按升序排序的,但也可以降序排序:
print(frame.sort_index(axis=1, ascending=False)) # 指明升序的参数ascending为False
输出为:
d c b a three 0 3 2 1 one 4 7 6 5
如果要按值排序,则可以使用sort_values函数:
obj = pd.Series([4, 7, -3, 2, np.nan]) # 如果有np.nan,会被放到最后,不管升序还是降序 print(obj.sort_values()) print() print(obj.sort_values(ascending=False))
输出为:
2 -3.0 3 2.0 0 4.0 1 7.0 4 NaN dtype: float64 1 7.0 0 4.0 3 2.0 2 -3.0 4 NaN dtype: float64
当排序一个DataFrame时,我们可能会希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的:
frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]}) print(frame.sort_values(by='b'))
输出为:
b a 2 -3 0 3 2 1 0 4 0 1 7 1
要根据多个列进行排序,传入名称的列表即可:
print(frame.sort_values(by=['a', 'b'])) # 会优先按照a排序,然后当a相同时,再按照b排序 print() print(frame.sort_values(by=['b', 'a']))
输出为:
b a 2 -3 0 0 4 0 3 2 1 1 7 1 b a 2 -3 0 3 2 1 0 4 0 1 7 1
有的时候我们不关心数值的绝对大小,而是关心相对排名,这个时候就会用到rank函数:
obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) print(obj.rank())
输出为:
0 6.5 1 1.0 2 6.5 3 4.5 4 3.0 5 2.0 6 4.5 dtype: float64
默认情况下,会按照从小到大的顺序排出不同数据对应的排名,如果出现数值相同的情况,rank函数默认会给这些数据分配平均排名,因此会出现4.5或6.5这样的排名。我们也可以指明参数method='first'来根据值在原数据中出现的顺序给出排名,例如下面的结果中,数据中标签0位于标签2的前面,之前的两个6.5排名变成了6和7:
obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) print(obj.rank(method='first'))
输出为:
0 6.0 1 1.0 2 7.0 3 4.0 4 3.0 5 2.0 6 5.0 dtype: float64
我们也可以按降序进行排名:
obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) print(obj.rank(ascending=False, method='first')) # 同样是指明ascending参数
输出为:
0 1.0 1 7.0 2 2.0 3 3.0 4 5.0 5 6.0 6 4.0 dtype: float64
DataFrame可以在行或列上计算排名:
frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 'c': [-2, 5, 8, -2.5]}) print(frame) print() print(frame.rank(axis='columns')) # frame的每一行进行了排名 print() print(frame.rank(axis='rows')) # frame的每一列进行了排名
输出为:
b a c 0 4.3 0 -2.0 1 7.0 1 5.0 2 -3.0 0 8.0 3 2.0 1 -2.5 b a c 0 3.0 2.0 1.0 1 3.0 1.0 2.0 2 1.0 2.0 3.0 3 3.0 2.0 1.0 b a c 0 3.0 1.5 2.0 1 4.0 3.5 3.0 2 1.0 1.5 4.0 3 2.0 3.5 1.0
1.6 汇总和计算描述统计
类似NumPy的ndarray,我们也经常需要对Series和DataFrame数据进行一些统计和计算。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。
df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5], [np.nan, np.nan], [0.75, -1.3]], index=['a', 'b', 'c', 'd'], columns=['one', 'two']) print(df) print() print(df.sum()) # 进行求和时会计算非NaN的值
输出为:
one two a 1.40 NaN b 7.10 -4.5 c NaN NaN d 0.75 -1.3 one 9.25 two -5.80 dtype: float64
传入axis='columns'或axis=1将会按行进行求和运算:
print(df.sum(axis=1)) # 全部是NaN的第三行结果为0.00
输出为:
a 1.40 b 2.60 c 0.00 d -0.55 dtype: float64
默认NaN会被跳过,通过skipna选项可以禁用该功能:
print(df.sum(axis=1, skipna=False))
输出为:
a NaN b 2.60 c NaN d -0.55 dtype: float64
其他类似的函数还包括min、max等。
还有一些函数会给出累计值,例如累积求和cumsum:
print(df.cumsum())
输出为:
one two a 1.40 NaN b 8.50 -4.5 c NaN NaN d 9.25 -5.8
还有一种既不是约简型也不是累计型,例如describe函数,它用于一次性产生多个汇总统计:
print(df.describe())
输出为:
one two count 3.000000 2.000000 mean 3.083333 -2.900000 std 3.493685 2.262742 min 0.750000 -4.500000 25% 1.075000 -3.700000 50% 1.400000 -2.900000 75% 4.250000 -2.100000 max 7.100000 -1.300000
也有一些函数会用到不止一列的信息,例如Series的corr方法用于计算两个Series中重叠的、非NA的、按索引对齐的值的相关系数。与此类似,cov用于计算协方差:
print(df['one'].corr(df['two'])) print(df['one'].cov(df['two']))
输出为:
-1.0 -10.16
DataFrame的corr和cov方法将以DataFrame的形式分别返回完整的相关系数或协方差矩阵:
print(df.corr()) print(df.cov())
输出为:
one two one 1.0 -1.0 two -1.0 1.0 one two one 12.205833 -10.16 two -10.160000 5.12
2 数据加载、存储与文件格式
2.1 读写文本格式的数据
2.1.1 读取文本格式的数据
pandas提供了一些用于将表格型数据(例如txt和csv文件)读取为DataFrame对象的函数,其中read_csv和read_table功能差不多,只是默认的分隔符不一样:read_csv的默认分隔符为逗号,read_table的默认分隔符为制表符('\t')。我们之后会用read_csv更多一些。
df = pd.read_csv('examples/ex1.csv') # ex1.csv文件默认以逗号为分隔符 print(df) print() df1 = pd.read_table('examples/ex1.csv', sep=',') # 如果用read_table函数,指明分隔符为逗号可以实现一样的功能 print(df1)
输出为:
a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo
ex1.csv的内容如下:
对于没有列名的文件,我们可以让pandas为其分配默认的列名,也可以自己定义列名:
df = pd.read_csv('examples/ex2.csv', header=None) # header=None是告诉pandas,这个csv的第一行不是列名,从第一行开始就是数据 print(df) print() df = pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message']) # 直接告诉pandas我们要指明列名 print(df) print() df = pd.read_csv('examples/ex1.csv', names=['a', 'b', 'c', 'd', 'message']) # 如果我们对于前面有列名的ex1.csv文件指明列名,就会有问题 print(df)
输出为:
0 1 2 3 4 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo a b c d message 0 a b c d message 1 1 2 3 4 hello 2 5 6 7 8 world 3 9 10 11 12 foo
ex2.csv的内容如下:
我们也可以指明某一列的值作为行索引:
df = pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'], index_col='message') # 用index_col指明某一列为行索引 print(df)
输出为:
a b c d message hello 1 2 3 4 world 5 6 7 8 foo 9 10 11 12
如果希望将多个列做成一个层次化索引,只需传入由列编号或列名组成的列表即可:
df = pd.read_csv('examples/ex3.csv', index_col=['key1', 'key2']) print(df) print() print(df.loc['one']) print() print(df.loc['one', 'b'])
输出为:
value1 value2 key1 key2 one a 1 2 b 3 4 c 5 6 d 7 8 two a 9 10 b 11 12 c 13 14 d 15 16 value1 value2 key2 a 1 2 b 3 4 c 5 6 d 7 8 value1 3 value2 4 Name: (one, b), dtype: int64
ex3.csv的内容如下:
另外一种常见情况是我们需要跳过csv文件中的某几行,例如注释等等:
df = pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3]) # 可以用skiprows来指明要跳过的行数 print(df) print() # 回顾前面,如果我们对于前面有列名的ex1.csv文件指明列名,可以跳过第一行 df = pd.read_csv('examples/ex1.csv', names=['a', 'b', 'c', 'd', 'e'], skiprows=[0]) print(df)
输出为:
a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo a b c d e 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo
ex4.csv的内容如下:
缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经常出现的标记值进行识别,比如NA及NULL:
df = pd.read_csv('examples/ex5.csv') print(df) print() df = pd.read_csv('examples/ex5.csv', na_values=['one']) # 或者我们也可以用na_values补充说明缺失值的情况,这里我们故意把字符串one说成缺失值 print(df)
输出为:
something a b c d message 0 one 1 2 3.0 4 NaN 1 two 5 6 NaN 8 world 2 three 9 10 11.0 12 foo something a b c d message 0 NaN 1 2 3.0 4 NaN 1 two 5 6 NaN 8 world 2 three 9 10 11.0 12 foo
ex5.csv的内容如下:
2.1.2 逐块读取文本文件
在处理很大的文件时,我们可能会希望先读取文件的开头一部分,或者一块一块地读取。
如果只想读取几行(避免读取整个文件),通过nrows进行指定即可:
df = pd.read_csv('examples/ex1.csv', nrows=1) # 这里作为演示,只读取第一行 print(df)
输出为:
a b c d message 0 1 2 3 4 hello
要逐块读取文件,可以指定chunksize(行数),结果会是一个迭代器,可以用于for循环中:
chunker = pd.read_csv('examples/ex1.csv', chunksize=2) # 这里作为演示,一次读取2行 for df in chunker: print(df) print()
输出为:
a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world a b c d message 2 9 10 11 12 foo
2.1.3 将数据写出到文本格式
利用pandas的to_csv函数,我们可以很方便地将数据写到一个以逗号分隔的文件中:
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} df = pd.DataFrame(data) df.to_csv('examples/ex6.csv')
ex6.csv的内容如下:
可以看到默认的情况下,会有行索引和列索引,我们可以分别通过参数index和header控制写出:
df.to_csv('examples/ex7.csv', index=False, header=False)
ex7.csv的内容如下:
或者我们也可以按照我们想要的顺序写出某些列:
df.to_csv('examples/ex8.csv', index=False, columns=['year', 'state'])
ex8.csv的内容如下:
Series也有类似的to_csv功能:
df['year'].to_csv('examples/ex9.csv') df['year'].to_csv('examples/ex10.csv', index=False)
ex9.csv的内容如下:
ex10.csv的内容如下:
2.2 读写二进制格式的数据
2.2.1 读写pickle文件
Python内置的pickle序列化是实现数据的高效二进制格式存储最简单的办法。pandas提供了read_pickle和to_pickle函数:
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} df = pd.DataFrame(data) df.to_pickle('examples/ex1.pkl') # pickle文件的后缀一般是pkl df1 = pd.read_pickle('examples/ex1.pkl') # 读取pickle文件 print(df1)
输出为:
state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 5 Nevada 2003 3.2
2.2.2 读写Microsoft Excel文件
我们也可以使用read_excel和to_excel函数来实现读写Excel文件:
df = pd.read_excel('examples/ex1.xlsx', 'Sheet1') print(df) df.to_excel('examples/ex2.xlsx')
输出为:
a b c d message 0 1 2 3 4 hello 1 5 6 7 8 world 2 9 10 11 12 foo
ex1.xlsx如下:
ex2.xlsx如下:
小结
在这一章,我们介绍了Pandas中两种最重要的数据结构,Series和DataFrame的基本使用方法,掌握这两种数据结构对于后续的学习非常重要。同时我们也介绍了Pandas的文件读取和写入操作。在实际应用过程中,我们经常会遇到csv格式的数据文件,Pandas能够很好地处理此类文件,给我们的分析工作带来很大的方便。