课程
Pandas 是 Python 数据科学家的事实标准工具箱,用于简化数据分析:例如,在开始分析之前,您可以用它来收集、探索和整理数据。Pandas 通过众多 I/O 与便捷的数据操作函数,让这些步骤变得轻松易行。此外,正如您将在本教程后文学到的,它也具备一定的可视化能力(通过使用 matplotlib)。
尽管 Pandas 的数据结构非常强大且灵活,但也伴随一定复杂性,尤其对初学者来说,可能会让分析变得更难而不是更易。本教程将重点讲解如何避免“踩坑”,写出更地道的 Pandas 代码。除了学习如何加载、探索和清洗数据,您还将进一步了解以下五个主题:
当然,本教程并不穷尽所有做法;Pandas 功能十分丰富,毫无疑问还有很多其他方式可以让您的 Pandas 代码更地道。
事不宜迟,我们开始吧!
第一步:加载并探索数据
如往常一样,数据分析从加载和探索数据开始。本节作为热身,您将使用 read_csv() 加载世界大学排名数据,随后用 head() 与 describe() 快速浏览数据。如果您想系统入门 Pandas,建议学习 DataCamp 的三门 Pandas 系列课程,从 Pandas Foundations 开始。
如果这些您都已经熟悉,可以跳过本节,直接查看如何以更地道的方式在 Pandas 中进行索引。
导入包
首先,让我们导入常用的数据科学 Python 包。您或许已经猜到,Pandas 必不可少。
# If you're working with a notebook, don't forget to use Matplotlib magic!
%matplotlib inline
# Import pandas under the alias pd import pandas as pd # Import seaborn under the alias sns import seaborn as sns # Set the Seaborn theme if desired sns.set_style('darkgrid')
此外,您还可以使用 watermark 包:这是一款让笔记本更可复现的好工具。与同事协作时,可复现性非常重要。未来的您也会感谢现在的自己!关于可复现数据科学,您可以在这里阅读更多内容。
数据
为开始本教程,您还需要获取一些“真实世界”的数据。本教程将使用来自 Kaggle 的数据集。如果您不熟悉 Kaggle,它是最大的 数据科学与机器学习 社区之一;学会基础后,它也是深入学习数据科学的好去处。
您将使用的数据集是 World University Rankings。
如上所述,Pandas 提供了许多从不同数据源读取数据的便捷方法(可在这里了解更多)。由于数据为 CSV 格式,我们将使用 .read_csv 方法。但在开始之前,您需要先从 Kaggle 下载数据(或者如果您不想注册帐号,可以从代码仓库的 data 文件夹获取)。
下载完成后,您应有一个名为 data 的文件夹,包含多个 csv 文件。
接下来,您将使用 泰晤士高等教育世界大学排名(简称 Times)与 世界大学学术排名(简称上海排名)的数据。data 文件夹中还有第三套排名系统 CWUR,但知名度较低,暂时可以忽略。
# Import Times Higher Education World University Rankings data
times_df = pd.read_csv('data/timesData.csv', thousands=",")
# Import Academic Ranking of World Universities data
shanghai_df = pd.read_csv('data/shanghaiData.csv')
快速查看数据
正如您在 DataCamp 的探索性数据分析教程中学到的,Pandas 提供了快速查看 DataFrame 的方法,即 .head() 获取前 n 行(默认 n 为 5),以及 .describe() 获取统计摘要。
运行下面的代码来回顾这些函数。数据已分别加载到 times_df 与 shanghai_df 变量中:
现在您已经加载好数据,可以开始思考如何用更地道的 Pandas 方式来操作数据了!
我们的地道 Pandas 代码技巧
1. 索引(Indexing)
首先,写出更地道的 Pandas 代码意味着要充分利用索引的威力。索引指的是从 DataFrame 中选择子集。
刚上手 Pandas 时,您可能需要一些时间来适应索引的工作方式。事实上,如果您用过(Python)列表,应该已经熟悉用方括号 [] 配合冒号 : 选择元素。这种方法同样适用于 DataFrame。
此外,还有两种更地道的子集选择方法:iloc 与 loc:
loc基于标签(label)。这意味着当您写loc[2]时,您在查找索引标签为2的那一行(或元素)。iloc基于位置(position)。这意味着当您写iloc[2]时,您在查找位于位置索引2的那一行(或元素)。
在下方的 DataCamp Light 模块中,在 IPython 控制台里输入 times_df.loc[2] 与 times_df.iloc[2] 试试看,观察它们的区别。然后使用 loc、iloc 以及传统方括号完成下面的练习!
当然,探索远不止简单地从行列中选择或切片。当您将 loc 与 iloc 与布尔数组等结合使用时,会更加有趣。
您会发现这已经有点超出按列或按索引选择的范畴;它更像是在“查询”您的数据!
loc 与 iloc 非常适合对数据进行索引,但当您连续使用两组方括号时要小心!Pandas 可能会返回视图的副本,这会让您不清楚自己到底在操作什么!因此,当 loc 或 iloc 与两组方括号组合使用时,请三思。
注意,为查询数据,Pandas 还提供了 query() 函数(自 0.13 版起可用),可以快速检查数据。例如,您可以写一个查询,查看哪些大学的 total_score 略低于 50:
当然,关于查询数据还有很多做法!不妨尝试自己构造一些查询,并在上方 DataCamp Light 模块的 IPython 控制台中测试。
如果一时缺少灵感,可以尝试返回在本国排名第一、以及世界排名第一的大学。此外,您还可以查询各大学的校友数量。
不知道如何构造查询?请参考下面的一些示例:
shanghai_df.query("national_rank == 1 and world_rank == 1")
shanghai_df.query("alumni < 20")
2. 方法链(Method chaining)
方法链是 Pandas 的典型用法,但刚开始使用这个 Python 数据操作库时,可能并不直观。它的本质是在同一个对象上连续调用多个方法。
本节中,您将通过 pipe() 创建数据管道,进一步理解方法链。
您也已经注意到,这两个数据集相当不同。差异包括:
- Times 数据集有 14 列,而上海排名有 11 列;
- Times 数据集有 2603 行,而上海排名有 4897 行;
- 上海排名缺少大学所属国家这一重要列。如有需要,稍后可以从 Times 数据集中获取该信息。
因此,为了同时使用两个 DataFrame,我们需要仅选择它们的公共列。希望这些公共列的内容相近。
因此,公共列为:
total_score:用于确定排名的分数。如 Kaggle 描述页所述,其中包含区间排名(介于两个数值之间)与并列排名(分数前带有=号)。university_name:大学名称。world_rank:大学的世界排名。year:对应排名的年份。
要拼接两份数据,我们需要先做一些清洗。现在应用完整的管道来清洗数据:
正如您可能注意到的,我在本教程中大量使用 .pipe。如果您来自 R 语言世界,应该对它多少有些熟悉。
这是 Pandas 自 0.16.2 版本引入的便捷方法,允许您将操作串联,从而消除中间 DataFrame 的需要,让代码更易读。
事实上,如果不用这个运算符,您就要把 df.pipe(f).pipe(g).pipe(h) 写成 h(g(f(df)))。一旦嵌套函数变多,这种写法就越来越难以阅读。
想进一步了解,强烈推荐阅读这篇博客文章。
现在两份 DataFrame 都已经清洗好,我们可以将它们拼接为一个 DataFrame。同时,别忘了清理缺失数据:
由于 total_score 列大约有 38% 的数据缺失,最好用 .drop 方法删除该列。
3. 内存优化
让 Pandas 代码更“地道”的第三种方式——在此也等同于“更高性能”——就是优化内存使用。尤其当您通过方法链构建数据管道时,内存会显著影响管道运行速度。下面我们将进一步探讨。
对本小型数据集来说这可能有些“杀鸡用牛刀”,但在实践中,将部分列转换为更合适的类型以优化内存通常是个好习惯。先用 .info 方法查看 ranking_df 占用多少内存。
第一行代码的结果显示数据占用 144 KB。不过,查看 .info 的参数后可以发现,有一个可选参数 memory_usage,默认值为 None。如果将 memory_usage 设为 deep 会怎样?
新的内存占用约为原先的 6 倍(更精确地说是 5.6 倍)。
为什么会这样?
差异源于未启用 “deep” 标志时,Pandas 不会估算 object dtype 的内存消耗。这类(Python)数据类型比其他 numpy 优化的 dtypes 更占空间。因此,建议将 object 类型转换为更合适的类型(例如处理类别数据时转换为 category)。
动手试试!
def memory_change(input_df, column, dtype):
df = input_df.copy()
old = round(df[column].memory_usage(deep=True) / 1024, 2) # In KB
new = round(df[column].astype(dtype).memory_usage(deep=True) / 1024, 2)# In KB
change = round(100 * (old - new) / (old), 2)
report = ("The inital memory footprint for {column} is: {old}KB.\n"
"The casted {column} now takes: {new}KB.\n"
"A change of {change} %.").format(**locals())
return report
print(memory_change(ranking_df,'world_rank', 'int16'))
print(memory_change(ranking_df,'university_name', 'category'))
print(memory_change(ranking_df,'name', 'category'))
既然您知道将 world_rank 转为 int16 等做法能更高效地利用内存,现在就实际应用这些更改吧。您可以使用 astype() 完成转换。最后再次检查当前的内存使用情况:
这样好多了。您还可以将 year 列降为 int32 来进一步优化。
4. GroupBy
现在我们有了一个结构良好(即符合整洁数据规范)且内存占用已优化的数据集,数据分析可以开始了。
您可能感兴趣的问题包括:
- 各年份中,每个排名系统给出的前 5 名大学有哪些?
- 每年的两套排名差异有多大?
让我们先利用前文介绍的地道 Pandas 招式,解决第一个问题!
在开始之前,请注意,“Massachusetts Institute of Technology (MIT)” 与 “Massachusetts Institute of Technology” 是同一所大学的两条不同记录。因此,您需要把前者改成后者。
在这类场景中,本教程前文介绍的 loc 函数就特别好用!
要在各年份、每个排名系统中找出前 5 名(更一般地说前 n 名)大学,伪代码如下:
- 对每个年份(
year列)与每个排名系统(name列): - 选出该年份、该排名系统对应的数据子集;
- 选择前 5 所大学,并存入列表;
- 将结果以字典形式存储,键为 (year, name),值为按排名降序排列的大学列表。
我们来实现它。
既然我们已经有了这个排名字典,来看下两套排名在各年份的差异(以百分比计):如果两个系统选出的前 5 所大学集合相同(即使排名顺序不同),则相似度为 100%。
您也看到了,这样做比较繁琐。有没有更好、更地道的 Pandas 写法?
当然有!首先,筛选出每年每个排名方法的前 5 所大学。使用 head() 查看结果:
现在有了可用的 DataFrame,让我们使用 .groupby 方法,基于上面定义的集合相似度,来比较两套排名方法。
您以更省事的方式,得到了与前面相同的结果。
5. 可视化
完成数据集的初步分析后,也该从可视化角度探索数据了。更地道的 Pandas 代码也意味着要利用 Pandas 与 Matplotlib 的绘图集成。借助它,您可以很快画出优秀的图表!
本节中,您将回到 times_df 与 shanghai_df 数据集,使用 Matplotlib 与 Seaborn 做一些基础可视化。

上图可以立刻看出,alumni 列存在一些 0 值。探索数据时务必留意这些情况:借助数据剖析,您可以发现这些 0 值并进行处理。若想了解更多,请查看 DataCamp 的数据剖析教程。
或者,如果数据集中存在不应当出现的值,您可以简单地将其替换为 NaN,再移除在绘图时会造成困难的 NaN 值。请注意,这只是快速调整数据的方法;您可能仍需要更完整的数据剖析步骤来真正提升数据质量,因为如下图中 num_students 列仍然出现了 0 值:

当然,可视化不必局限于 Matplotlib。还有其他可让您快速完成可视化的库,例如 Seaborn。
Seaborn 主要用于创建美观的统计图表(见其官网描述)。事实上,在 Matplotlib 2.0 之前,要画出漂亮的图并不轻松(尽管可行)。
Pandas 与 Seaborn 的组合也能让您通过可视化快速迭代地探索数据。
我们看几个示例!
看看下面这张图:统计各国家在数据中出现的次数,取前十个,并在绘制条形图时将计数按从高到低排序:

接着,您可以绘制这些国家的平均分:

您的图表也可以做得更复杂。下面的配对图(pairplot)旨在更清晰地展示数据集中变量两两之间的关系:
import numpy as np
np.seterr(invalid='ignore')
sns.pairplot(times_df, hue='country')
plt.show()

同样,您也可以看看 FacetGrid 与 regplot 的组合,它能帮助您了解前五个国家的 total_score 多年间的变化趋势:

最后,在探索数据时,相关性图也很有用。您可以使用 Seaborn 的 heatmap() 函数来完成:

关于可视化的探索可以不断延伸,我们就先到这里吧 :)
下一步
本次就到这里,希望您喜欢这篇关于 Pandas 中级技巧的文章,尤其是如何写出更地道的 Pandas 代码。
如果您喜欢这篇文章并想更深入地了解 Pandas 的性能,强烈推荐以下资料:
- Tom Augspurger 的 Modern Pandas 教程;
- Stephen Simmons 的演讲 Pandas From The Inside;
- 如果您想了解如何用其他 Python 工具实现 groupby 操作,强烈推荐阅读这篇博客文章。