本文通过一个分数统计分析的案例,串联介绍了 Pandas 的十个高级操作,帮助初学者快速掌握 Pandas 的一些常用技巧。
import pandas as pd
pd.__version__
'1.5.3'
一次考试测验后,语文老师和数学老师分别批改完答卷,并统计了所有考生的得分。作为班主任,需要将所有学生的语文、数学两科成绩进行汇总。
df1 = pd.DataFrame({"Name": ["张三", "李四", "王五"], "Chinese": [36, 60, 81]})
df2 = pd.DataFrame({"Name": ["李四", "王五", "赵六"], "Math": [49, 64, 90]})
pd.merge(df1, df2, on="Name", how="inner")
Name | Chinese | Math | |
---|---|---|---|
0 | 李四 | 60 | 49 |
1 | 王五 | 81 | 64 |
内连接 (INNER JOIN
) 是一种多表合并方式,它只保留键的交集。在这个场景中,我们需要键的并集,那些缺失的分数表示缺考,此时可以使用外连接 (OUTER JOIN
)。
df = pd.merge(df1, df2, on="Name", how="outer")
df
Name | Chinese | Math | |
---|---|---|---|
0 | 张三 | 36.0 | NaN |
1 | 李四 | 60.0 | 49.0 |
2 | 王五 | 81.0 | 64.0 |
3 | 赵六 | NaN | 90.0 |
此外,多表合并还有一种方式 concat
,它简单地将行和列拼接在一起。当多张表有相同列名时,合并后保留成一列;不同列名时,合并后保留所有列。
pd.concat([df1, df2])
Name | Chinese | Math | |
---|---|---|---|
0 | 张三 | 36.0 | NaN |
1 | 李四 | 60.0 | NaN |
2 | 王五 | 81.0 | NaN |
0 | 李四 | NaN | 49.0 |
1 | 王五 | NaN | 64.0 |
2 | 赵六 | NaN | 90.0 |
缺失的分数表示学生缺考,因此默认记为零分。
dfa = df.fillna(0)
dfa
Name | Chinese | Math | |
---|---|---|---|
0 | 张三 | 36.0 | 0.0 |
1 | 李四 | 60.0 | 49.0 |
2 | 王五 | 81.0 | 64.0 |
3 | 赵六 | 0.0 | 90.0 |
通过 set_index
方法设置索引列,通过 reset_index
方法重置索引列。
dfi = dfa.set_index("Name")
dfi
Chinese | Math | |
---|---|---|
Name | ||
张三 | 36.0 | 0.0 |
李四 | 60.0 | 49.0 |
王五 | 81.0 | 64.0 |
赵六 | 0.0 | 90.0 |
dfi.reset_index()
Name | Chinese | Math | |
---|---|---|---|
0 | 张三 | 36.0 | 0.0 |
1 | 李四 | 60.0 | 49.0 |
2 | 王五 | 81.0 | 64.0 |
3 | 赵六 | 0.0 | 90.0 |
由于这次考生的成绩不理想,需要对分数进行“美化”。具体操作为:先对分数开平方根,再乘以 10,最后取整数部分。
dff = dfi.applymap(lambda x: round(10 * x**0.5))
dff
Chinese | Math | |
---|---|---|
Name | ||
张三 | 60 | 0 |
李四 | 77 | 70 |
王五 | 90 | 80 |
赵六 | 0 | 95 |
各科成绩都在 80 分及以上的同学可以被评为优秀学生,请查询出这些学生。
dff.query("Chinese>=80 and Math>=80")
Chinese | Math | |
---|---|---|
Name | ||
王五 | 90 | 80 |
当前的成绩表使用不同列来表示不同学科的成绩,如果未来需要增加英语、物理、化学、生物、政治、历史、地理等科目,就需要不断扩充列,导致表格变得非常宽。为了满足支持更多科目成绩的需求,另一种数据存储方式是使用长表,其中一列记录学科名称,另一列记录学生在该学科的分数。为了将现有的宽表转换为长表,可以采用以下方法。
dfl = pd.melt(
dff.reset_index(),
id_vars=["Name"],
value_vars=["Chinese", "Math"],
var_name="Subject",
value_name="Score",
)
dfl
Name | Subject | Score | |
---|---|---|---|
0 | 张三 | Chinese | 60 |
1 | 李四 | Chinese | 77 |
2 | 王五 | Chinese | 90 |
3 | 赵六 | Chinese | 0 |
4 | 张三 | Math | 0 |
5 | 李四 | Math | 70 |
6 | 王五 | Math | 80 |
7 | 赵六 | Math | 95 |
透视一下班级里学生各科的成绩。
dfl.pivot_table(index="Name", columns="Subject", values="Score", aggfunc="sum")
Subject | Chinese | Math |
---|---|---|
Name | ||
张三 | 60 | 0 |
李四 | 77 | 70 |
王五 | 90 | 80 |
赵六 | 0 | 95 |
统计班级里每位学生的各科成绩总分。
dfl.groupby("Name").sum("Score")
Score | |
---|---|
Name | |
张三 | 60 |
李四 | 147 |
王五 | 170 |
赵六 | 95 |
聚合函数还支持在每组数据中应用一个函数,例如计算每位学生的各科成绩的极差,以了解每位学生的偏科情况。
dfl.groupby("Name")["Score"].apply(lambda x: x.max() - x.min())
Name 张三 60 李四 7 王五 10 赵六 95 Name: Score, dtype: int64
对连续值进行分桶操作,将其转换为离散值,是一种常用的数据处理方法。例如,我们希望将连续的分数值 Score 划分为不及格、及格、优秀三个等级,通过 60 分和 80 分作为分界点进行分桶。
dfl["Category"] = pd.cut(
dfl["Score"], bins=[0, 60, 80, 100], labels=["不及格", "及格", "优秀"], right=False
)
dfl
Name | Subject | Score | Category | |
---|---|---|---|---|
0 | 张三 | Chinese | 60 | 及格 |
1 | 李四 | Chinese | 77 | 及格 |
2 | 王五 | Chinese | 90 | 优秀 |
3 | 赵六 | Chinese | 0 | 不及格 |
4 | 张三 | Math | 0 | 不及格 |
5 | 李四 | Math | 70 | 及格 |
6 | 王五 | Math | 80 | 优秀 |
7 | 赵六 | Math | 95 | 优秀 |
分析每位学生不及格、及格、优秀三档的分布情况,可以清楚看到王五有两门科目都是优秀。
pd.crosstab(dfl["Name"], dfl["Category"])
Category | 不及格 | 及格 | 优秀 |
---|---|---|---|
Name | |||
张三 | 1 | 1 | 0 |
李四 | 0 | 2 | 0 |
王五 | 0 | 0 | 2 |
赵六 | 1 | 0 | 1 |