python数据处理基础之pandas基础数据结构 -- DataFrame

在上一章《pandas基础数据结构-Series》我们提到过,pandas其实就是个功能强大的代码版Excel。其中的SeriesDataFrame就是用于抽象表及封装表的各种高级处理方法的数据结构。我们已经介绍完表示记录内容的Series,这章就主要说说DataFrame,看看他是怎么抽象表示表框架,以及可以对表格做怎样的处理。

学完本章,你就可以发现,DataFrame就是抽象了表结构,拥有表结构所需要的各种属性及操作一个表结构的各种方法。而Series,抽象了组成表内容的列向量与行向量。通过本章学习后,你会更加了解这段话。

一个图示看懂Series与DataFrame

这是上一章中就给出的表,通过这章的共同学习,我们就可以使用pandas库将之表示出来。

1 导图

(建议看每一章时,先把此导图打印出来对着看,好让自己对整体内容有个大概的把握与知道自己学习到哪个位置:)我自己做此笔记也是这样,把导图都打印出来,然后对着自己的笔记回顾,看完哪里就去哪里打个勾表示复习过一遍了)

2 DataFrame的创建及基本属性

2.1 DataFrame的创建

说到创建一个DataFrame,我们不妨先想一下,像我一开始展示的图示那样一张普通的表包含什么?我们先来看看一张普通的表是一般是这样子:

一张普通体验表
1
2
3
4
        tall  weight  age
Peter 120 70 10
Ken 70 50 9
Anne 100 52 11
  • 首先是我们很容易就可以发现,普通的表就是一个二维数组。由行和列组成。是的,一个表我们可以抽象成是由多个行向量和多个列向量组成的。
  • 其中,“行向量”我们一般称之为记录(比如,上面Peter的体验结果,我们就称之为一条记录)。
  • “列向量”我们称之为字段。比如上面tall一列的数据,表示全班人的身高数据。
  • DataFrame也是这样抽象一张表格的,也就是其主要的数据成员就是“行向量集合(index)”与“列向量集合(columns)”。
  • 这里可以提一下的就是,单独抽出来一个列向量或行向量,就是Series。所以我们说DataFrame是由Series组成的也未尝不可,这也是我们先学Series再学DataFrame的原因。

指定了行向量与列向量,就可以创建一个DataFrame表了。

创建”图示看懂Series与DataFrame“中的表格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建 DataFrame
data = {
'tall': [120, 70, 100],
'weight': [70, 50, 52],
'age': [10, 9, 11]
}
# 如果不指定index, pd会自动为你添上从0开始的数字序列作为index
examTable = pd.DataFrame(data, index=['Peter', 'Ken', 'Anne'])
log('体检表', examTable)
# 我们看看指定 columns列向量集合会怎样,不包在此向量中的原始数据会被忽略~
tallTable = pd.DataFrame(data, index=['Peter', 'Ken', 'Anne'], columns=['tall'])
log('体检表(只看身高)', tallTable)

# ==> 体检表: <class 'pandas.core.frame.DataFrame'>
# tall weight age
# Peter 120 70 10
# Ken 70 50 9
# Anne 100 52 11
#
# ==> 体检表(只看身高): <class 'pandas.core.frame.DataFrame'>
# tall
# Peter 120
# Ken 70
# Anne 100

2.2 DataFrame的主要属性

从上面的小节我们已然知道,DataFrame由行向量集合与列向量集合组成,所以其最主要的属性必然就是 index与 columns啦。没错,此外还有values,直接返回组成DataFrame结构的ndarray数据块,这说明了什么?说明在真正的内存中,DataFrame中的数据并没有分开存放,而是以ndarray的形式存在一起了,这无疑是一种非常高明巧妙的作法,有兴趣的童鞋可以网上找些文章深入探讨下DataFrame的实现方式,我们这里就不深究了。

这里提一下,无论是”行索引”还是”列索引”使用的是pd的Index数据结构,与ndarray结构有点像,下边的代码示例中我也打印出来了。实际上对之进行处理时,大家也可以当作ndarray来处理,

此外,和Series一样,DataFrame也可以为自己的行索引和列索引取名字。

DataFrame的主要属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

log('index属性', examTable.index)
log('columns属性', examTable.columns)
# -- values属性和Series一样,也是使用ndarray多维数组来表示表格的数据。
log('values属性', examTable.values)
# -- DataFrame也可以像Series一样指定一些name作为标识
examTable.index.name = 'student-name'
examTable.columns.name = 'exam-result'
log('添加了name的DataFrame', examTable)

# ==> index属性: <class 'pandas.core.indexes.base.Index'>
# Index(['Peter', 'Ken', 'Anne'], dtype='object')
#
# ==> columns属性: <class 'pandas.core.indexes.base.Index'>
# Index(['tall', 'weight', 'age'], dtype='object')
#
# ==> values属性: <class 'numpy.ndarray'>
# [[120 70 10]
# [ 70 50 9]
# [100 52 11]]
#
# ==> 添加了name的DataFrame: <class 'pandas.core.frame.DataFrame'>
# exam-result tall weight age
# student-name
#
# Peter 120 70 10
# Ken 70 50 9
# Anne 100 52 11

3 索引访问

对于一张表来说,索引访问可以说是其最主要的功能了。在数据处理中,我们常常会有这样的需求,比如看某一行的数据、获取某一列的数据,或者是具体的业务如获取Ken的体重信息。DataFrame提示了丰富的索引访问功能,可以满足我们的各种需要。

3.1 列索引

DataFrame提供了有点类似于字典访问的方法来进行列索引,可以说是对列的操作有特殊的照顾了。

我们这里在介绍和学习API时,不妨作下思考,为啥pandas要给列索引”特殊的照顾”呢?我个人觉得,因为在实际的程序存储中,行索引往往是由数字组成的id序列,更适用于下面介绍的index索引的方法。而字段名一般都由字符串组成,更适合使用key去索引访问~

比如,我们要获取上面表中全班同学的身高数据,直接 examTable['tall']这样就行了,会返回全班同学身高的Series。注意,单列索引返回该列数据组成的Series,多列索引返回为DataFrame,这个其实也很好推出来。

另外,说了“特殊照顾”,DataFrame还对列索引提供了点访问操作,非常的方便优雅。

列索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -- 通过字段索引, 访问的是用Series表示的字段记录(也可以通俗点说是使用Series封装的列数据)。
# 从输出的Series可以看出,其name就是我们使用的索引,也就是列字段名。而index依旧是DataFrame的index
log('索引访问身高', examTable['tall'])
log('索引访问身高2', examTable.tall)
# 注意,多列索引返回的也是DataFrame!!
log('多列索引,获取身高与年龄字段', examTable[['tall', 'age']])

# ==> 索引访问身高: <class 'pandas.core.series.Series'>
# student-name
# Peter 120
# Ken 70
# Anne 100
# Name: tall, dtype: int64
#
# ==> 索引访问身高2: <class 'pandas.core.series.Series'>
# student-name
#
# Peter 120
# Ken 70
# Anne 100
# Name: tall, dtype: int64
#
# ==> 多列索引,获取身高与年龄字段: <class 'pandas.core.frame.DataFrame'>
# exam-result tall age
# student-name
# Peter 120 10
# Ken 70 9
# Anne 100 11

3.2 行索引

行索引主要是两个方法。

  • df.loc[key | keyArray] # 主要针对行索引的key访问
  • df.iloc[index | indexArray] # 使用行号来访问
行索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 使用index索引要使用loc定位方法。loc
# 输出的结果也是Series,表示索引到的一条记录
log('index索引Peter的体验结果', examTable.loc['Peter'])
log('index多行索引', examTable.loc[['Peter', 'Ken']]) # 传入要索引的index数组
log('index多行索引(使用行号)', examTable.iloc[[0, 2]]) # 传入要索引行号

# ==> index索引Peter的体验结果: <class 'pandas.core.series.Series'>
# exam-result (注意下,这里ColumnsName变成了Series的Name!)
# tall 120
# weight 70
# age 10
# Name: Peter, dtype: int64
#
# ==> index多行索引: <class 'pandas.core.frame.DataFrame'>
# exam-result tall weight age
# student-name
# Peter 120 70 10
# Ken 70 50 9
#
# ==> index多行索引(使用行号): <class 'pandas.core.frame.DataFrame'>
# exam-result tall weight age
# student-name
# Peter 120 70 10
# Anne 100 52 11
#
# ==> index行列一起索引(第1、3行,前2列): <class 'pandas.core.frame.DataFrame'>
# exam-result tall weight
# student-name
# Peter 120 70
# Anne 100 52

3.3 联合索引

当我们将行索引与列索引联合使用时,就可以访问到我们具体想要的值了。

你可以对DataFrame进行行索引,再进行列索引。也可以先列索引,再对结果进行行索引。

联合索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

log('联合索引-先行索引再列索引', examTable.loc[['Peter', 'Anne']][['tall', 'age']])
log('联合索引-先列索引再行索引', examTable[['tall', 'age']].loc[['Peter', 'Anne']])
log('index行列一起索引(第1、3行,前2列)', examTable.iloc[[0, 2], [0, 1]])

# ==> 联合索引-先行索引再列索引: <class 'pandas.core.frame.DataFrame'>
# exam-result tall age
# student-name
# Peter 120 10
# Anne 100 11
#
# ==> 联合索引-先列索引再行索引: <class 'pandas.core.frame.DataFrame'>
# exam-result tall age
#
# student-name
# Peter 120 10
# Anne 100 11

你还可以使用条件来进行联合索引。

条件联合索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
result = examTable.iloc[:][examTable.tall < 100]
log('examTable.tall < 100', (examTable.tall < 100))
log('身高小于100的学生', result)

# ==> examTable.tall < 100: <class 'pandas.core.series.Series'>
# student-name
# Peter False
# Ken True
# Anne False
# Name: tall, dtype: bool
#
# ==> 身高小于100的学生: <class 'pandas.core.frame.DataFrame'>
# exam-result tall weight age
# student-name
# Ken 70 50 9

4 DataFrame的基本操作

真的,对DataFrame的这些基本操作更能加深你 pandas其实就是个功能强大的代码版Excel的印象。。

你可以像给字典添加成员一样,使用索引访问来给表添加一个未成有过的字段(相当于添加了一列)。对列使用del命令删除。

行操作需要使用df的内置方法 append(series)drop(index)

DataFrame的基本操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -- 添加列
examTable['mark'] = 0 # 添加一列表示体检的分数,并初始化为0
# examTable['mark'] = [90, 85, 99] # 也可以直接给每行记录分别赋值,但长度必须匹配
log('体检表添加一列结果', examTable)

# -- 删除列
# 删除列使用del方法
del examTable['mark']
log('体检表删除mark这一列', examTable)

# -- 添加行
examTable = examTable.append(Series([115, 66, 9], name='Alice', index=examTable.columns))
log('添加了一行数据', examTable)

# -- 删除行
examTable = examTable.drop(['Peter'])
log('删除了一行数据', examTable)

5 小结

作为小结和对本章的练习,大家可以试着在纸上算算下面程序的输出,并亲自复制到源文件中执行对比看自己对了多少:

本章源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# -*- coding: UTF-8 -*-
"""
pandas基础数据结构 DataFrame.

Copyright 2018 luochenxun https://luochenxun.com
"""

import numpy as np
import pandas as pd
from pandas import Series, DataFrame

def log(msgOrOjb, obj=None):
"""一个实用的log方法."""
if isinstance(msgOrOjb, str):
if obj is not None:
print('\n==> ' + msgOrOjb + ':' , type(obj))
print(obj)
else:
print('\n==> ' + msgOrOjb)
else:
print('\n==> ', type(msgOrOjb))
print(msgOrOjb)


def main():
# 创建 DataFrame
data = {
'tall': [120, 70, 100],
'weight': [70, 50, 52],
'age': [10, 9, 11]
}
# 如果不指定index, pd会自动为你添上从0开始的数字序列作为index
examTable = pd.DataFrame(data, index=['Peter', 'Ken', 'Anne'])
log('体检表', examTable)
tallTable = pd.DataFrame(data, index=['Peter', 'Ken', 'Anne'], columns=['tall'])
log('体检表(只看身高)', tallTable)

# DataFrame的主要属性
log('index属性', examTable.index)
log('columns属性', examTable.columns)
# -- values属性和Series一样,也是使用ndarray多维数组来表示表格的数据。
log('values属性', examTable.values)
# -- DataFrame也可以像Series一样指定一些name作为标识
examTable.index.name = 'student-name'
examTable.columns.name = 'exam-result'
log('添加了name的DataFrame', examTable)

# DataFrame的索引访问
# -- 通过字段索引, 访问的是用Series表示的字段记录(也可以通俗点说是使用Series封装的列数据)。
# 从输出的Series可以看出,其name就是我们使用的索引,也就是列字段名。而index依旧是DataFrame的index
log('索引访问身高', examTable['tall'])
log('索引访问身高2', examTable.tall)
log('多列索引', examTable[['tall', 'age']])
# -- 通过index索引
# 使用index索引要使用loc定位方法。loc
# 输出的结果也是Series,表示索引到的一条记录
log('index索引Peter的体验结果', examTable.loc['Peter'])
log('index多行索引', examTable.loc[['Peter', 'Ken']]) # 传入要索引的index数组
log('index多行索引(使用行号)', examTable.iloc[[0, 2]]) # 传入要索引行号
# 联合索引
log('联合索引-先行索引再列索引', examTable.loc[['Peter', 'Anne']][['tall', 'age']])
log('联合索引-先列索引再行索引', examTable[['tall', 'age']].loc[['Peter', 'Anne']])
log('index行列一起索引(第1、3行,前2列)', examTable.iloc[[0, 2], [0, 1]])
# 条件联合索引
result = examTable.iloc[:][examTable.tall < 100]
log('examTable.tall < 100', (examTable.tall < 100))
log('身高小于100的学生', result)

# DataFrame的基本操作
# -- 添加列
examTable['mark'] = 0 # 添加一列表示体检的分数,并初始化为0
# examTable['mark'] = [90, 85, 99] # 也可以直接给每行记录分别赋值,但长度必须匹配
log('体检表添加一列结果', examTable)
# -- 删除列
# 删除列使用del方法
del examTable['mark']
log('体检表删除mark这一列', examTable)

# -- 添加行
examTable = examTable.append(Series([115, 66, 9], name='Alice', index=examTable.columns))
log('添加了一行数据', examTable)
# -- 删除行
examTable = examTable.drop(['Peter'])
log('删除了一行数据', examTable)

if __name__ == '__main__':
main()

6 引用

  1. 《利用python进行数据分析》 - WesMcKinney著,SeanCheney译 - WesMcKinney是pandas的开发者,所以必须是讲解numpy、pandas等数据处理工具最权威的专家了,他的这本书写的很简洁很易读,SeanCheney翻译的也是非常完美。十分值得推荐的。