一、Pandas概述
1.1 简介
Pandas
是Python生态中非常重要的数据分析包,它是一个开源的库,采用BSD开源协议。
Pandas
是基于NumPy
构建的数据分析包,但它含有比ndarray
更为高级的数据结构和操作工具,如Series
类型、DataFrame
类型等。
Pandas
的便捷功能离不开高效的底层数据结构的支持。Pandas
主要有三种数据结构:
-
Series
(类似于一维数组) -
DataFrame
(类似于二维数组) -
Panel
(类似于三维数组)。由于Panel
并不常用,因此,新版本的Pandas已经将其列为废弃(Deprecated)的数据结构。
此外,Pandas
还是数据读取“小能手”,支持从多种数据存储文件(如CSV、TXT、Excel、HDF5等)中读取数据,支持从数据库(如SQL)中读取数据,还支持从Web(如JSON、HTML等)中读取数据。
1.2 安装
# pip install pandas
1.3 导入
import numpy as np
import pandas as pd
pd.__version__
二、Series类型数据
Series
是Pandas
的核心数据结构之一,也是理解高阶数据结构DataFrame
的基础。
Series
(有中文译为“系列”)是一种类似于一维数组的数据结构,是由一组数据及与之对应的标签(即索引)构成的。
2.1 创建Series
语法:pd.Series(data, index=index)
其中data
就是数据源,其类型可以是一系列的整数、字符串,也可是浮点数或某类Python对象。默认索引就是数据的标签(label)。
In [1]: a = pd.Series([-1,2,5,7])
In [2]: a
Out[2]:
0 -1
1 2
2 5
3 7
dtype: int64
In [3]: a.index
Out[3]: RangeIndex(start=0, stop=4, step=1)
In [4]: a.values
Out[4]: array([-1, 2, 5, 7], dtype=int64)
由上述代码可知,Series
数据有两列,第一列是数据对应的索引,第二列就是常见的数组元素。由此可见,Series
是一种自带标签的一维数组(one-dimensional labeled array)。
我们可以通过Series
的index
和values
属性,分别获取索引和数组元素值。
2.1.1 Series
与列表
Series
的数据源可以用列表来填充。
二者有相似之处,它们内部都包括一系列的数据。
不同之处在于,列表内的元素可以是相同类型的,也可以是不同类型的,也就是说列表中的元素是“大杂烩”。而Series
则不同,它依赖于NumPy
中的N维数组(ndarray
)而构建,因此,其内部的数据要整齐划一,数据类型必须相同。
此外,Series
增加对应的标签(label)作为索引。如果没有显式添加索引,Python会自动添加一个[0, n-1]
内的索引值(n
为Series对象内含元素的个数)。通常的视图是索引在左,数值在右。
2.1.2 Series与字典
创建Series
对象时,其标签并不必然是0~n-1
内的数字,它也可以被显式指定为其他类型,甚至可在创建索引后被二次修改。
In [1]: a=pd.Series([-1,2,5,7],['a','b','c','d'])
In [2]: a
Out[2]:
a -1
b 2
c 5
d 7
dtype: int64
In [3]: a.values
Out[3]: array([-1, 2, 5, 7], dtype=int64)
In [4]: a.index
Out[4]: Index(['a', 'b', 'c', 'd'], dtype='object')
#修改标签
In [5]: a.index=['a', 'b', 'c', 'e']
In [6]: a
Out[6]:
a -1
b 2
c 5
e 7
dtype: int64
乍一看,Series
与Python中的字典颇有相似之处。的确如此,Series
中的index
可对应字典中的key
,Series
中的value
与字典中的value
相同。因此,Series
也可以由现有的字典数据类型通过“打包”来创建。
由于字典中的key
可以“对标”Series
中的index
,两者都起到快速定位数据的作用,所以无须单独设置Series
所需的index
参数。
In [1]: d=pd.Series({'a':1, 'b':2, 'c':3, 'e':4})
In [2]: d
Out[2]:
a 1
b 2
c 3
e 4
dtype: int64
如果Pandas
中的Series
与Python中的字典完全一样,那么Series
就没有存在的必要了。言外之意就是,它与字典还是有不同之处的。我们知道,字典是一种无序的数据类型(python3.6之前),而Series
却是有序的,并且Series
的index
和value
之间是相互独立的。此外,两者的索引也是有区别的,Series
的index
是可变的,而字典的key
是不可变的。
2.2 Series
的统计功能
Series
还提供了简单的统计方法(如describe()
)供我们使用。describe()
方法为以列为单位进行统计分析。默认情况下,describe()
只对数值型的列进行统计分析。其统计参数的意义简述如下。
- count:一列数据的个数。
- mean:一列数据的均值。
- std:一列数据的均方差。
- min:一列数据中的最小值。
- max:一列数据中的最大值。
- 25%:一列数据中前25%的数据的分位数。
- 50%:一列数据中前50%的数据的分位数。
- 75%:一列数据中前75%的数据的分位数。
In [1]: d=pd.Series({'a':1, 'b':2, 'c':3, 'e':4})
In [2]: d.describe()
Out[2]:
count 4.000000
mean 2.500000
std 1.290994
min 1.000000
25% 1.750000
50% 2.500000
75% 3.250000
max 4.000000
dtype: float64
2.3 Series中的数据访问
一旦指定Series
的标签,就可以通过特定标签,访问、修改索引位置对应的数值。Series
对象在本质上就是一个带有标签的NumPy
数组,因此,NumPy
中的一些概念和操作方法,可直接用于Series
对象。
比如:
通过索引访问数组元素
通过切片访问数组元素
花式索引:列表作为索引
布尔索引:比较表达式作为索引
In [1]: d=pd.Series({'a':1, 'b':2, 'c':3, 'e':4})
# 按标签访问
In [2]: d['a']
Out[2]: 1
# 按索引访问
In [3]: d[0]
Out[3]: 1
# 按切片访问
In [4]: d[1:3]
Out[4]:
b 2
c 3
dtype: int64
# 按花式索引访问
In [5]: d[[1,2,3]]
Out[5]:
b 2
c 3
e 4
dtype: int64
# 按布尔索引访问
In [6]: d[d>2]
Out[6]:
c 3
e 4
dtype: int64
特别需要注意的是,与基于数字的切片不同,基于标签的切片访问,其访问区间是左闭右也闭的,也就是说访问是“指哪打哪”的,不留余地。因此,索引为’a’、'b’和’c’的这三个元素的值,都被读取到了。
In [1]: d=pd.Series({'a':1, 'b':2, 'c':3, 'e':4})
In [2]: d['a':'c']
Out[2]:
a 1
b 2
c 3
dtype: int64
2.4 Series
的元素操作
2.4.1 添加元素
两个Series
对象还可以通过append()
方法实施叠加操作,以达到Series
对象合并的目的。
In 1]: a=pd.Series([1,2,3])
In [2: b=pd.Series([4,5,6])
In [3]: a.append(b)
Out[3]:
0 1
1 2
2 3
0 4
1 5
2 6
dtype: int64
In [4]: a.append(b,ignore_index=True)
Out[4]:
0 1
1 2
2 3
3 4
4 5
5 6
dtype: int64
通过append()
方法的确可以将参数中的对象“追加”到目标对象之后,但这会产生一些小问题,因为a
和b
的索引都是0
、1
、2
,叠加到一起就会产生重复的索引,不利于通过索引来访问Series
中的元素。为了解决这个问题,我们可以在append()
方法中添加参数ignore_index=True
,这样原始Series
对象中的索引都会被忽略,而由Pandas
统一给数值添加索引。
2.4.2 删除元素
当我们想要删除Series
中的一条或者多条数据时,可以使用Pandas
提供的drop()
方法。
In [1]: a =pd.Series([1,2,3,4])
In [2]: a
Out[2]:
0 1
1 2
2 3
3 4
dtype: int64
# 删除索引为0的元素,相当于a.drop(labels=0)
In [3]: a.drop(0)
Out[3]:
1 2
2 3
3 4
dtype: int64
# a数据并没有改变
In [4]: a
Out[4]:
0 1
1 2
2 3
3 4
dtype: int64
对Series
进行删除操作并不会“惊扰”原有Series
中的数值。虽然使用drop()
方法删掉了索引值为0
的数据,但原有Series
中的数据依然安然无恙。这是因为,drop()
操作的流程是这样的:先将原始的Series
数据复制到一个新的内存空间(即所谓的深拷贝),再在新的Series
对象基础上,删除指定索引值,这时,新旧两个Series
分处不同的内存空间,自然操作起来互不干涉。你可以理解为,drop()
操作仅仅返回原有Series
对象的一个视图而已。
如果想一次性删除多个索引值对应的数据,就需要把这多个索引值打包为一个列表。
In [5]: a.drop([0,1])
Out[5]:
2 3
3 4
dtype: int64
如果的确想删除原始Series
对象中的数据,可以在drop()
方法中多启用一个参数inplace
,它是一个布尔类型变量,默认值为False
,如果设置为True
,drop()
操作就会在“本地”完成,最终的删除效果便会体现在原始Series
对象上。
In [6]: a.drop([0,1],inplace=True)
In [7]: a
Out[7]:
2 3
3 4
dtype: int64
2.5 Series
中的向量化操作
类似于NumPy
,Pandas
中的数据结构也支持广播操作。比如说,某个向量乘以某个标量,那么这个标量会自我复制,并拉伸至维度尺寸与向量相同,然后即可进行逐元素(element-wise)操作。
In [1]: a=pd.Series([1,2,3])
In [2]: a*3
Out[2]:
0 3
1 6
2 9
dtype: int64
需要说明的是,任何NaN
(Not a Number,即空置)参与的计算,返回的结果依然是NaN
。
在代码层面,向量化通常是消除代码中显式for循环语句的“艺术”。在底层实现上,Pandas
的很多操作都是基于NumPy
实现的,而在NumPy
中,向量化操作通常意味着并行处理。
另外,Series
对象也可以作为NumPy
函数的一个参数。顾名思义,在本质上,Series
就是“一系列”的数据,类似数组向量。这样一来,它就可以在NumPy
函数的操作下,达到“向量进,向量出”的目的,而不像C或Java等编程语言一样使用for
循环来完成类似的操作。
In [1]: s=pd.Series(np.random.randn(5))
In [2]: s
Out[2]:
0 1.902140
1 -0.758570
2 1.026571
3 2.361329
4 -1.087620
dtype: float64
In [3]: a=np.abs(s)
In [4]: a
Out[4]:
0 1.902140
1 0.758570
2 1.026571
3 2.361329
4 1.087620
dtype: float64
2.6 Series
的name
属性
关于Series
的属性,除了我们在前面讨论过的index
与values
,还有两个很有用的需要说明,那就是name
与index.name
。name
可以理解为数值列的名称。如果把index
也理解为一个特殊索引列的话,那么index.name就是这个索引列的名称。name
属性多用在Pandas
另外一个常见的数据结构DataFrame
中,DataFrame
可视为多个Series
对象的组合。
默认情况下,name
与index.name
都被设置为None
。
在特定场合下,我们也可以通过如下代码进行修改。
三、DataFrame
类型数据
如果把Series
看作Excel表中的一列,那么DataFrame
就是Excel中的一张表。从数据结构的角度来看,Series
好比一个带标签的一维数组,而DataFrame
就是一个带标签的二维数组,它可以由若干个一维数组(Series
)构成。
3.1 构建DataFrame
为了方便访问数据,DataFrame
中不仅有行索引(好比Excel表中最左侧的索引编号),还有列索引(好比Excel表中各个列的列名)。
我们可以通过字典、Series
等基本数据结构来构建DataFrame
。
最常用的方法之一是,先构建一个由列表或NumPy
数组组成的字典,然后再将字典作为DataFrame
中的参数。
In [1]: df=pd.DataFrame({'test':[1,2,3,4]})
In [2]: df
Out[2]:
test
0 1
1 2
2 3
3 4
DataFrame
是一种表格型数据结构,它含有一组有序的列,每列的值可以不同。充当DataFrame
数据源的字典中有两部分:key
和value
。其角色各不相同,字典的key
变成了DataFrame
的列名称,而字典的value
是一个列表,列表的长度就是行数。
为每一行打一个标签,得到的就是索引,位于DataFrame
对象的最左侧。从上面的输出可以看出,与Series
类似的是,在默认情况下,DataFrame
的索引也是从0
开始的自然数序列。
如果充当数据源的字典中有多个key/value
对,那么每个key
都对应一列。
In [1]: df=pd.DataFrame({'one':[1,2,3,4],'two':[5,6,7,8]})
In [2]: df
Out[2]:
one two
0 1 5
1 2 6
2 3 7
3 4 8
由输出可以看出,字典的key
对应DataFrame
中的column
(列)。每个key
对应的value
变成了不同的列数据。因此,在某种程度上,DataFrame
可以看作由Series
组成的大字典。
除了可以将字典当作构造DataFrame
的数据源,我们也可以将NumPy
中的二维数组转化为DataFrame
对象。二维数组比较“纯粹”,只能提供必要的数据,DataFrame
的索引名称和列名称均无法从数组对象中获取。因此,通过二维数组创建的DataFrame
列名及行名都是默认的自然数序列。
In [1]: import numpy as np
In [2]: a=np.arange(1,10).reshape(3,3)
In [3]: a
Out[3]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [4]: df=pd.DataFrame(a)
In [5]: df
Out[5]:
0 1 2
0 1 2 3
1 4 5 6
2 7 8 9
当然,也可以在创建时显式指定列名及index
行名。
In [6]: df=pd.DataFrame(a,columns=['a','b','c'],index=['one','two','three'])
In [7]: df
Out[7]:
a b c
one 1 2 3
two 4 5 6
three 7 8 9
3.2 访问DataFrame
中的列与行
3.2.1 访问列
访问DataFrame
中的列很方便,因为DataFrame
提供了特殊属性——columns
,通过具体的列名称,我们就可以轻松获取一列或多列数据。
在Panda
s中,DataFrame
还有一个“神奇”特性,就是可以将列的名称作为DataFrame
对象的属性来访问数据。例如,对于df
而言,它有三列,其列名分别为a
、b
和c
。事实上,df这个对象同时拥有这三个属性。我们知道,访问一个对象属性的方法是“对象名.属性名
”。
In [8]: df['a']
Out[8]:
one 1
two 4
three 7
Name: a, dtype: int32
In [9]: df.a
Out[9]:
one 1
two 4
three 7
Name: a, dtype: int32
df.a
和df2['a']
是等价的,但有一点需要注意,如果列名的字符串包含空格,或存在其他不符合Python变量命名规范的情况,则不能通过访问对象属性的方式来访问某个特定的列。此外,上述方法仅仅对单个列是有效的。如果想要同时访问多个列,还是得“规规矩矩”地将多个列的名称打包进一个列表之中,例如,df[['a','b']
就是一个包含两个列名的列表。
In [10]: df.columns
Out[10]: Index(['a', 'b', 'c'], dtype='object')
In [11]: df.columns.values
Out[11]: array(['a', 'b', 'c'], dtype=object)
In [12]: df.columns.values[0]
Out[12]: 'a'
df.columns
返回的是一个Index
对象,如果想读取这个对象的值,还需要进一步读取这个Index
的values
属性。df.columns.values
返回的是一个数组对象,我们可以直接用访问数组的方式(如下标)来访问它。
3.2.2 访问行
倘若想获取DataFrame
中一行或多行数据,最简单的方法莫过于使用切片技术,DataFrame
的切片方法和列表及NumPy
是类似的。
注意:只能是切片,不能是单个索引。
In [13]: df[1:3]
Out[13]:
a b c
two 4 5 6
three 7 8 9
以数字切片的方法来获取DataFrame
的行数据,有时也有局限性。DataFrame
提供了备用方案,即使用loc(index)
方法,这里的loc
是location(位置)的简写,其参数index
是行的索引标签。
In [14]: df
Out[14]:
a b c
one 1 2 3
two 4 5 6
three 7 8 9
In [15]: df.loc[['one','two']]
Out[15]:
a b c
one 1 2 3
two 4 5 6
此外,还有一个方法iloc
值得关注,它完全是基于位置的索引,其中的参数都是数字(该方法开头的“i”是指index,特指数字索引)。iloc
的用法与NumPy
的切片用法完全一样,可以把它视作DataFrame
版本的切片操作。
也正因如此,iloc
虽为DataFrame
对象的一个方法,但这个方法并不像其他方法一样有一对圆括号紧跟其后,而是如同NumPy
一样,使用一对方括号[]来协助完成切片操作,这显然是为了和NumPy
的用法“接轨”,降低用户的学习门槛。
In [16]: df.iloc[:,1:]
Out[16]:
b c
one 2 3
two 5 6
three 8 9
在iloc
方法中,行和列的索引用逗号隔开,逗号前是行索引,逗号后是列索引
方括号中没有逗号时,表示的是行索引。如果仅仅给出一个数字,则返回这个行索引代表的一行数据,单行数据就是一个Series
对象。
In [17]: df.iloc[1]
Out[17]:
a 4
b 5
c 6
Name: two, dtype: int32
我们也可以利用iloc
方法返回DataFrame
的多行数据。如果这些行数据是连续的,可以用行索引的切片操作来获取。如果这些行数据是不连续的,可以把这些间断的行索引编号汇集起来,赋值给一个列表,然后将这个列表当作iloc
方法的参数。
In [18]: df.iloc[1:2]
Out[18]:
a b c
two 4 5 6
In [19]: df.iloc[[0,2]]
Out[19]:
a b c
one 1 2 3
three 7 8 9
iloc
方法的优势并不体现在对行粒度的访问上,而是体现在它精确的区域定位上,方括号内每增加一个逗号,就增加一个维度的控制权。
In [20]: df.iloc[0,2]
Out[20]: 3
索引基础用法如下:
操作 | 句法 | 结果 |
选择列 |
|
|
用标签选择行 |
|
|
用整数位置选择行 |
|
|
行切片 |
|
|
用布尔向量选择行 |
|
|
3.3 DataFrame
中的删除操作
有了行或列的索引,就可以对DataFrame
中的数据进行修改。类似于Series
,在DataFrame
中同样可以使用drop()
方法删除一行或者一列。
In [21]: df=pd.DataFrame({'one':[1,2,3],'two':[4,5,6,],'three':[7,8,9]})
In [22]: df
Out[22]:
one two three
0 1 4 7
1 2 5 8
2 3 6 9
In [23]: df.drop('three',axis='columns')
Out[23]:
one two
0 1 4
1 2 5
2 3 6
In [24]: df
Out[24]:
one two three
0 1 4 7
1 2 5 8
2 3 6 9
In [25]: df.drop(0,axis=0)
Out[25]:
one two three
1 2 5 8
2 3 6 9
删除列时,轴值还可以设置为axis=1
,这与axis='columns'
是等价的。
如果我们把drop()
函数的删除轴方向设置为行方向(axis=0
),这样就可达到删除行的目的。
同时“删除”多行数据,这时需要把多个行号用列表的方括号括起来。
如:df.drop([0,1],axis=0)
类似于Series
中的drop()
方法,上述的删除操作仅仅是假象。输出结果仅仅是原有DataFrame
的一个视图,原始DataFrame
的数据并没有发生变化。
如果删除DataFrame
原始数据就是要借助drop()
中的另外一个参数inplace
(本地),其默认值为False
,此时我们将其设置为True
。
我们还可以利用全局内置函数del
,在原始DataFrame
对象中删除某一列。
参考
《Python极简讲义》
https://www.pypandas.cn/docs/