学习Pandas已经有一年多,使用Pandas进行数据分析也差不多有一年了。在这个过程中,我不断总结和整理一些实用的方法。今天,我想分享三个我在数据处理时频繁使用的函数:apply、Map和applyMap,其中apply是主要角色,而Map和applyMap则是附加功能。

数据处理主要涉及多种清洗操作,除了简单的缺失值和重复值处理外,更复杂的任务包括异常值处理和各种数据变换,如类型转换和简单的数值计算等。在这些过程中,如何既能高效处理数据,又能保持优雅,Pandas中的这几个函数提供了理想的解决方案。
为了展示这三个函数在数据处理中的应用,我将以经典的泰坦尼克号数据集为例。需要下载该数据集和示例源码的朋友可以在后台回复关键字apply获取下载方式。
01 apply的方法论
在深入学习apply函数的具体应用之前,有必要先了解apply函数的基本理念。apply的英文意思是“应用”,在多种编程语言中都能找到类似的用法。例如,在我最近学习的Scala语言中,apply被用作伴生对象中自动创建对象的默认实现。由此可见,apply函数的重要性。在Pandas中,apply的核心功能可以概括为:
apply:我本身不处理数据,只是数据的搬运工。
换句话说,apply自身并不具备数据处理能力,但可以作为其他数据处理方法的调度器。那么,调度什么?又为谁调度呢?这两个问题是理解apply的关键:
调度什么?apply接收一个数据处理函数作为主要参数,并将其应用于相应的数据上。因此,调度的内容取决于接收到的数据处理函数;为谁调度?也就是apply所接收的函数作用于哪个对象?数据处理的粒度可以是单个元素(标量),一行或一列(Series),甚至是整个DataFrame。
以上的描述可能显得有些抽象,那么让我们直接看代码示例!
02 apply基本方法
理解apply的核心在于明确两个方面:调度函数和作用对象。调度函数可以是Python内置函数,也可以是自定义函数,只要适用于特定的作用对象(标量、Series或DataFrame)即可。而作用对象取决于调用apply的对象类型,具体而言:
当一个Series对象调用apply时,数据处理函数作用于该Series的每个元素,实现从一个Series转换到另一个Series;而当一个DataFrame对象调用apply时,数据处理函数作用于该DataFrame的每一行或每一列,实现从一个DataFrame转换到一个Series;如果DataFrame经过groupby分组后调用apply,则数据处理函数作用于groupby后的每个子DataFrame上,作用对象依然是DataFrame。
以泰坦尼克号数据集为例,原始数据集如下:

1. 应用到Series的每个元素:将性别sex列转化为0和1数值,female对应0,male对应1。使用apply函数实现这一功能非常简单:

这里,apply接收一个lambda匿名函数,通过简单的if-else逻辑实现数据的映射,该功能非常简单,不需要其他参数。
2. 下面是一个稍微复杂的例子,注意到年龄age列的当前数据类型是小数,需要将其转换为整数,同时处理年龄限制。我们需要定义一个函数来处理这些逻辑:
def get_age(age, Max_age, Min_age): age = int(age) if age > Max_age: age = Max_age if age < Min_age: age = Min_age return age
然后,直接对age列调用该函数,其中age参数由调用该函数的Series进行向量化填充,而其他两个参数则通过args传入。具体实现如下:

3. 应用到DataFrame的每个Series
DataFrame是Pandas中的核心数据结构,其每一行和每一列都属于Series类型。调用apply时需要指定axis参数,axis=0表示按列处理,axis=1表示按行处理,默认为axis=0。这里再举两个小例子:
1)取所有数值列的数据最大值,尽管可以直接调用max函数,但为了演示apply的使用,我们仍然这样尝试:

上述apply函数完成了对四个数值列求最大值,默认按列处理(axis=0)。
2)按行处理的例子:根据性别和年龄,区分4类人群:女孩、成年女子、男孩和成年男子。首先定义人群划分的函数:
def cat_person(sR): if sR[‘sex_num’] == 0: if sR[‘age_num’] < 18: return '女孩' else: return '成年女子' else: if sR['age_num'] < 18: return '男孩' else: return '成年男子'
基于此,用apply简单调用。设置axis=1,表示按行处理,每行都是一个包含age和sex信息的Series,通过cat_person函数进行判断,实现人群的划分:

4. 应用到DataFrame groupby后的每个分组DataFrame
个人认为这是一个非常有效的用法,结合groupby和apply,可以实现个性化的聚合统计功能。比如,我们希望统计不同舱位等级的“生存年龄比”,定义为各舱位等级内生存人员的年龄之和与所有人员年龄之和的比值。实现这一数据统计,首先需要以舱位等级进行分组,然后对每个分组进行统计,示例代码如下:

apply接收一个lambda匿名函数,该函数接收一个DataFrame作为参数,并提取survived列和age_num列进行计算。最终得到每个舱位等级的统计指标,返回类型为Series对象。
以上可以梳理apply函数的执行流程:首先确认调用apply的数据结构类型,是Series还是DataFrame;如果是DataFrame,还需确定是直接调用apply还是经过groupby分组后调用。前者对应apply处理一行或一列,后者则处理每个分组的子DataFrame,最后根据作用对象类型设计相应的接收函数,完成个性化的数据处理。
03 apply的两个兄弟
前面介绍了apply的三种应用场景,功能已经非常强大。除了apply,Pandas还提供了两个类似的函数:Map和applyMap。相较于功能强大的apply,二者的功能相对局限。具体来说,它们的功能如下:
1. Map。在Python中提到Map,通常联想到两种情况:一种是字典,另一种是内置函数Map,用于数据映射。在Pandas中,这两者都有体现:对Series的每个元素进行字典映射或函数变换。以替换性别列为例,应用Map函数的实现方式为:

虽然Map提供了两种数据转换方式,但仅适用于Series,无法应用于DataFrame。同时,Map在处理索引列时的应用是其独特之处,apply无法实现。

2. applyMap。顾名思义,applyMap是apply和Map的结合体,综合了apply可以应用于DataFrame和Map仅能应用于元素级的特点。因此,applyMap用于将接收函数应用于DataFrame的每个元素,实现相应的变换。
从某种角度来看,这种变换的前提是DataFrame各列元素的数据类型相同,且具有相近的业务含义,否则同一变换可能无法保证预期效果。
假设我们需要获取DataFrame中各个元素的数据类型,则应用applyMap的实现如下:

04 小结
apply、Map和applyMap常用于实现Pandas中的数据变换,通过接收一个函数实现特定的数据处理任务。
