一、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类型数据

SeriesPandas的核心数据结构之一,也是理解高阶数据结构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)

我们可以通过Seriesindexvalues属性,分别获取索引和数组元素值。

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可对应字典中的keySeries中的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却是有序的,并且Seriesindexvalue之间是相互独立的。此外,两者的索引也是有区别的,Seriesindex是可变的,而字典的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()方法的确可以将参数中的对象“追加”到目标对象之后,但这会产生一些小问题,因为ab的索引都是012,叠加到一起就会产生重复的索引,不利于通过索引来访问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,如果设置为Truedrop()操作就会在“本地”完成,最终的删除效果便会体现在原始Series对象上。

In [6]: a.drop([0,1],inplace=True)
In [7]: a
Out[7]:
2    3
3    4
dtype: int64

2.5 Series中的向量化操作

类似于NumPyPandas中的数据结构也支持广播操作。比如说,某个向量乘以某个标量,那么这个标量会自我复制,并拉伸至维度尺寸与向量相同,然后即可进行逐元素(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 Seriesname属性

关于Series的属性,除了我们在前面讨论过的indexvalues,还有两个很有用的需要说明,那就是nameindex.namename可以理解为数值列的名称。如果把index也理解为一个特殊索引列的话,那么index.name就是这个索引列的名称。
name属性多用在Pandas另外一个常见的数据结构DataFrame中,DataFrame可视为多个Series对象的组合。
默认情况下,nameindex.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数据源的字典中有两部分:keyvalue。其角色各不相同,字典的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,通过具体的列名称,我们就可以轻松获取一列或多列数据。
Pandas中,DataFrame还有一个“神奇”特性,就是可以将列的名称作为DataFrame对象的属性来访问数据。例如,对于df而言,它有三列,其列名分别为abc。事实上,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.adf2['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对象,如果想读取这个对象的值,还需要进一步读取这个Indexvalues属性。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

索引基础用法如下:

操作

句法

结果

选择列

df[col]

Series

用标签选择行

df.loc[label]

Series

用整数位置选择行

df.iloc[loc]

Series

行切片

df[5:10]

DataFrame

用布尔向量选择行

df[bool_vec]

DataFrame

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/