apply()函数常用于对DataFrame进行行迭代或者列迭代,它的axis的含义与统计聚合函数的axis的含义一致。apply()的参数往往是一个以序列为输入的函数,例如,对于mean(),使用apply()可以写出:
In [79]: df_demo = df[['Height', 'Weight']]
def my_mean(x):
res = x.mean()
return res
df_demo.apply(my_mean)
Out[79]: Height 163.218033
Weight 55.015873
dtype: float64
对于简单的函数,可以利用Lambda表达式使得书写简洁,如下代码中的x就指代被调用的df_demo表中逐个输入的序列:
In [80]: df_demo.apply(lambda x:x.mean())
Out[80]: Height 163.218033
Weight 55.015873
type: float64
若指定axis=1,那么每次传入函数的就是由行元素组成的Series,其结果与2.3.2节例子中的逐行均值的结果一致。
In [81]: df_demo.apply(lambda x:x.mean(), axis=1).head()
Out[81]: 0 102.45
1 118.25
2 138.95
3 41.00
4 124.00
dtype: float64
这里再举一个例子:mad()返回的是一个序列中元素偏离该序列均值的绝对值大小的均值,例如序列[1,3,7,10]中,均值为5.25,每一个元素偏离的绝对值为[4.25,2.25,1.75,4.75],这个偏离序列的均值为3.25。现在利用apply()、mad()计算身高和体重的指标:
In [82]: df_demo.apply(lambda x:(x-x.mean()).abs().mean())
Out[82]: Height 6.707229
Weight 10.391870
dtype: float64
In [83]: df_demo.mad()
Out[83]: Height 6.707229
Weight 10.391870
dtype: float64
既然apply()如此强大,是不是就意味着其他的函数都没有用武之地,它们都可以用自定义函数来替换呢?答案是否定的,apply()的自由性是牺牲性能换来的。当apply()中迭代的行或列的数量较多时,运算时间明显变长。仍然以计算身高和体重的均值这一过程为例,我们来分别比较在200行数据样本下使用apply()和使用内置函数的性能差异:
In [84]: %timeit -n 100 -r 7 df_demo.apply(lambda x:x.mean(), axis=1)
Out[84]: 16.6ms ± 347 µs per loop (mean ± std.dev.of 7 runs, 100 loops each)
In [85]: %timeit df_demo.mean(1)
Out[85]: 182µs ± 10.7 µs per loop (mean ± std.dev.of 7 runs, 100 loops each)
注解
在Jupyter中可以使用%timeit来估计一行代码所运行的时间,其中参数-r表示运行的轮数(runs),参数-n表示每一轮该代码运行的次数(loops)。
从结果可以看到,在这个例子中它们竟存在高达约100倍的时长差距,这样的结果显然是我们不愿看见的。从另一方面说,我们应该在何时使用apply()?笔者认为只有当不存在内置函数能够解决当下的计算问题且apply()的迭代次数较少时,我们才考虑使用apply()来辅助完成计算任务。总之,在apply()的“诱惑”前,我们应当保持谨慎。