Python中带索引的数组-Series和DataFrame

Python的pandas包提供了Series和DataFrame数据类型,用于描述和处理结构化数组,即带索引的一维数组和二维数组。如果把结构化数组理解为表,索引就是表中的表头字段。

pandas包及其安装

pandas是在NumPy的基础上开发出来的,所以继承了NumPy计算速度快的优点。而且pandas包中提供了很多进行数据处理的函数,调用它们,可以快速可靠地实现表数据的处理,而且代码很简洁。

本书是使用Python官网上下载的软件Python 3.7.7进行介绍的,软件并不包含pandas模块,所以,使用它们之前需要先进行安装。在DOS命令窗口用pip工具安装它们。

在DOS命令窗口的提示符后键入下面的命令行,安装pandas。

code.python
python –m pip install pandas

安装位置一般在C:\Users\用户名\ AppData\Local\Programs\Python\Python3x下。最后的Python3x对应于Python软件的版本,如果版本为3.7,则为Python37。

pandas Series

pandas包提供了两种数据类型,即Series和DataFrame,分别对应于一维数组和二维数组。跟NumPy数组不同的是,Series和DataFrame是带索引的一维数组和二维数组。

下面用pandas包的Series方法创建一个Series类型的对象并用变量ser引用它:

code.python
>>> import pandas as pd
>>> ser=pd.Series([10,20,30,40])

查看ser:

code.python
>>> ser
0    10
1    20
2    30
3    40
dtype: int64

可见,Series类型的数据显示为两列,第1列为索引标签,第2列为数据一维数组。如果把索引看作是key,那么它是一个类似字典的数据结构,每一条数据由索引标签和对应的值组成。

一、创建Series类型的对象

上面使用pandas包的Series方法创建了一个Series类型的对象。它实际上是利用列表数据创建的。使用Series方法,还可以将元组数据、字典数据、NumPy数组等转换为Series类型的对象。

下面通过元组数据创建Series类型的对象。

code.python
>>> ser=pd.Series((10,20,30,40))
>>> ser
0    10
1    20
2    30
3    40
dtype: int64

通过字典数据创建Series类型的对象。此时字典数据的键被转换为Series数据的索引。

code.python
>>> ser=pd.Series({"a":10,"b":20,"c":30,"d":40})
>>> ser
a    10
b    20
c    30
d    40
dtype: int64

通过NumPy数组创建Series类型的对象。

code.python
>>> ser=pd.Series(np.arange(10,50,10))
>>> ser
0    10
1    20
2    30
3    40
dtype: int32

上面创建Series对象时,除利用字典创建时外,Series数据的索引都是自动创建的,为0基数的顺序递增的整数。实际上,创建Series对象时,可以使用index参数指定索引。下面用index参数指定所创建的Series数据的索引。

code.python
>>> ser=pd.Series(np.arange(10,50,10),index=["a","b","c","d"])
>>> ser
a    10
b    20
c    30
d    40
dtype: int32

还可以使用name参数指定Series对象的名称。

code.python
>>> ser=pd.Series(np.arange(10,50,10),index=["a","b","c","d"],name="得分")
>>> ser
a    10
b    20
c    30
d    40
Name: 得分, dtype: int32

二、Series对象的描述

使用Series对象的shape, size, index, values等属性可以获取数据形状、大小、索引标签和值等数据。下面创建一个Series类型的对象ser:

code.python
>>> ser=pd.Series(np.arange(10,50,10),index=["a","b","c","d"])
>>> ser
a    10
b    20
c    30
d    40
dtype: int32

用shape属性获取ser的形状。

code.python
>>> ser.shape
(4,)

用size属性获取ser的大小。

code.python
>>> ser.size
4

用index属性获取ser的索引标签。

code.python
>>> ser.index
Index(['a', 'b', 'c', 'd'], dtype='object')

用values属性获取ser的值。

code.python
>>> ser.values
array([10, 20, 30, 40])

使用Series对象的head和tail方法可以获取对象中前面和后面指定个数的数据。默认时个数为5。下面获取ser中前两个和后两个数据。

code.python
>>> ser.head(2)
a    10
b    20
dtype: int32
>>> ser.tail(2)
c    30
d    40
dtype: int32

三、索引和切片

创建Series类型的数据后,如果希望提取其中的某个值或某些值,需要通过索引或切片来实现。可以使用中括号获取单个索引,此时返回的是元素类型;或者中括号里面用一个列表获取多个索引,此时返回的是一个Series类型。

下面创建一个Series类型的数据ser:

code.python
>>> ser=pd.Series(np.arange(10,50,10),index=["a","b","c","d"])
>>> ser
a    10
b    20
c    30
d    40
dtype: int32

获取第2个值,它的索引标签为"b"。

code.python
>>> r1=ser["b"]
>>> r1
20

用type函数获取r1的数据类型:

code.python
>>> type(r1)
<class 'numpy.int32'>

返回的是元素的数据类型。

下面获取第1个和第4个值,使用它们的索引标签组成的列表进行获取。

code.python
>>> r2=ser[["a","d"]]
>>> r2
a    10
d    40
Name: 得分, dtype: int32

用type函数获取r2的数据类型:

code.python
>>> type(r2)
<class 'pandas.core.series.Series'>

可见,此时返回的是Series类型的数据。

除了使用中括号,还可以使用Series类型对象的loc方法和iloc方法进行索引。loc方法使用数据的索引标签进行索引,iloc方法则使用顺序编号进行索引。

下面获取ser中索引标签"a"和"d"对应的值。

code.python
>>> r3=ser.loc[["a","d"]]
>>> r3
a    10
d    40
Name: 得分, dtype: int32

用iloc方法获取ser中的第1和第4条数据。

code.python
>>> r4=ser.iloc[[0,3]]
>>> r4
a    10
d    40
Name: 得分, dtype: int32

使用冒号,可以对Series数据进行切片。下面对ser数据中从"a"标签到"c"标签连续获取值。

code.python
>>> r5=ser["a":"c"]
>>> r5
a    10
b    20
c    30
Name: 得分, dtype: int32

下面用iloc方法获取ser中第2个及以后的所有数据。

code.python
>>> r6=ser.iloc[1:]
>>> r6
b    20
c    30
d    40
Name: 得分, dtype: int32

四、布尔索引

在中括号中使用布尔表达式可以实现布尔索引。

下面获取ser中值不超过20的数据。

code.python
>>> ser[ser.values<=20]
a    10
b    20
dtype: int32

下面获取ser中索引标签不为"a"的数据。

code.python
>>> ser[ser.index!="a"]
b    20
c    30
d    40
dtype: int32

pandas DataFrame

pandas DataFrame类型的数据是带行索引和列索引的二维表数据。下面用pandas包的DataFrame方法将一个二维列表转换为DataFrame对象。

code.python
>>> import pandas as pd    #导入pandas包
>>> data=[[1,2,3],[4,5,6],[7,8,9]]    #创建二维列表
>>> df=pd.DataFrame(data)    #利用二维列表创建DataFrame对象
>>> df
   0  1  2
0  1  2  3
1  4  5  6
2  7  8  9

上面df即为利用二维列表创建的DataFrame对象,第1行的0~2为自动生成的列索引标签,第1列的0~2为自动生成的行索引标签,内部3行3列的1~9为df的值。

一、创建DataFrame对象

上面用二维列表创建了DataFrame对象。使用index参数可以设置行索引标签,使用columns参数可以设置列索引标签。

code.python
>>> data=[[1,2,3],[4,5,6],[7,8,9]]
>>> df=pd.DataFrame(data,index=["a","b","c"],columns=["A","B","C"])
>>> df
   A  B  C
a  1  2  3
b  4  5  6
c  7  8  9

下面利用二维元组创建DataFrame对象。

code.python
>>> data=((1,2,3),(4,5,6),(7,8,9))
>>> df=pd.DataFrame(data)
>>> df
   0  1  2
0  1  2  3
1  4  5  6
2  7  8  9

利用字典创建DataFrame对象。字典中键值对的键表示列索引标签,值用数据区的行数据组成列表表示。

code.python
>>> data={"a":[1,2,3],"b":[4,5,6],"c":[7,8,9]}
>>> df=pd.DataFrame(data)
>>> df
   a  b  c
0  1  4  7
1  2  5  8
2  3  6  9

利用NumPy数组创建DataFrame对象。

code.python
>>> import numpy as np
>>> data=np.array(([1, 2, 3], [4, 5, 6],[7,8,9]))
>>> df=pd.DataFrame(data)
>>> df
   0  1  2
0  1  2  3
1  4  5  6
2  7  8  9

此外,还可以通过从文件导入数据来创建DataFrame对象,这也是最常用的一种方法。可以从Excel文件、CSV文件和文本文件等导入数据。这部分内容将在9.3节详细介绍。

使用xlwings包的转换器和选项工具,还可以直接从Excel工作表指定区域获取数据并转换为DataFrame对象。这部分内容请参见9.3.5小节的介绍。

二、DataFrame对象的描述

创建DataFrame对象以后,可以使用info, describe, dtypes, shape等一系列属性和方法对它进行描述。下面首先创建一个DataFrame对象df。

code.python
>>> data=[[1,2,3],[4,5,6],[7,8,9]]
>>> df=pd.DataFrame(data,index=["a","b","c"],columns=["A","B","C"])
>>> df
   A  B  C
a  1  2  3
b  4  5  6
c  7  8  9

用info方法获取df的信息。

code.python
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 3 entries, a to c
Data columns(total 3 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   A       3 non-null      int64
 1   B       3 non-null      int64
 2   C       3 non-null      int64
dtypes: int64(3)
memory usage: 96.0+ bytes

使用info方法获取的DataFrame对象信息包括对象的类型、行索引和列索引信息、每列数据的列标签、非缺失值个数和数据类型、占用内存大小等。

用dtypes属性获取df每列数据的类型。

code.python
>>> df.dtypes
A    int64
B    int64
C    int64
dtype: object

用shape属性获取df的行数和列数,用元组给出。

code.python
>>> df.shape
(3, 3)

用len函数获取df的行数和列数。

code.python
>>> len(df)    #行数
3
>>> len(df.columns)    #列数
3

用index属性获取df的行索引标签。

code.python
>>> df.index
Index(['a', 'b', 'c'], dtype='object')

用columns属性获取df的列索引标签。

code.python
>>> df.columns
Index(['A', 'B', 'C'], dtype='object')

用values属性获取df的值。

code.python
>>> df.values
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]], dtype=int64)

用head方法获取前n行数据,默认时n=5。

code.python
>>> df.head(2)
   A  B  C
a  1  2  3
b  4  5  6

用tail方法获取后n行数据,默认时n=5。

code.python
>>> df.tail(2)
   A  B  C
b  4  5  6
c  7  8  9

用describe方法获取df每列数据的描述统计量,包括数据个数、均值、标准差、最小值、25%分位数、中值、75%分位数、最大值等。

code.python
>>> df.describe()
         A    B    C
count  3.0  3.0  3.0
mean   4.0  5.0  6.0
std    3.0  3.0  3.0
min    1.0  2.0  3.0
25%    2.5  3.5  4.5
50%    4.0  5.0  6.0
75%    5.5  6.5  7.5
max    7.0  8.0  9.0

三、数据索引和切片

创建DataFrame对象后,如果希望提取其中的某行某列或某些行某些列,需要通过索引或切片来实现。可以使用中括号获取单个索引,此时返回的是Series类型;或者中括号里面用一个列表获取多个索引,此时返回的是一个DataFrame类型。

下面创建一个DataFrame对象df。

code.python
>>> data=[[1,2,3],[4,5,6],[7,8,9]]
>>> df=pd.DataFrame(data,index=["a","b","c"],columns=["A","B","C"])
>>> df
   A  B  C
a  1  2  3
b  4  5  6
c  7  8  9

用中括号获取列索引标签为" A"的列。

code.python
>>> c1=df["A"]
>>> c1
a    1
b    4
c    7
Name: A, dtype: int64

查看c1的数据类型。

code.python
>>> type(c1)
<class 'pandas.core.series.Series'>

可见,通过索引获取DataFrame数据的单列时得到的是一个Series类型的数据。

下面用loc方法获取行索引标签为的行。

code.python
>>> r1=df.loc["a"]
>>> r1
A    1
B    2
C    3
Name: a, dtype: int64

查看r1的数据类型。

code.python
>>> type(r1)
<class 'pandas.core.series.Series'>

可见,通过索引获取DataFrame数据的单行时得到的是一个Series类型的数据。也可以用iloc方法获取行,与loc方法不同的是,iloc方法的参数为表示行编号的整数,不是标签。

可以通过指定多个索引标签来获取多个行或列。这多个行或列的索引标签组成列表放在中括号中。

code.python
>>> c23=df[["A","C"]]
>>> c23
   A  C
a  1  3
b  4  6
c  7  9
>>> r23=df.loc[["a","c"]]
>>> r23
   A  B  C
a  1  2  3
c  7  8  9

查看c23和r23的数据类型。

code.python
>>> type(c23)
<class 'pandas.core.frame.DataFrame'>
>>> type(r23)
<class 'pandas.core.frame.DataFrame'>

可见,获取多行和多列返回的是DataFrame类型的数据。

上面用中括号获取列,用loc方法也可以获取列,例如:

code.python
>>> c4=df.loc[:,"B"]
>>> c4
a    2
b    5
c    8
Name: B, dtype: int64

中括号中的冒号表示B标签对应的各行数据全部取。

用中括号获取列时中括号里面输入的是单列的标签,此时返回的是Series类型的数据。如果中括号里面输入的是单列标签组成的列表,返回的则是DataFrame类型的数据。

code.python
>>> c5=df[["B"]]
>>> c5
   B
a  2
b  5
c  8
>>> type(c5)
<class 'pandas.core.frame.DataFrame'>

用中括号索引列以后,引用values属性得到的是NumPy数组数据。

code.python
>>> ar=df["B"].values
>>> ar
array([2, 5, 8], dtype=int64)
>>> type(ar)
<class 'numpy.ndarray'>

使用冒号可以对DataFrame数据进行切片。下面的切片取所有行,取列标签"A"到"B"的所有列。

code.python
>>> df.loc[:,"A":"B"]
   A  B
a  1  2
b  4  5
c  7  8

下面的切片取行标签"a"到"b"的所有行,取列标签"B"到"C"的所有列。

code.python
>>> df.loc["a":"b","B":"C"]
   B  C
a  2  3
b  5  6

下面的切片取行标签"b"及后面的所有行,取列标签"B"及以前的所有列。

code.python
>>> df.loc["b":,:"B"]
   A  B
b  4  5
c  7  8

四、布尔索引

在中括号中使用布尔表达式实现布尔索引。

下面获取df中B列数据大于等于3的行数据。

code.python
>>> df[df["B"]>=3]
   A  B  C
b  4  5  6
c  7  8  9

获取df中A列数据大于等于2并且C列数据等于9的行数据。

code.python
>>> df[(df["A"]>=2)&(df["C"]==9)]
   A  B  C
c  7  8  9

获取df中B列数据界于4和9之间的行数据。

code.python
>>> df[df["B"].between(4,9)]
   A  B  C
b  4  5  6
c  7  8  9

获取df中A列数据取0-5范围内整数的行数据。

code.python
>>> df[df["A"].isin(range(6))]
   A  B  C
a  1  2  3
b  4  5  6

获取df中B列数据界于4和9之间的行数据,然后取A列和C列的数据。

code.python
>>> df[df["B"].between(4,9)][["A","C"]]
   A  C
b  4  6
c  7  9

找到行标签为"b"的行中大于等于5的数据。

code.python
>>> df.loc[["b"]]>=5
       A     B     C
b  False  True  True

行标签为"b"的行中大于等于5的数据对应的布尔值为True。

Excel与pandas交换数据

利用pandas包的read_excel方法将Excel数据导入到pandas,使用to_excel方法将pandas数据写入到Excel文件。

利用pandas包的read_excel方法读取Excel数据。该方法的参数比较多,常用的参数如表7-8中所示。利用这些参数,可以导入规整数据,也可以处理很多不规范的Excel数据。导入后的数据为DataFrame类型的数据。

表7-8 read_excel方法的部分参数

参 数 说 明
io Excel文件的路径和名称
sheet_name 读取数据的工作表的名称,可以指定名称,也可以指定索引号,不指定时读取第1个工作表
header 指定用哪行数据作为索引行,如果是多层索引,用多行的行号组成列表进行指定
index_col 指定用哪列数据作为索引列,如果是多层索引,用多列的列号或名称组成列表进行指定
usecols 如果只需要导入原始数据中的部分列数据,使用该参数用列表进行指定
dtype 用字典指定特定列的数据类型,如{"A":np.float64 }指定A列的数据类型为64位浮点型
nrows 指定需要读取的行数
skiprows 指定读入时忽略前面多少行
skip_footer 指定读入时忽略后面多少行
names 用列表指定列的列索引标签
engine 执行数据导入的引擎,如"xlrd", "openpyxl"等

注意:使用read_excel方法导入数据时有时会出现类似没有安装xlrd的错误以及其他各种错误。建议安装openpyxl,在使用read_excel方法时指定engine参数的值为"openpyxl"。

把路径“Samples\ch07\Python\”下的示例文件“身份证号.xlsx”复制到D盘。该文件工作簿中有2个工作表,保存的是部分工作人员的身份信息。现在用pandas包的read_excel方法导入该文件中的数据。

code.python
>>> df=pd.read_excel(io="D:\身份证号.xlsx",engine="openpyxl")
>>> df
     工号   部门  姓名                身份证号 性别
0  1001  财务部  陈东  510321197810030016  女
1  1002  财务部  田菊  412823198005251008  男
2  1003  生产部  王伟  430225198003113024  男
3  1004  生产部  韦龙  430225198511163008  女
4  1005  销售部  刘洋  430225198008123008  女

默认时导入第1个工作表中的数据,将第1行数据作为表头,即列索引标签。行索引从0开始自动对行进行编号。

使用sheet_name参数可以指定打开某一个或多个工作表,用index_col参数指定某列作为行索引。下面同时打开前两个工作表,指定“工号”列作为行索引。

code.python
>>> df=pd.read_excel(io="D:\身份证号.xlsx",sheet_name=[0,1],index_col="工号",engine="openpyxl")
>>> df
{0:        部门  姓名                身份证号 性别
工号
1001  财务部  陈东  510321197810030016  女
1002  财务部  田菊  412823198005251008  男
1003  生产部  王伟  430225198003113024  男
1004  生产部  韦龙  430225198511163008  女
1005  销售部  刘洋  430225198008123008  女, 1:        部门   姓名                身份证号 性别
工号
1006  生产部   吕川  320325197001017024  女
1007  销售部   杨莉  420117197302174976  男
1008  财务部   夏东  132801194705058000  女
1009  销售部   吴晓  430225198001153024  男
1010  销售部  宋恩龙  320325198001017984  女}

现在同时导入了两个工作表中的数据,并且将“工号”列数据进行行索引。可见,此时返回的结果为字典类型,字典中键值对的键为工作表的索引号,值为工作表的数据,为DataFrame类型。可以用type函数查看数据类型。

code.python
>>> type(df[0])
<class 'pandas.core.frame.DataFrame'>

其他参数请自行测试,比如选择列数据、忽略前面的部分行或后面的部分行、给没有列索引标签的数据添加标签等。

使用DataFrame对象的to_excel方法将pandas数据写入到Excel文件。比如上面导入了前两个工作表的数据,现在希望将这两个工作表的数据合并后保存到另外一个Excel文件中。使用pandas包的concat方法垂向拼接两个工作表的数据,然后保存到D盘下的new_file.xlsx文件中。

code.python
>>> df1=df[0]
>>> df2=df[1]
>>> df0=pd.concat([df1,df2])
>>> df0
       部门   姓名                身份证号 性别
工号
1001  财务部   陈东  510321197810030016  女
1002  财务部   田菊  412823198005251008  男
1003  生产部   王伟  430225198003113024  男
1004  生产部   韦龙  430225198511163008  女
1005  销售部   刘洋  430225198008123008  女
1006  生产部   吕川  320325197001017024  女
1007  销售部   杨莉  420117197302174976  男
1008  财务部   夏东  132801194705058000  女
1009  销售部   吴晓  430225198001153024  男
1010  销售部  宋恩龙  320325198001017984  女
>>> df0.to_excel("D:\\new_file.xlsx")

合并后的数据被正确保存到指定文件中。

使用xlwings包,可以实现多个DataFrame数据在同一个工作表中的读写操作。下面首先导入xlwings,打开D盘下的“身份证号.xlsx”文件。该文件工作簿中有2个工作表,保存的是部分工作人员的身份信息。

code.python
>>> import xlwings as xw  #导入xlwings
>>> #创建Excel应用,可见,不添加工作簿
>>> app=xw.App(visible=True, add_book=False)
>>> #打开数据文件,可写
>>> bk=app.books.open(fullname="D:\\身份证号.xlsx",read_only=False)
>>> #获取工作簿中的2个工作表
>>> sht1=bk.sheets[0]
>>> sht2=bk.sheets[1]
>>> #添加1个新工作表,放在最后面,命名
>>> sht3=bk.sheets.add(after=bk.sheets(bk.sheets.count))
>>> sht3.name="多DataFrame"

下面使用xlwings的转换器和选项工具,将已有的两个工作表中的数据以DataFrame类型读取到df1和df2。用pandas包的concat方法垂向拼接它们,得到第3个DataFrame数据df3。

code.python
>>> df1=sht1.range("A1:E6").options(pd.DataFrame).value
>>> df2=sht2.range("A1:E6").options(pd.DataFrame).value
>>> df3=pd.concat([df1,df2])

将这3个DataFrame数据写入到第3个工作表中的指定位置。只需要指定区域的左上角单元格即可。

code.python
>>> sht3.range("A1").value=df1
>>> sht3.range("A8").value=df2
>>> sht3.range("G1").value=df3

第3个工作表中的显示效果如图7-13所示。可见,使用xlwings包,我们实现了多个DataFrame数据在同一个工作表中的读和写。

Document Image

图7-13 将多个DataFrame写入同一个工作表