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

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

先看一个例子:

# coding=utf-8

import pandas as pd

df = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},

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': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},

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()无关。

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: