pandas 实践手册
本篇博客参考自 Python Data Science Handbook 第三章,旨在对 pandas 库的使用方法进行详细介绍。
安装和使用
关于 pandas 的安装可以参考官方教程,官方推荐直接基于 Anaconda 进行安装。安装完成后,我们可以导入 pandas 并查看其版本:
In[1]: import pandas |
与 Numpy 一样,为了使用方便我们会将 Pandas 以别名的形式导入:
In[2]: import pandas as pd |
在接下来的介绍中我们都将使用该导入方式。值得一提的是,在 Jupyter lab 中我们可以通过 Tab 键来进行自动补全,使用问号来查看相关文档,如下所示:
In [3]: pd.<TAB> # 查看可以调用的方法 |
Pandas 对象
本章节将介绍三种基本的 Pandas 对象(数据结构):Series
、DataFrame
和 Index
。我们可以简单地将 Pandas 对象理解为 Numpy 数组的增强版本,其中行与列可以通过标签进行识别,而不仅是简单的数字索引。Pandas 为这些基本数据结构提供了一系列有用的工具与方法。为了小节之间的独立性,每节的最开始会先进行包导入(编号每节独立):
In[1]: import numpy as np |
Series 对象
Series
对象是一个可索引数据的一维数组,我们可以基于列表或数组来创建该对象:
In[2]: data = pd.Series([0.25, 0.5, 0.75, 1.0]) |
如上所示,Series
对象包含了一个值序列和一个索引序列,我们可以分别通过 values
与 index
属性来进行访问。values
属性是一个 Numpy 数组:
In[3]: data.values |
index
属性则是一个类数组的对象,类型为 pd.Index
,将在之后进行介绍:
In[4]: data.index |
和 Numpy 数组类似,我们可以通过方括号输入对应的索引来访问数据:
In[5]: data[1] |
Series 作为广义 Numpy 数组
虽然看起来和一维 Numpy 数组很像,但 Series
对象要比其更加通用和灵活。两者的关键区别在于:Numpy 数组使用隐式定义的数值索引来访问值,而 Series
对象则使用明确定义的索引来访问值。这一明确的索引定义赋予了 Series
对象额外的能力,例如索引不一定是整数,也可以是任意类型的值:
In[7]: data = pd.Series([0.25, 0.5, 0.75, 1.0], |
元素的访问也随之更改:
In[8]: data['b'] |
我们甚至可以使用非连续的索引:
In[9]: data = pd.Series([0.25, 0.5, 0.75, 1.0], |
Series 作为特殊的字典
我们还可以将 Series
看作一种特殊的 Python 字典。字典是一种将任意的键映射到任意的值上的数据结构,而 Series
则是将包含类型信息的键映射到包含类型信息的值上的数据结构。类型信息可以为 Series
提供比普通字典更高效的操作。
我们可以直接基于字典来构建 Series
对象:
In[11]: population_dict = {'California': 38332521, |
可以看到索引即为字典的键(新版 Pandas 中似乎不会对键进行排序以生成索引,而是保持原状)。我们可以像字典一样通过索引访问值,也可以使用字典不支持的切片操作(注意此处的切片会包含尾部):
In[12]: population['California'] |
构建 Series 对象
如上所述,Series
对象的构建方式有很多种,其基本遵循如下形式:
pd.Series(data, index=index) |
其中 index
为可选参数,data
可以是很多数据结构之一,例如:
- 列表或 Numpy 数组:
In[14]: pd.Series([2, 4, 6]) |
- 标量(重复填充):
In[15]: pd.Series(5, index=[100, 200, 300]) |
- 字典(键即为索引):
In[16]: pd.Series({2:'a', 1:'b', 3:'c'}) |
索引还可以特别设置为子集的形式,例如:
In[17]: pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2]) |
DataFrame 对象
与 Series
对象一样,DataFrame
对象也可以被认为是 Numpy 数组的推广,或是一种特殊的 Python 字典。下面我们将分别从这两个角度进行介绍。
DataFrame 作为广义 Numpy 数组
我们可以将 DataFrame
看做一个拥有灵活的行索引与列名的二维 Numpy 数组,其本质上就是一系列对齐(共享相同的索引)的 Series
对象。为了说明这一点,我们首先构建一个包含五大洲面积数据的 Series
:
In[18]: area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297, |
将该对象与之前的人口数据进行合并(使用字典),即可得到一个 DataFrame
对象:
In[19]: states = pd.DataFrame({'population': population, |
与 Series
对象类似,我们可以通过 index
属性来获取 DataFrame
对象的索引标签:
In[20]: states.index |
此外,DataFrame
对象还有一个 columns
属性,其为一个包含列标签的 Index
对象:
In[21]: states.columns |
因此,DataFrame
对象可以看做是二维 Numpy 数组的推广,其行与列都拥有广义的索引以方便进行数据查询。
DataFrame 作为特殊的字典
我们也可以将 DataFrame
对象看作一种特殊的字典,其将一个列名映射到一个 Series
对象上。例如:
In[22]: states['area'] |
注意如果直接访问行索引会报错,因此 DataFrame
对象需要首先通过列索引来找到列对象,再去通过行索引访问具体的值。而对于二维 Numpy 数组来说,data[0]
返回的是第一行,需要与 DataFrame
区分开来(其返回的是列)。
构建 DataFrame 对象
DataFrame
对象的构建方式同样有很多种,例如:
- 基于单个 Series 对象构建:
In[23]: pd.DataFrame(population, columns=['population']) |
- 基于字典的列表构建:
In[24]: data = [{'a': i, 'b': 2 * i} |
即使字典中缺少部分键,Pandas 也会自动填充为 NaN
:
In[25]: pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}]) |
- 基于 Series 对象的字典构建:
In[26]: pd.DataFrame({'population': population, |
- 基于二维 Numpy 数组构建(不指定则为整数索引):
In[27]: pd.DataFrame(np.random.rand(3, 2), |
- 基于 Numpy 结构化数组构建(较为特殊):
In[28]: A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')]) |
以上是书中列举的常用构建方法,这里补充一个在使用过程中遇到的构建案例:
- 基于嵌套列表(或元组)构建(可以混用):
In[extra1]: pd.DataFrame([[1,2],[2,3],[3,4]], columns=['A', 'B']) |
对于这种构建方式,在实践过程中我们可以对于每组数据构建一个列表,然后通过 list(zip(a_list, b_list))
创建嵌套列表,再基于上述方式创建 DataFrame
即可(行索引为默认整数索引)。
Index 对象
在 Series
对象与 DataFrame
对象中,都包含由于查找与修改数据的索引(index),其结构为一个 Index
对象。我们可以将 Index
对象看做一个不可变数组或是一个有序集合(多重集,因为可能包含重复值)。下面将分别从这两个角度进行介绍。首先我们基于一个整数列表创建一个简单的 Index
对象:
In[30]: ind = pd.Index([2, 3, 5, 7, 11]) |
Index 作为不可变数组
Index
对象可以执行很多与数组类似的操作,如通过索引访问:
In[31]: ind[1] |
其也拥有很多与 Numpy 数组相似的属性:
In[33]: print(ind.size, ind.shape, ind.ndim, ind.dtype) |
需要注意的是,Index
对象与 Numpy 数组的区别在于其是不可变的(类似列表与元组的区别),我们不能对索引进行修改:
In[34]: ind[1] = 0 |
这样可以保证在共享索引时更加安全。
Index 作为有序集合
Pandas 对象的设计初衷之一是便于执行数据集之间的连接这样的操作。Index
对象遵循 Python 内置的 set
数据结构的特性,可以方便地进行各种连接操作,例如:
In[35]: indA = pd.Index([1, 3, 5, 7, 9]) |
上述操作也可以通过对象方法执行,如 indA.intersection(indB)
。
未完待续