Pandas知识点-详解行列级批处理函数apply

Python 碎片

共 9893字,需浏览 20分钟

 · 2022-07-06

在Pandas中,DataFrame和Series等对象需要执行批量处理操作时,可以借用apply()函数来实现。


apply()的核心功能是实现“批量”调度处理,至于批量做什么,由用户传入的函数决定(自定义或现成的函数)。函数传递给apply(),apply()会帮用户在DataFrame和Series等对象中(按行或按列)批量执行传入的函数。


先看一个例子:


# coding=utf-8
import pandas as pd

df = pd.DataFrame({'Col-1': [135], 'Col-2': [246], 'Col-3': [987], 'Col-4': [369]},
                  index=['A''B''C'])
print(df)
df_new = df.apply(lambda x: x-1)
print('-' * 30'\n', df_new, sep='')
   Col-1  Col-2  Col-3  Col-4
A 1 2 9 3
B 3 4 8 6
C 5 6 7 9
------------------------------
Col-1 Col-2 Col-3 Col-4
A 0 1 8 2
B 2 3 7 5
C 4 5 6 8


从这个例子可以看出,apply的使用非常简便且优雅,一行代码就对DataFrame中的所有数据都执行了一遍传入的匿名函数。


apply用法和参数介绍



apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds):


  • func: 应用于每一列或每一行的函数,这个函数可以是Python内置函数、Pandas或其他库中的函数、自定义函数、匿名函数。

  • axis: 设置批处理函数按列还是按行应用,0或index表示按列应用函数,1或columns表示按行应用函数,默认值为0。

  • raw: 设置将列/行作为Series对象传递给函数,还是作为ndarray对象传递给函数。raw是bool类型,默认为False。

False: 将列/行作为Series对象传递给函数。
True: 将列/行作为ndarray对象传递给函数。apply中的func函数将接收ndarray对象,如果应用numpy中的函数,这样可以提升性能。


  • result_type: 当axis=1时,设置返回结果的类型和样式,支持{'expand', 'reduce', 'broadcast', None}四种类型,默认为None。

expand: 列表式的结果将被转化为列。
reduce: 如果可能的话,返回一个Series,而不是返回列表式的结果。这与expand相反。
broadcast: 结果将被广播成DataFrame的原始形状,DataFrame的原始索引和列将被保留。
None: 结果取决于应用函数的返回值,列表式的结果将以Series形式返回,如果应用函数返回Series将会扩展到列。

  • args: 传给应用函数func的位置参数,args接收的数据类型为元组,如果只有一个位置参数要注意加逗号。


  • **kwds: 如果func中有关键字参数,可以传给**kwds。


raw和result_type通常不需要自己设置,保持默认即可。


下面依次介绍apply()函数的各种用法。


传入不同类型的函数



import numpy as np

df = pd.DataFrame({'Col-1': [135], 'Col-2': [246], 'Col-3': [987], 'Col-4': [369]},
                  index=['A''B''C'])
print(df)
df1 = df.apply(max)  # python内置函数
print('-' * 30'\n', df1, sep='')
df2 = df.apply(np.mean)  # numpy中的函数
print('-' * 30'\n', df2, sep='')
df3 = df.apply(pd.DataFrame.min)  # pandas中的方法
print('-' * 30'\n', df3, sep='')
   Col-1  Col-2  Col-3  Col-4
A 1 2 9 3
B 3 4 8 6
C 5 6 7 9
------------------------------
Col-1 5
Col-2 6
Col-3 9
Col-4 9
dtype: int64
------------------------------
Col-1 3.0
Col-2 4.0
Col-3 8.0
Col-4 6.0
dtype: float64
------------------------------
Col-1 1
Col-2 2
Col-3 7
Col-4 3
dtype: int64
def make_ok(s):
    return pd.Series(['{}ok'.format(d) for d in s])


df4 = df.apply(make_ok)  # 自定义函数
print('-' * 30'\n', df4, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1ok 2ok 9ok 3ok
1 3ok 4ok 8ok 6ok
2 5ok 6ok 7ok 9ok


设置按行还是按列



def make_ok(s):
    if isinstance(s, pd.Series):
        if s.name in df.columns:
            return pd.Series(['{}ok-列'.format(d) for d in s])
        else:
            return pd.Series(['{}ok-行'.format(d) for d in s])
    else:
        return '{}ok'.format(s)


df5 = df.apply(make_ok, axis=0)  # 按列处理
print('-' * 30'\n', df5, sep='')
df6 = df.apply(make_ok, axis=1)  # 按行处理
print('-' * 30'\n', df6, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1ok-列 2ok-列 9ok-列 3ok-列
1 3ok-列 4ok-列 8ok-列 6ok-列
2 5ok-列 6ok-列 7ok-列 9ok-列
------------------------------
0 1 2 3
A 1ok-行 2ok-行 9ok-行 3ok-行
B 3ok-行 4ok-行 8ok-行 6ok-行
C 5ok-行 6ok-行 7ok-行 9ok-行


这里推演一下按列和按行的过程,当axis参数为0或index时,按列从DataFrame中取数据,如上面的例子先取到第一列Col-1:[1, 3, 5],将第一列传给函数func,然后取第二列Col-2:[2, 4, 6]...以此类推。当axis参数为1或columns时,按行从DataFrame中取数据,如上面的例子先取到第一行A:[1, 2, 9, 3],将第一行传递给函数func执行后取第二行,以此类推。


同时,当按列应用函数时,DataFrame的index变成了默认索引(0开始的自然数),当按行应用函数时,DataFrame的columns变成了默认索引,如果要保持与原DataFrame一样,则需要重新设置。


函数func的参数



def yes_or_no(s, answer):
    if answer != 'yes' and answer != 'no':
        answer = 'yes'
    if isinstance(s, pd.Series):
        return pd.Series(['{}-{}'.format(d, answer) for d in s])
    else:
        return '{}-{}'.format(s, answer)


df7 = df.apply(yes_or_no, args=('yes',))
df7.index = ['A''B''C']
print('-' * 30'\n', df7, sep='')
df8 = df.apply(yes_or_no, args=('no',))
print('-' * 30'\n', df8, sep='')
df9 = df.apply(yes_or_no, args=(0,))
print('-' * 30'\n', df9, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
A 1-yes 2-yes 9-yes 3-yes
B 3-yes 4-yes 8-yes 6-yes
C 5-yes 6-yes 7-yes 9-yes
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1-no 2-no 9-no 3-no
1 3-no 4-no 8-no 6-no
2 5-no 6-no 7-no 9-no
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1-yes 2-yes 9-yes 3-yes
1 3-yes 4-yes 8-yes 6-yes
2 5-yes 6-yes 7-yes 9-yes


在apply()中,func函数的第一个参数默认会传入Series对象,这就是前面说的“将列/行作为Series对象传递给函数”,因此函数func至少要有一个参数,这个参数相当于类方法中的self,不需要在args中传值。如果func没有参数,则不能在apply中使用。


如果func的参数多于一个,则多出来的参数通过args传递,args接收一个元组,args里只有一个值时,需要加上逗号。如果func中有关键字参数,可以传到apply中**kwds的位置。


传入多个函数进行聚合



df10 = df.apply([np.max, np.min])
print('-' * 40'\n', df10, sep='')
df11 = df.apply({'Col-1': np.mean, 'Col-2': np.min})
print('-' * 40'\n', df11, sep='')
df12 = df.apply({'Col-1': [np.mean, np.median], 'Col-2': [np.min, np.mean]})
print('-' * 40'\n', df12, sep='')
----------------------------------------
Col-1 Col-2 Col-3 Col-4
amax 5 6 9 9
amin 1 2 7 3
----------------------------------------
Col-1 3.0
Col-2 2.0
dtype: float64
----------------------------------------
Col-1 Col-2
mean 3.0 4.0
median 3.0 NaN
amin NaN 2.0


当在apply中传入多个函数时,返回的结果被聚合成一个新的DataFrame或Series,作用类似于pandas中的聚合函数DataFrame.agg()。[后续文章会介绍agg()]


通过函数名字符串调用函数



df13 = df.apply('mean', axis=1)
print('-' * 30'\n', df13, sep='')
df14 = df.apply(['mean''min'], axis=1)
print('-' * 30'\n', df14, sep='')
------------------------------
A 3.75
B 5.25
C 6.75
dtype: float64
------------------------------
mean min
A 3.75 1.0
B 5.25 3.0
C 6.75 5.0


apply()支持函数名用字符串传给func,调用函数。


修改DataFrame本身



df15 = df.copy()
# 读取df的一列,将处理结果添加到原df中,增加一列
df15['Col-x'] = df15['Col-1'].apply(make_ok)
print('-' * 40'\n', df15, sep='')
# 读取df的一行,将处理结果添加到原df中,增加一行
df15.loc['Z'] = df15.loc['A'].apply(yes_or_no, args=('yes',))
print('-' * 40'\n', df15, sep='')
----------------------------------------
Col-1 Col-2 Col-3 Col-4 Col-x
A 1 2 9 3 1ok
B 3 4 8 6 3ok
C 5 6 7 9 5ok
----------------------------------------
Col-1 Col-2 Col-3 Col-4 Col-x
A 1 2 9 3 1ok
B 3 4 8 6 3ok
C 5 6 7 9 5ok
Z 1-yes 2-yes 9-yes 3-yes 1ok-yes


DataFrame增加一列或一行可以直接赋值,修改一个DataFrame后将结果赋值给本身,这样相当于修改了原始DataFrame。


Series使用apply



s0 = df['Col-2'].apply(make_ok)
print('-' * 20'\n', s0, sep='')
s = pd.Series(range(5), index=[alpha for alpha in 'abcde'])
print('-' * 20'\n', s, sep='')
s1 = s.apply(make_ok)
print('-' * 20'\n', s1, sep='')
--------------------
A 2ok
B 4ok
C 6ok
Name: Col-2, dtype: object
--------------------
a 0
b 1
c 2
d 3
e 4
dtype: int64
--------------------
a 0ok
b 1ok
c 2ok
d 3ok
e 4ok
dtype: object


DataFrame中的一行或一列都是一个Series,所以用DataFrame的列或行调用apply()就相当于Series调用apply()。


在DataFrame中,apply()将行/列作为Series传给func函数,在Series中,apply()将Series中的每一个值传给func函数。对于这两种情况,func接受的参数类型完全不一样,因此使用时一定要注意func函数的参数类型,否则可能不适用。


s2 = s.apply(np.mean)
print('-' * 20'\n', s2, sep='')
s3 = np.mean(s)
print('-' * 20'\n', s3, sep='')
--------------------
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
--------------------
2.0

将Series中的每一个值传给apply()中的函数func,返回的结果仍然是一个Series。将Series作为一个整体传给apply()中的函数func,有些函数返回的结果仍然是Series,如上面的自定义函数,有些函数返回的结果不再是Series,而是一个其他类型的数据,如numpy中的统计运算函数(mean、max、min)等。


因此DataFrame经过apply()批处理后,可能会变成一个Series,这是由apply()中的函数func的返回值决定的,与apply()无关。


以上就是pandas中的apply()函数的用法介绍和分析,希望对你有帮助,想要深入的了解apply()函数的底层原理,可以打个断点,在“Debugger”模式中看运行过程,也可以看源码。如果你有其他的想法或疑问,欢迎加我好友一起交流讨论。

参考文档: 

[1] pandas中文网:https://www.pypandas.cn/docs/

浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报