6. Pandas 入门#


pandas 是 Python 做统计分析时最重要的数据分析工具之一,它基于 numpy 开发,提供了许多处理大型数据集所需的函数,可以灵活高效的处理各种数据集。

pandas 一般使用的数据类型为 DataFrame, DataFrame 是二维数据,相当于 Excel 里面的一张表格数据,使用 pandas 时首先要导入 pandas 包。

import pandas as pd

6.1. 创建,读取与存储数据#


6.1.1. 创建数据#


例如有下面的数据:

姓名

统计学

高数

英语

张三

85

82

84

李四

68

63

90

王五

90

88

78

我们使用字典类型数据,将上面的读取到一个 DataFrame 里面:

import pandas as pd

df = pd.DataFrame(
    {
        "姓名": ["张三", "李四", "王五"],
        "统计学": [85, 68, 90],
        "高数": [82, 63, 88],
        "英语": [84, 90, 78],
    }
)
df
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78

上面的图形中,第一行是列标题,第一列是行标题,可以分别通过 columnsindex 访问。

df.columns
Index(['姓名', '统计学', '高数', '英语'], dtype='object')
df.index
RangeIndex(start=0, stop=3, step=1)

6.1.2. 读取数据#


多数情况下,我们要读取数据文件(例如 excel),假设上面的例子在 excel 文件 ‘transcripts.xlsx’ 里并放在电脑硬盘位置“datas/” 中,可以通过函数read_excel读取文件:

import pandas as pd

# 前几行代码是为了运行 live code 而进行的设置,
# 与本章的教学内容无关
import os

if os.getcwd() == "/home/jovyan":
    os.chdir("data-science/")

# 正文
df = pd.read_excel("datas/transcripts.xlsx")

注意

  • 苹果电脑的文件地址用符号/分割,而 windows 系统的文件地址用符号\分割

  • 文件地址前加上r能够保持字符串原始值的含义,而不对其中的符号进行转义

read_excel 的一般语法如下:

read_excel(io, sheetname=0, header=0, skiprows=None, index_col=None, encoding = None)
io 数据文件的地址与名字,一般为字符串
sheetname 工作簿名字,默认为0,表示读取第一张工作簿
header 作为列名的行,默认为0,即取第一行的值为列名
skiprows 省略指定行数的数据,从第一行开始查起
index_col 行标题所在的列
encoding 解码方式,一般为 'gbk'或 'utf-8'

注意

  • 读取一些数据文件时,可能需要添加解码参数 encoding=’gbk’ 或 encoding=’utf-8’ 才能正确读取

还有一种常见的数据文件类型为 csv,我们只需使用 pandas 中的函数read_csv,它的语法与read_excel基本一致。

6.1.3. 存储数据#


存储时使用函数 to_excelto_csv。例如我们将数据 df 仍然存储到文件夹 “datas/” 里,并命名为 “marks.xlsx”:

df.to_excel("datas/marks.xlsx")
  • 默认情况下索引(index)不会写入文件,如果希望将索引也保存到文件中,可以通过设置index=True(默认是index=False

6.2. 查看与修改数据#


快速查看 DataFrame 各列数据的统计信息可以使用 describe() 函数,包括各列数据的非空数值数目、均值、标准差、最大值、最小值、分位数。

df.describe()
统计学 高数 英语
count 3.000000 3.000000 3.0
mean 81.000000 77.666667 84.0
std 11.532563 13.051181 6.0
min 68.000000 63.000000 78.0
25% 76.500000 72.500000 81.0
50% 85.000000 82.000000 84.0
75% 87.500000 85.000000 87.0
max 90.000000 88.000000 90.0

另外还有几个方便使用的函数:

函数

作用

info()

查看各列数据的类型

head()

查看前 5 行数据

tail()

查看后 5 行数据

6.2.1. 查看单行,单列,单元格数据#


查看某一列数据时,最简单的方式是在中括号[]里面输入列名的方式,例如查看英语成绩那一列数据:

df["英语"]
0    84
1    90
2    78
Name: 英语, dtype: int64

查看某一行数据时,可以用loc[]函数跟行索引的方式;另外一种方式是中括号[]里面跟着相邻两个行索引。

df.loc[0]  # 显示第 1 行的数据
姓名     张三
统计学    85
高数     82
英语     84
Name: 0, dtype: object
df.loc[2]  # 显示第 2 行的数据
姓名     王五
统计学    90
高数     88
英语     78
Name: 2, dtype: object
df[0:1]  # 显示第 1 行的数据
姓名 统计学 高数 英语
0 张三 85 82 84
df[1:2]  # 显示第 2 行的数据
姓名 统计学 高数 英语
1 李四 68 63 90

若查看某个单元格,比较方便的方式是用两个中括号,每个中括号内分别跟着列索引和行索引,例如查看李四的高数成绩:

df["高数"][1]
63

或者

df.loc[1]["高数"]
63

6.2.2. 查看多行,多列数据#


查看多行、多列数据时,可以用 iloc,它不仅能查看多行多列数据,也能查看单行、单列或某个单元格数据。 例如,查看行数据:

df.iloc[1]  # 查看第 2 行数据
df.iloc[0:2]  # 查看前 2 行数据
df.iloc[[0, 2]]  # 查看第 1 行与第 3 行数据
姓名 统计学 高数 英语
0 张三 85 82 84
2 王五 90 88 78

查看列数据:

df.iloc[:, 1]  # 查看第 2 列数据
df.iloc[:, 0:2]  # 查看前 2 列数据
df.iloc[:, [0, 2]]  # 查看第 1 列与第 3 列数据
姓名 高数
0 张三 82
1 李四 63
2 王五 88

查看一块数据:

df.iloc[0:2, 0:2]  # 查看前 2 行,前 2 列的一块数据
df.iloc[[0, 2], [0, 2]]  # 查看第 1、第 3 行,第 1、第 3 列的一块数据
df.iloc[0:2, [0, 2]]  # 查看前 3 行,第 1、第 3 列的一块数据
df.iloc[[0, 2], 0:2]  # 第 1、第 3 行,前 2 列的一块数据
姓名 统计学
0 张三 85
2 王五 90

查看某个单元格:

df.iloc[1, 1]  # 查看第 2 行,第 2 列的单元格数据
68

查看具体几列数据,可以在中括号[]里面跟一个索引列表,例如,查看统计学与英语的成绩:

df[["统计学", "英语"]]
统计学 英语
0 85 84
1 68 90
2 90 78
df[0:3]
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78

查看具体几行数据,可以用iloc结合索引列表的形式,例如,查看张三与王五的成绩:

df.iloc[[0, 2]]
姓名 统计学 高数 英语
0 张三 85 82 84
2 王五 90 88 78

6.2.3. 查看数据的统计值#


Pandas 自带一些统计函数,可以得到列数据的统计值,这些统计函数包括:mean(均值),max(最大值),min(最小值),median(中位数),std(标准差),count(计数),skew(均值),quantile(分位数)等。有时候需要添加参数numeric_only = True,表示只对数值数列应用函数。

df["统计学"].mean()  # 求出统计学课程等平均分数
81.0
df.mean(numeric_only=True)  # 求出每门课程等平均值,因为第一列姓名不为数值,添加了参数 ``numeric_only = True``
统计学    81.000000
高数     77.666667
英语     84.000000
dtype: float64
df["高数"].max()  # 求出高数课程等最高分
88
df.mean(numeric_only=True, axis=1)  # 通过设置 axis = 1 对行调用函数
0    83.666667
1    73.666667
2    85.333333
dtype: float64

6.2.4. 修改数据#


在修改 pandas 的 DataFrame 数据时,将 pandas 的索引位置赋值为新的值。例如,当修改某列数据时,语法为:df[‘列名'] =  某个值或某个元素个数与 DataFrame 列数相同的列表

df["统计学"] = 80  # 修改统计学这一列的数值为同一个值 80
df
姓名 统计学 高数 英语
0 张三 80 82 84
1 李四 80 63 90
2 王五 80 88 78
df["统计学"] = [95, 76, 88]  # 修改统计学这一列的数值为列表中的值
df
姓名 统计学 高数 英语
0 张三 95 82 84
1 李四 76 63 90
2 王五 88 88 78
df.iloc[1] = 80  # 修改第 2 行的数据为同一个值 80
df
姓名 统计学 高数 英语
0 张三 95 82 84
1 80 80 80 80
2 王五 88 88 78
df.iloc[1] = ["赵四", 95, 90, 89]  # 修改第 2 行的数据为列表中的值
df
姓名 统计学 高数 英语
0 张三 95 82 84
1 赵四 95 90 89
2 王五 88 88 78

下面的代码修改第2行第2列中的数值:

df.iloc[1, 1] = 100  # 修改第2行第2列中的数值
df
姓名 统计学 高数 英语
0 张三 95 82 84
1 赵四 100 90 89
2 王五 88 88 78

6.3. 增加,删除与合并数据#


6.3.1. 增加数据#


在原数据末尾增加一列时,语法为 df[‘新列名'] = 某个值或某个元素个数与 DataFrame 列数相同的列表,例如:

df["计算机"] = [92, 69, 75]  # 增加一列计算机课程的成绩
df
姓名 统计学 高数 英语 计算机
0 张三 95 82 84 92
1 赵四 100 90 89 69
2 王五 88 88 78 75

在原数据末尾增加一行数据时,比较简单的方式是用 loc 函数,df.loc[行索引] = 新行值

df.loc[3] = ["马六", 65, 70, 69, 55]  # 在末尾增加一行数据
df
姓名 统计学 高数 英语 计算机
0 张三 95 82 84 92
1 赵四 100 90 89 69
2 王五 88 88 78 75
3 马六 65 70 69 55

若要在指定位置插入列,则需要用到 insert 函数。

df.insert(1, "运筹学", [61, 72, 84, 81])  # 在第 1 列后面插入新的一列
df
姓名 运筹学 统计学 高数 英语 计算机
0 张三 61 95 82 84 92
1 赵四 72 100 90 89 69
2 王五 84 88 88 78 75
3 马六 81 65 70 69 55
df.insert(3, "Python", [81, 76, 74, 71])  # 在第 4 列后面插入新的一列
df
姓名 运筹学 统计学 Python 高数 英语 计算机
0 张三 61 95 81 82 84 92
1 赵四 72 100 76 90 89 69
2 王五 84 88 74 88 78 75
3 马六 81 65 71 70 69 55

若要在指定位置插入行,目前 Pandas 还没有专门的函数,一般采用concat函数合并多个 DataFrame 的方式,增加多列或多行数据也可以使用 concat函数或merge函数,具体参看后面的合并数据章节。

6.3.2. 删除数据#


Pandas 可以利用drop函数删除行数据或列数据。删除一行时,参数为行标签名以及inplace = True。若没有参数inplace = True,原始的 DataFrame 数据不变。

df.drop(3, inplace=True)  # 删除第 3 行
df
姓名 运筹学 统计学 Python 高数 英语 计算机
0 张三 61 95 81 82 84 92
1 赵四 72 100 76 90 89 69
2 王五 84 88 74 88 78 75

删除一列时,多了一个参数axis = 1

df.drop("英语", inplace=True, axis=1)
df
姓名 运筹学 统计学 Python 高数 计算机
0 张三 61 95 81 82 92
1 赵四 72 100 76 90 69
2 王五 84 88 74 88 75
df.drop(["运筹学", "高数"], inplace=True, axis=1)  # 删除两行
df
姓名 统计学 Python 计算机
0 张三 95 81 92
1 赵四 100 76 69
2 王五 88 74 75

drop 函数还可以根据条件删除数据,例如:

df.drop(df[df["统计学"]>95].index, inplace=True) # 删除所有统计学成绩高于95分的成绩
df
姓名 统计学 Python 计算机
0 张三 95 81 92
2 王五 88 74 75
df.reset_index(drop=True) # 重新设置 index,并舍弃原有的 index
姓名 统计学 Python 计算机
0 张三 95 81 92
1 王五 88 74 75

6.3.3. 合并数据#


Pandas 中比较常用的两个合并数据的方法是concatmerge。 当两个 DataFrame 数据表具有完全相同的列标签时,一般用concat,其他情况下多用merge

df1 = pd.DataFrame(
    {
        "姓名": ["张三", "李四", "王五"],
        "统计学": [85, 68, 90],
        "高数": [82, 63, 88],
        "英语": [84, 90, 78],
    }
)
df1
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
df2 = pd.DataFrame(
    {"姓名": ["马大帅", "陈小虎"], "统计学": [83, 59], "高数": [92, 70], "英语": [94, 78]}
)
df2
姓名 统计学 高数 英语
0 马大帅 83 92 94
1 陈小虎 59 70 78

两张表具有完全相同的列名,用concat合并的代码如下:

pd.concat([df1, df2])  #  注意中括号不等丢
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
0 马大帅 83 92 94
1 陈小虎 59 70 78

若要合并后的 index 重新命名,可以加参数ignore_index = True,让合并后数据的 index 重新从小到大命名:

pd.concat([df1, df2], ignore_index=True)
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
3 马大帅 83 92 94
4 陈小虎 59 70 78
  • concat 默认按行合并,若按列合并,可以在小括号内添加参数axis=1

假如有下面的数据:

df3 = pd.DataFrame({"姓名": ["张三", "李四", "王五"], "会计": [75, 78, 80], "管理学": [94, 96, 88]})
df3
姓名 会计 管理学
0 张三 75 94
1 李四 78 96
2 王五 80 88

df1 与 df3 的姓名相同,但列名不完全相同。我们想把 df3 的列添加到 df1 中,此时就要使用merge方法了,它的使用语法一般如下:

DataFrame.merge(right, how='inner', on=None)
right 需要合并的另一个 DataFrame 数据
how 默认为 'inner',表示内连接,取两个数据表中匹配字段的交集进行合并
'outer',表示外连接,取两个数据表中匹配字段的并集进行合并
'left',表示左连接,取左边数据表中匹配字段进行合并
'right',表示右连接,取右边数据表中匹配字段进行合并
on 匹配的字段(列),可以是一个或多个

因此,对于 df1 与 df3,用merge合并时,匹配的字段(列名)为’姓名’:

df1.merge(df3, on="姓名")
姓名 统计学 高数 英语 会计 管理学
0 张三 85 82 84 75 94
1 李四 68 63 90 78 96
2 王五 90 88 78 80 88

merge也能实现concat的合并效果,例如,合并 df1 与 df2:

df1.merge(df2, on=["姓名", "统计学", "高数", "英语"], how="outer")
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
3 陈小虎 59 70 78
4 马大帅 83 92 94

在上面的代码中,匹配的字段为所有的列,连接方式为外连接,实现结果与concat相同。若连接方式为其他类型,显示效果如下:

df1.merge(df2, on=["姓名", "统计学", "高数", "英语"], how="inner")  # 内连接时两个数据表匹配字段的交集为空
姓名 统计学 高数 英语
df1.merge(df2, on=["姓名", "统计学", "高数", "英语"], how="left")  # 左连接时保留左数据表的所有匹配字段
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
df1.merge(df2, on=["姓名", "统计学", "高数", "英语"], how="right")  # 右连接时保留右数据表的所有匹配字段
姓名 统计学 高数 英语
0 马大帅 83 92 94
1 陈小虎 59 70 78

在合并数据表时,若某些字段没有对应数据,Pandas 会自动用 NaN 替代,下面的例子展示了不同连接方式的效果。

df1 = pd.DataFrame(
    {
        "班级": ["一班", "二班", "一班"],
        "姓名": ["张三", "李四", "王五"],
        "性别": ["男", "男", "女"],
        "籍贯": ["北京", "上海", "重庆"],
    }
)
df1
班级 姓名 性别 籍贯
0 一班 张三 北京
1 二班 李四 上海
2 一班 王五 重庆
df2 = pd.DataFrame({"姓名": ["张三", "陈小虎"], "统计学": [85, 59]})
df2
姓名 统计学
0 张三 85
1 陈小虎 59
df1.merge(df2, on="姓名")
班级 姓名 性别 籍贯 统计学
0 一班 张三 北京 85
df1.merge(df2, on="姓名", how="outer")  # 某些字段没有对应数据,则显示为 NaN
班级 姓名 性别 籍贯 统计学
0 一班 张三 北京 85.0
1 二班 李四 上海 NaN
2 一班 王五 重庆 NaN
3 NaN 陈小虎 NaN NaN 59.0
df1.merge(df2, on="姓名", how="left")
班级 姓名 性别 籍贯 统计学
0 一班 张三 北京 85.0
1 二班 李四 上海 NaN
2 一班 王五 重庆 NaN
df1.merge(df2, on="姓名", how="right")
班级 姓名 性别 籍贯 统计学
0 一班 张三 北京 85
1 NaN 陈小虎 NaN NaN 59

6.4. 查询,排序,分组汇总数据#


6.4.1. 查询数据#


对 DataFrame 数据按照一定条件查询时,一般会用到一些比较运算符,例如:>, >=, ==, <, <=, !=。条件查询一般只针对列数据,查询时,Pandas 首先生成布尔索引 True 或 False,再通过指定索引生成查询后的数据。

例如,下面的代码:

import pandas as pd

df = pd.DataFrame(
    {
        "姓名": ["张三", "李四", "王五"],
        "统计学": [85, 68, 90],
        "高数": [82, 63, 88],
        "英语": [84, 90, 78],
    }
)
df
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
df["统计学"] > 83  # 统计学成绩大于83的布尔索引
0     True
1    False
2     True
Name: 统计学, dtype: bool
df[df["统计学"] > 83]  # 生成统计学成绩大于83的数据
姓名 统计学 高数 英语
0 张三 85 82 84
2 王五 90 88 78

对于多条件查询,可以用&表示并:两个条件都要满足,用|表示或:两个条件满足一个即可。举例:

df[(df["高数"] > 65) & (df["英语"] > 80)]  # 查询高数成绩大于65,英语成绩大于80的数据,注意小括号
姓名 统计学 高数 英语
0 张三 85 82 84
df[(df["高数"] > 65) | (df["英语"] > 80)]  # 查询高数成绩大于65,英语成绩大于80的数据,注意小括号
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78

6.4.2. 排序数据#


在处理数据时,经常要对数据排序。 Pandas 提供了一个方便的函数sort_values对数值进行排序,它的使用语法如下:

DataFrame.sort_values(by, axis=0, ascending=True, inplace=False, na_position='last', ignore_index=False)
by 需要排序的列名(axis=0)或行名(axis=1),可以有多个
axis 0 表示对列排序,1 表示对行排序,默认是0
ascending 升序还是降序,默认是升序排列
inplace 是否替换原有数据,默认为 False 不替换
na_position 缺失值 NaN 放在排序的前面还是后面,默认是放在后面
ignore_index 如果为 True,则排序后的行号重新标号,默认是 False

对统计学成绩升序排列:

df.sort_values(by="统计学")
姓名 统计学 高数 英语
1 李四 68 63 90
0 张三 85 82 84
2 王五 90 88 78

对统计学与高数成绩降序排序:

df.sort_values(by=["统计学", "高数"], ascending=False)
姓名 统计学 高数 英语
2 王五 90 88 78
0 张三 85 82 84
1 李四 68 63 90

在参数by中,统计学在前,高数在后,表示:先把统计学成绩降序排序,若统计学成绩有相同的,再按高数成绩降序排序。

由于排序时列标签经常被打乱,可以使用ignore_index = True重新排列标签,inplace = True改变原始数据。

df.sort_values(by="统计学")  # 排序,可以看出列标也发生了变动
姓名 统计学 高数 英语
1 李四 68 63 90
0 张三 85 82 84
2 王五 90 88 78
df.sort_values(by="统计学", ignore_index=True)  # 排序后重新排列标签
姓名 统计学 高数 英语
0 李四 68 63 90
1 张三 85 82 84
2 王五 90 88 78
df.sort_values(by="统计学", ignore_index=True)  # 排序后,没有替换原始数据
df
姓名 统计学 高数 英语
0 张三 85 82 84
1 李四 68 63 90
2 王五 90 88 78
df.sort_values(by="统计学", ignore_index=True, inplace=True)  # 排序后,替换了原始数据
df
姓名 统计学 高数 英语
0 李四 68 63 90
1 张三 85 82 84
2 王五 90 88 78

6.4.3. 分组汇总groupby#


在数据分析时,经常也需要将数据分组汇总。例如,在统计学生的期末考试成绩时,可能需要根据专业或者班级来分别计算平均成绩、最高成绩等。此时,可以利用 Pandas 的groupby函数方便地实现这些功能。

groupby 经常结合一些汇总统计量使用,例如 mean(均值),max(最大值),min(最小值),median(中位数),std(标准差),mad(平均绝对偏差),count(计数),size(也是计数), skew(均值),quantile(分位数)。

下面的代码按性别汇总了平均成绩:

df = pd.DataFrame(
    [
        ["张三", "一班", "男", 85, 68, 90],
        ["李四", "二班", "男", 82, 63, 88],
        ["王五", "二班", "女", 84, 90, 78],
        ["魏小小", "三班", "女", 75, 68, 80],
        ["马小倩", "二班", "女", 69, 55, 63],
        ["陈小虎", "一班", "男", 89, 95, 93],
    ],
    columns=["姓名", "班级", "性别", "统计学", "高数", "英语"],
)
df
姓名 班级 性别 统计学 高数 英语
0 张三 一班 85 68 90
1 李四 二班 82 63 88
2 王五 二班 84 90 78
3 魏小小 三班 75 68 80
4 马小倩 二班 69 55 63
5 陈小虎 一班 89 95 93
df.groupby("性别").count()
姓名 班级 统计学 高数 英语
性别
3 3 3 3 3
3 3 3 3 3
df.groupby("性别").size()  # 与 count 略有区别
性别
女    3
男    3
dtype: int64
df2 = df.groupby("性别").size()
df2
性别
女    3
男    3
dtype: int64
df.groupby("性别").mean(
    numeric_only=True
)  # 按性别汇总平均成绩, numeric_only = True 表示仅选择对函数有效的数据列
统计学 高数 英语
性别
76.000000 71.000000 73.666667
85.333333 75.333333 90.333333
df.groupby(["班级", "性别"]).mean(numeric_only=True)  # 按班级、性别汇总平均成绩
统计学 高数 英语
班级 性别
一班 87.0 81.5 91.5
三班 75.0 68.0 80.0
二班 76.5 72.5 70.5
82.0 63.0 88.0

也可以将汇总后的数据赋值给一个新的 DataFrame 变量:

df2 = df.groupby(["班级", "性别"]).mean(numeric_only=True)  # 按班级、性别汇总平均成绩
df2
统计学 高数 英语
班级 性别
一班 87.0 81.5 91.5
三班 75.0 68.0 80.0
二班 76.5 72.5 70.5
82.0 63.0 88.0
df2.index
MultiIndex([('一班', '男'),
            ('三班', '女'),
            ('二班', '女'),
            ('二班', '男')],
           names=['班级', '性别'])

汇总后,发现分类汇总的类别作为新的 index 了(一列或多列分类汇总都是这样)。可以通过加一个参数as_index=False设置。

df2 = df.groupby(["班级", "性别"], as_index=False).mean(numeric_only=True)  # 按班级、性别汇总平均成绩
df2
班级 性别 统计学 高数 英语
0 一班 87.0 81.5 91.5
1 三班 75.0 68.0 80.0
2 二班 76.5 72.5 70.5
3 二班 82.0 63.0 88.0

若要汇总多个统计量,则可以跟agg方法,例如,下面的代码按照班级与性别,汇总了每个班的最高、最低、平均成绩。

df2.groupby(["班级", "性别"]).agg(["max", "min", "mean"], numeric_only=True)  # 汇总了每个班每个性别的最高、最低、平均成绩
统计学 高数 英语
max min mean max min mean max min mean
班级 性别
一班 87.0 87.0 87.0 81.5 81.5 81.5 91.5 91.5 91.5
三班 75.0 75.0 75.0 68.0 68.0 68.0 80.0 80.0 80.0
二班 76.5 76.5 76.5 72.5 72.5 72.5 70.5 70.5 70.5
82.0 82.0 82.0 63.0 63.0 63.0 88.0 88.0 88.0

6.4.4. reset_index函数#


也可以在 groupby 函数后面再使用函数reset_index将多个index作为新的列标题。

df2 = df.groupby(["班级", "性别"]).mean(numeric_only=True).reset_index()  # 按班级、性别汇总平均成绩
df2
班级 性别 统计学 高数 英语
0 一班 87.0 81.5 91.5
1 三班 75.0 68.0 80.0
2 二班 76.5 72.5 70.5
3 二班 82.0 63.0 88.0

函数reset_index可以跟drop=True参数去除 index。

df2 = (
    df.groupby(["班级", "性别"]).mean(numeric_only=True).reset_index(drop=True)
)  # 按班级、性别汇总平均成绩
df2
统计学 高数 英语
0 87.0 81.5 91.5
1 75.0 68.0 80.0
2 76.5 72.5 70.5
3 82.0 63.0 88.0

函数reset_index还可以跟 names 参数重新定义列标题的名字。

df2 = (
    df.groupby(["班级", "性别"]).mean(numeric_only=True).reset_index(names=["class", "sex"])
)  # 按班级、性别汇总平均成绩
df2
class sex 统计学 高数 英语
0 一班 87.0 81.5 91.5
1 三班 75.0 68.0 80.0
2 二班 76.5 72.5 70.5
3 二班 82.0 63.0 88.0
df2.columns
Index(['class', 'sex', '统计学', '高数', '英语'], dtype='object')

6.5. 与数组或列表的转换#


使用 Pandas 中的values方法可以将 DataFrame 数据转化为 Numpy 的数组形式。例如,对于前面的数据例子:

df = pd.DataFrame(
    {
        "姓名": ["张三", "李四", "王五"],
        "统计学": [85, 68, 90],
        "高数": [82, 63, 88],
        "英语": [84, 90, 78],
    }
)
df[["统计学", "高数", "英语"]].values
array([[85, 82, 84],
       [68, 63, 90],
       [90, 88, 78]])

也可以对某列数据,使用tolist()方法,将该列数据转化为 Python 本身的列表类型 list:

df["统计学"].tolist()
[85, 68, 90]

除了字典类型外,Pandas 也支持将一维或二维的数组或列表转化成 DataFrame 类型。

a = [1, 2, 3]
pd.DataFrame(a)
0
0 1
1 2
2 3
a = [[1, 2, 3], [4, 5, 6]]
pd.DataFrame(a)
0 1 2
0 1 2 3
1 4 5 6
import numpy as np

b = np.array([3, 3, 4])
pd.DataFrame(b)
0
0 3
1 3
2 4
c = np.matrix([[1, 2], [3, 4]])
pd.DataFrame(c)
0 1
0 1 2
1 3 4

6.6. 自定义函数#


Pandas 可以使用apply调用自定义函数。

import numpy as np

df["统计学"].apply(np.sqrt)  # 调用 Numpy 中的求平方根函数对统计学成绩每个元素求平方根
0    9.219544
1    8.246211
2    9.486833
Name: 统计学, dtype: float64

我们也可以使用lambda定义一个匿名函数在apply里面调用:

df["高数新"] = df["高数"].apply(lambda x: x - 10)  # 对每一个高数成绩都减去 10 分,并赋值给一个新的列

apply 也可以调用更复杂的自定义函数,例如,下面定义一个成绩替换函数,将分数替换为”优良中差”:

def replace_score(x):
    if x >= 90:
        return "优"
    elif x >= 80:
        return "良"
    elif x >= 60:
        return "中"
    else:
        return "差"


df["英语"].apply(replace_score)
0    良
1    优
2    中
Name: 英语, dtype: object

6.7. 数据清洗#


6.7.1. 数据替换replace, fillna#


在进行数据处理时,经常需要对原始数据的一些异常值或错误值进行批量处理。 Pandas 提供了replace函数方便地进行这项操作。replace函数第一项为原数据中的值,第二项为需要替换的值。例如,有下面的学生成绩:

import pandas as pd
import numpy as np

df = pd.DataFrame(
    np.array([[85, 68, 90], [82, 63, 88], [84, 90, 78]]),
    columns=["统计学", "高数", "英语"],
    index=["张三", "李四", "王五"],
)
df
统计学 高数 英语
张三 85 68 90
李四 82 63 88
王五 84 90 78

将其中的分数 90 替换为缺失值 NaN,注意,replace返回了一个新的数据,但原始数据并没有改变。

df.replace(90, np.nan)
统计学 高数 英语
张三 85 68.0 NaN
李四 82 63.0 88.0
王五 84 NaN 78.0
df  # 原始数据并没有变
统计学 高数 英语
张三 85 68 90
李四 82 63 88
王五 84 90 78

若需要原始数据改变,需要跟上参数inplace = True ,下面讲到的函数fillna drop_duplicatesdropnarename等也可以跟这个参数将原始数据改变。

df.replace(90, np.nan, inplace=True)
df  # 原始数据改变了
统计学 高数 英语
张三 85 68.0 NaN
李四 82 63.0 88.0
王五 84 NaN 78.0

Pandas 提供fillna函数批量替换数据表中的缺失值 NaN。例如:

df.fillna(80)  # 将缺失值替换为 80
统计学 高数 英语
张三 85 68.0 80.0
李四 82 63.0 88.0
王五 84 80.0 78.0
df.fillna("missing")  # 将缺失值替换为一个字符串
统计学 高数 英语
张三 85 68.0 missing
李四 82 63.0 88.0
王五 84 missing 78.0

Pandas 还可以直接调用isnull函数判断数据是否为缺失值:

df["英语"].isnull()
张三     True
李四    False
王五    False
Name: 英语, dtype: bool

6.7.2. 数据类型转换 astype#


很多时候,我们需要对原始数据进行数据转换。例如,一些原始数据中的数字为文本类型,我们需要讲它们转化为数值类型才能进行之后的计算操作。常见的数据类型转换函数为astype

import pandas as pd
import numpy as np

df = pd.DataFrame(
    np.array([["85", "68", "90"], ["82", "63", "88"], ["84", "90", "78"]]),
    columns=["统计学", "高数", "英语"],
    index=["张三", "李四", "王五"],
)
df
统计学 高数 英语
张三 85 68 90
李四 82 63 88
王五 84 90 78
print(df.dtypes)
统计学    object
高数     object
英语     object
dtype: object

上面的 pandas 数据为文本类型,我们利用astype将其转化为浮点型数值类型。

df = df[["统计学", "高数", "英语"]].astype(float)
df
统计学 高数 英语
张三 85.0 68.0 90.0
李四 82.0 63.0 88.0
王五 84.0 90.0 78.0
print(df.dtypes)
统计学    float64
高数     float64
英语     float64
dtype: object

改变as_type小括号中的参数为 int,str等,我们同样可以将原始数据转化为整数型,字符串型等。

6.7.3. 重复值处理drop_duplicates#


在数据量比较大时,经常会遇到重复数据的问题,可以使用函数drop_duplicates将重复数据去掉。例如:

df = pd.DataFrame(
    [
        ["一班", "男", 85, 68, 90],
        ["一班", "男", 85, 68, 90],
        ["二班", "女", 84, 90, 78],
        ["三班", "女", 75, 68, 80],
        ["二班", "女", 69, 55, 63],
        ["一班", "男", 89, 95, 93],
    ],
    columns=["班级", "性别", "统计学", "高数", "英语"],
    index=["张三", "张三", "王五", "马六", "陈小虎", "魏大帅"],
)
df
班级 性别 统计学 高数 英语
张三 一班 85 68 90
张三 一班 85 68 90
王五 二班 84 90 78
马六 三班 75 68 80
陈小虎 二班 69 55 63
魏大帅 一班 89 95 93

前两行的数据完全相同,使用函数drop_duplicates处理:

df.drop_duplicates()
班级 性别 统计学 高数 英语
张三 一班 85 68 90
王五 二班 84 90 78
马六 三班 75 68 80
陈小虎 二班 69 55 63
魏大帅 一班 89 95 93

drop_duplicates的语法如下:

DataFrame.drop_duplicates(keep='first', inplace=False, ignore_index=False)
keep 'fist'表示除了第一个重复的行都删除
'last'表示除了最后一个重复的行都删除
False 表示删除所有重复的行
inplace 若 inplace=True,表示处理后的数据替换原数据
ignore_index 如果为 True,则排序后的行号重新标号,默认是 False

6.7.4. 缺失值处理drop_na#


dropna函数可以将数据表中包含缺失值的行去除掉,与drop_duplicates的语法类似。它的语法如下:

DataFrame.dropna(axis=0, how='any', thresh=None, inplace=False, ignore_index=False)
axis axis=0 表示删除含空值所在行,axis=1 表示删除含空值所在列
how 默认为 how='any',表示若有空值,就删除
how='all',表示全部是空值才删除该行或该列
thresh 空值的个数,作为行或列的删除标准
inplace 若 inplace=True,表示处理后的数据替换原数据
ignore_index 如果为 True,则排序后的行号重新标号,默认是 False

df = pd.DataFrame(
    {"统计学": [85, 68, np.nan], "高数": [82, 75, 88], "英语": [np.nan, 90, 78]},
    index=["张三", "李四", "王五"],
)
df
统计学 高数 英语
张三 85.0 82 NaN
李四 68.0 75 90.0
王五 NaN 88 78.0
df.dropna()  # 默认为删除含有空值的行,并且只要行里面有 Nan 值,就删除
统计学 高数 英语
李四 68.0 75 90.0
df.dropna(axis=1)  # 删除含有空值的列
高数
张三 82
李四 75
王五 88
df.dropna(how="all")  # 全部是空值才删除
统计学 高数 英语
张三 85.0 82 NaN
李四 68.0 75 90.0
王五 NaN 88 78.0

6.7.5. 重命名rename#


在处理数据表时,有时候要给数据的行名或列名重命名,此时要用到rename函数,rename函数中的参数为一个字典形式:替换前的名字为字典的 key,替换后的名字为字典的 value。

df = pd.DataFrame({"统计学": [85, 68, 90], "高数": [82, 63, 88], "英语": [84, 90, 78]})
df
统计学 高数 英语
0 85 82 84
1 68 63 90
2 90 88 78
df.rename(columns={"统计学": "科目A", "高数": "科目B"})  # 更改其中两列的列名
科目A 科目B 英语
0 85 82 84
1 68 63 90
2 90 88 78

tip

若要处理后的数据替换原数据,则要加参数inplace=True

6.8. 时间序列数据处理#


6.8.1. to_datetimedt.strftime#


实际工作中,经常会遇到大量的时间序列数据。这些时间序列的原始数据一般为文本格式,我们需要将它转化为时间日期格式。 Pandas 提供了 to_datetime函数将其他日期时间格式转化为 Python 的日期时间格式。例如:

import pandas as pd

pd.to_datetime("20200514")
Timestamp('2020-05-14 00:00:00')
pd.to_datetime("2020/05/14")
Timestamp('2020-05-14 00:00:00')
pd.to_datetime("2020-05-14")
Timestamp('2020-05-14 00:00:00')

在上面的代码中可以看出,to_datetime方法将不同类型的时间数据字符串转化为 Timestamp 格式:年-月-日 时:分:秒。另外一个函数dt.strftime可以将 Pandas 的 Timestamp 格式数据转化为其他格式的字符串,例如:

df = pd.DataFrame(
    {
        "入校时间": ["2016-09-15", "2017-03-23", "2018-09-05"],
        "统计学": [85, 68, 90],
        "高数": [82, 63, 88],
        "英语": [84, 90, 78],
        "姓名": ["张三", "李四", "王五"],
    }
)
df
入校时间 统计学 高数 英语 姓名
0 2016-09-15 85 82 84 张三
1 2017-03-23 68 63 90 李四
2 2018-09-05 90 88 78 王五
df.iloc[0, 0]  # 原始数据中的时间为字符串
'2016-09-15'
df["入校时间"] = pd.to_datetime(df["入校时间"])  # 将原始数据中的时间转化为 python 的日期格式
df
入校时间 统计学 高数 英语 姓名
0 2016-09-15 85 82 84 张三
1 2017-03-23 68 63 90 李四
2 2018-09-05 90 88 78 王五
df.iloc[0, 0]
Timestamp('2016-09-15 00:00:00')
df["入校时间"].dt.strftime("%m-%d-%y")  # 将日期格式转化为自定义格式的字符串
0    09-15-16
1    03-23-17
2    09-05-18
Name: 入校时间, dtype: object
df["入校时间"].dt.strftime("%B-%d-%y")  # 将日期格式转化为自定义格式的字符串,小写 Y 年份显示两位
0    September-15-16
1        March-23-17
2    September-05-18
Name: 入校时间, dtype: object
df["入校时间"].dt.strftime("%B-%d-%Y")  # 将日期格式转化为自定义格式的字符串,大写 Y 则年份显示四位
0    September-15-2016
1        March-23-2017
2    September-05-2018
Name: 入校时间, dtype: object
df["入校时间"].dt.strftime("%Y-%W")  # 将日期格式转化为自定义格式的字符串,大写 W 显示的为该年第几周
0    2016-37
1    2017-12
2    2018-36
Name: 入校时间, dtype: object
df["入校时间"].dt.strftime("%Y-%m-%w")  # 将日期格式转化为自定义格式的字符串,小写 w 显示的为该月第几周
0    2016-09-4
1    2017-03-4
2    2018-09-3
Name: 入校时间, dtype: object

6.8.2. 数据聚合函数resample#


对于很多时间序列数据,有时候经常需要对它们按一定周期进行数据聚合,可以用 Pandas 提供的resample函数。resample 方法中的小括号中用不同参数表示聚合的频率。举例:

import numpy as np

index = pd.date_range("12/24/2019", periods=10, freq="D")  # 生成一个时间序列,频率为天,周期数为 10
index
DatetimeIndex(['2019-12-24', '2019-12-25', '2019-12-26', '2019-12-27',
               '2019-12-28', '2019-12-29', '2019-12-30', '2019-12-31',
               '2020-01-01', '2020-01-02'],
              dtype='datetime64[ns]', freq='D')
df = pd.DataFrame(np.arange(10), index=index)  # 创建一个包含时间序列的 DataFrame 数据表
df
0
2019-12-24 0
2019-12-25 1
2019-12-26 2
2019-12-27 3
2019-12-28 4
2019-12-29 5
2019-12-30 6
2019-12-31 7
2020-01-01 8
2020-01-02 9
df.resample("M").sum()  # 用 resample 方法按月('M')对数据聚合
/var/folders/qz/d57_41f55ylf02cqq8twh3540000gn/T/ipykernel_7222/359928143.py:1: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.
  df.resample("M").sum()  # 用 resample 方法按月('M')对数据聚合
0
2019-12-31 28
2020-01-31 17
df.resample("3D").sum()  # 用 resample 方法每 3 天('3D')对数据聚合
0
2019-12-24 3
2019-12-27 12
2019-12-30 21
2020-01-02 9
df.resample("w").sum()  # 用 resample 方法每周('w')对数据聚合
/var/folders/qz/d57_41f55ylf02cqq8twh3540000gn/T/ipykernel_7222/3844630848.py:1: FutureWarning: 'w' is deprecated and will be removed in a future version, please use 'W' instead.
  df.resample("w").sum()  # 用 resample 方法每周('w')对数据聚合
0
2019-12-29 15
2020-01-05 30

resample的其他参数中, ‘T’ 表示分钟,‘H’ 表示小时,除了跟sum()方法外,还可以跟asfreq()ffillapply()等。

6.9. Pandas 画图#


除了结合 matplotlib 与 seaborn 画图外,Pandas 也有自己的画图函数plot,它的语法一般为:

DataFrame.plot(x=None,y=None, kind='line',subplots=False, title=None)
x 横坐标数据
y 纵坐标数据
kind 默认是线图,还可以是‘bar’,'barh','box','pie','scatter','hist'等
subplots 是否将每一列数据分别生成一个子图
title 图形的标题

import pandas as pd

df = pd.DataFrame(
    {"statistics": [85, 68, 90], "math": [82, 63, 88], "English": [84, 90, 78]}
)
df
statistics math English
0 85 82 84
1 68 63 90
2 90 88 78
df.plot()
<Axes: >
_images/1faaeea6e976cdabee7843fe4255300b1c9557c13b4fe68b3790a4bab110aeee.png

从上图可以看出,Pandas 的plot默认对每一列数据,画一个线图。

df.plot(kind="bar", title="My picture")  # 画出柱状图
<Axes: title={'center': 'My picture'}>
_images/2d3f443fb2a552b8ecd1165bb6cc2f283a2e9fef54c662f1c812cca1c18e8183.png
df.plot(kind="bar", subplots=True)  # 对每一列数据非别生成一个子图
array([<Axes: title={'center': 'statistics'}>,
       <Axes: title={'center': 'math'}>,
       <Axes: title={'center': 'English'}>], dtype=object)
_images/d75919c42c5a1a8c0e104de05aab24b4ca1e650741a3717b87b72aed744f1d5a.png
df.plot(x="math", y="statistics", kind="scatter")  # 指定横坐标与纵坐标,生成一个散点图
<Axes: xlabel='math', ylabel='statistics'>
_images/cbba063b9094b1878c5d594bf2b41a838f21103f3501609b973c99c034342fc4.png

tip

pandas 有大量处理数据的函数以及众多参数设置,限于篇幅关系,本书不详细赘述。读者使用 pandas 处理数据时,可以进一步查阅官方文档。

6.10. 练习#


Exercise 6.1

自定义一个包含10个学生,3门课程的成绩单 excel 文件,其中包含不及格的数据。用 pandas 读取该文件,剔除有成绩不及格的学生条目,并输出剩下成绩单各科目的平均成绩,最高成绩;最后将剩下学生所有科目的平均成绩排序并输出名次。