看过来
《pandas 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
pandas 的 apply()
方法是用来调用一个函数(Python method),让此函数对数据对象进行批量处理。Pandas 的很多对象都可以使用 apply()
来调用函数,如 Dataframe、Series、分组对象、各种时间序列等。
以 DataFrame
为例,它的一般语法为:
df.apply(func, axis=0, args=(), **kwds)
参数有:
func
:函数,应用于每列或每行的函数axis
:{0 or ‘index’, 1 or ‘columns’}, 默认为 0, 应用函数的轴方向args:
func 的位置参数**kwds
:要作为关键字参数传递给 func 的其他关键字参数,1.3.0 开始支持返回:
函数示例图如下:
对源数据(df/ser 等)应用函数(fun),在应用时可以指定函数处理的方向,如果 axis 为 0 时(默认)则按列应用,为 1 时按行应用,当然源数据只有一列时(series)只能按列处理。不管是行还是列,每次处理的都是一个序列(可以认为是 Series)。
关于函数(fun)的要求,其第一个参数必须是要处理的 Series,参数还可以定义位置参数和关键字参数,对应地用可在函数名 fun 后传入。
定义函数 fun 的返回值时,必须返回一个和传入 Series 相同长度的序列(不一定必须是 Series 类型),抑或是一个标量,总之可以广播到源数据的行或者列上。
函数 fun 参数,只需要传入这个函数的函数名就可以了,第一个参数接收一个 Series,还可以定义位置参数和关键字参数,如以下定义一个对:
def mymax(x):
return x.max()
df.apply(mymax) # 应用函数
如果,给定位置参数和关键字参数的话:
def myfun(x, a, max=None):
return x + a - max
df.apply(myfun, 10, max=1) # 应用函数
总结一下,fun 可传入的函数形式有以下几种(DataFrame 和 Series 均适用):
ser.apply(fun) # 自定义
ser.apply(max) # python 内置函数
ser.apply(lambda x: x*2) # lambda
ser.apply(np.mean) # numpy 等其他库的函数 ufunc
ser.apply(pd.Series.first_valid_index) # Pandas 自己的函数
ser.apply('count') # Pandas 自己的函数
ser.apply('shape') # Pandas 自己的属性
ser.apply('tan') # numpy 的 ufunc 名
# 多个函数
ser.apply([sum, 'count']) # 相当于 .aggregate, 即.agg
ser.apply(np.array(["sum", "mean"]))
ser.apply({'Q1':sum, 'Q2':'count'}) # 同上
此外,apply 还可以传入多个函数,对数据进行聚合,实现 agg 的效果:
df = pd.read_csv('https://gairuo.com/file/data/team.csv')
df.apply(['max', 'min'])
'''
name team Q1 Q2 Q3 Q4
max Zachary E 98 99 99 99
min Aaron A 1 1 1 2
'''
df.apply({'Q1':'max', 'Q2': 'min'})
'''
Q1 98
Q2 1
dtype: int64
'''
df.apply({'Q1':'mean', 'Q2': ['max', 'min']})
'''
Q1 Q2
mean 49.2 NaN
max NaN 99.0
min NaN 1.0
'''
接下来介绍常用的数据类型使用 apply()
的方法:
DataFrame 有两种方法可以应用函数。
apply 在 DataFrame 上的完整语法是:
DataFrame.apply(func, axis=0, raw=False,
result_type=None, args=(), **kwds)
参数有:
func
:函数axis=0
:func 执行的轴方向raw=False
:bool, 默认 False,确定行或列是作为 Series 还是 ndarray 对象传递:result_type=None
:{‘expand’, ‘reduce’, ‘broadcast’, None}, 默认 None,仅在 axis=1 (columns) 时都起作用:args=()
:func 位置参数**kwds
:func 关键字参数以下是案例:
import numpy as np
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3],
'B': [4, 5, 6]},
index=['a', 'b', 'c'])
df
# A B
# a 1 4
# b 2 5
# c 3 6
# 对各列应用函数
df.apply(lambda x: np.sum(x))
A 6
B 15
dtype: int64
# 对各行应用函数
df.apply(lambda x: np.sum(x), axis=1)
a 5
b 7
c 9
dtype: int64
# 对每个值应用函数
df.applymap(lambda x: x * 2)
# A B
# a 2 8
# b 4 10
# c 6 12
# apply 将各列或各行作为 Series 传递
df.apply(type)
# A <class 'pandas.core.series.Series'>
# B <class 'pandas.core.series.Series'>
# dtype: object
# applymap 中应用的函数将值本身作为参数传递给函数
df.applymap(type)
# A B
# a <type 'numpy.int64'> <type 'numpy.int64'>
# b <type 'numpy.int64'> <type 'numpy.int64'>
# c <type 'numpy.int64'> <type 'numpy.int64'>
再看一些官方的案例:
df = pd.DataFrame([[4, 9]] * 3, columns=['A', 'B'])
df
''' A B
0 4 9
1 4 9
2 4 9
'''
# 使用numpy通用函数 (如 np.sqrt(df)):
df.apply(np.sqrt)
'''
A B
0 2.0 3.0
1 2.0 3.0
2 2.0 3.0
'''
# 使用聚合功能
df.apply(np.sum, axis=0)
'''
A 12
B 27
dtype: int64
'''
df.apply(np.sum, axis=1)
'''
0 13
1 13
2 13
dtype: int64
'''
# 在每行上返回类似列表的内容
df.apply(lambda x: [1, 2], axis=1)
'''
0 [1, 2]
1 [1, 2]
2 [1, 2]
dtype: object
'''
# result_type='expand' 将类似列表的结果扩展到数据的列
df.apply(lambda x: [1, 2], axis=1, result_type='expand')
'''
0 1
0 1 2
1 1 2
2 1 2
'''
# 在函数中返回一个序列,生成的列名将是序列索引。
df.apply(lambda x: pd.Series([1, 2], index=['foo', 'bar']), axis=1)
'''
foo bar
0 1 2
1 1 2
2 1 2
'''
# result_type='broadcast' 将确保函数返回相同的形状结果
# 无论是 list-like 还是 scalar,并沿轴进行广播
# 生成的列名将是原始列名。
df.apply(lambda x: [1, 2], axis=1, result_type='broadcast')
'''
A B
0 1 2
1 1 2
2 1 2
'''
apply 对 Series 值调用函数。可以是 ufunc(应用于整个系列的 NumPy 函数)或只对单个值起作用的 Python 函数。Series 有两种方法可以将函数应用于的每个值,行为几乎相同,apply 在应用函数时使用更清晰。
Series.apply(func, convert_dtype=True,
args=(), **kwds)
参数:
**kwds
:传递给 func 的其他关键字参数返回:
简单示例:
s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s.apply(lambda x: x * 2)
# a 2
# b 4
# c 6
# dtype: int64
# apply 的参数中,Series 的值本身
s.apply(type)
# a <type 'numpy.int64'>
# b <type 'numpy.int64'>
# c <type 'numpy.int64'>
# dtype: object
# 函数返回多个值的 tuple
s.apply(lambda x: (x, x * 2))
# a (1, 2)
# b (2, 4)
# c (3, 6)
# dtype: object
# 如果想将结果设为 DataFrame,则返回值作为 Series
s.apply(lambda x: pd.Series([x, x * 2], index=['col1', 'col2']))
# col1 col2
# a 1 2
# b 2 4
# c 3 6
# map 的行为与 apply 几乎相同(在map中不能得到 DataFrame)
s.map(lambda x: x * 2)
# a 2
# b 4
# c 6
# dtype: int64
s.map(type)
# a <type 'numpy.int64'>
# b <type 'numpy.int64'>
# c <type 'numpy.int64'>
# dtype: object
# map 除了函数以外,还可以取 mapping 用的 dict 和 Series 作为参数
s.map(pd.Series(['x', 'y', 'z'], index=[1, 2, 3]))
# a x
# b y
# c z
# dtype: object
下边是一些官网案例:
# 为每个城市创建一个具有典型夏季温度的序列
s = pd.Series([20, 21, 12],
index=['London', 'New York', 'Helsinki'])
s
'''
London 20
New York 21
Helsinki 12
dtype: int64
'''
# 定义函数并将其作为参数传递给 apply,求值平方化。
def square(x):
return x ** 2
s.apply(square)
'''
London 400
New York 441
Helsinki 144
dtype: int64
'''
# 通过将匿名函数作为参数传递给 apply
s.apply(lambda x: x ** 2)
'''
London 400
New York 441
Helsinki 144
dtype: int64
'''
# 定义一个需要附加位置参数的自定义函数
# 并使用args关键字传递这些附加参数。
def subtract_custom_value(x, custom_value):
return x - custom_value
s.apply(subtract_custom_value, args=(5,))
'''
London 15
New York 16
Helsinki 7
dtype: int64
'''
# 定义一个接受关键字参数并将这些参数传递
# 给 apply 的自定义函数。
def add_custom_values(x, **kwargs):
for month in kwargs:
x += kwargs[month]
return x
s.apply(add_custom_values, june=30, july=20, august=25)
'''
London 95
New York 96
Helsinki 87
dtype: int64
'''
# 使用Numpy库中的函数
s.apply(np.log)
'''
London 2.995732
New York 3.044522
Helsinki 2.484907
dtype: float64
'''
GroupBy.apply 可以将该功能应用到每个组中,即按组应用函数 func 并将结果合并在一起。无论是 Series 还是 DataFrame 分组对象都支持。
语法为:
GroupBy.apply(func, *args, include_groups=True, **kwargs)
参数:
返回:
传递给 apply 的函数必须将 dataframe 作为其第一个参数并返回 dataframe、Series或 scalar。apply 将负责将结果重新组合到一个数据帧或序列中。因此,apply 是一种高度灵活的分组方法。
这儿的函数调用形式和上文中 DataFrame apply 传入的一样,不同的时字符串形式时,它是指的 DataFrameGroupBy 对象的方法 (https://pandas.pydata.org/docs/reference/groupby.html)。
虽然 apply 是一种非常灵活的方法,但它的缺点是使用它可能比使用 agg 或transform 等更具体的方法慢很多。Pandas 提供了一系列的方法,比使用 apply 用于特定目的要快得多,所以在使用 apply 之前要尝试使用 agg 等其他方法。
df = pd.DataFrame({'group': ['g1', 'g2', 'g1', 'g2'],
'A': [1, 2, 3, 4], 'B': [5, 6, 7, 8]},
columns=['group', 'A', 'B'])
df
# group A B
# 0 g1 1 5
# 1 g2 2 6
# 2 g1 3 7
# 3 g2 4 8
grouped = df.groupby('group')
grouped.apply(np.mean)
# A B
# group
# g1 2 6
# g2 3 7
注意,由于处理优化,目标组数 == 应用函数的次数,但它不是执行次数。函数中的破坏性处理可能会产生意想不到的后果。
# 应用函数
def f(x):
print('called')
return x
# 组数为 2
grouped.ngroups
# 2
# f 执行3次
grouped.apply(f)
# called
# called
# called
Pandas 的样式对象 Styler 也支持 apply,按列、按行或按表应用函数,更新 HTML 的显示样式效果。
df.style.apply(
func: 'Callable[..., Styler]',
axis: 'Axis | None' = 0,
subset: 'Subset | None' = None,
**kwargs,
) -> 'Styler'
参数:
attribute: value; attribute2: value2; …
或者,如果不向该元素应用任何内容,则为空字符串或 None。**kwargs
:dict,传给 func 的参数这与 DataFrame.apply 类似,只是 axis=None 将函数一次应用于整个数据帧,而不是按列或按行应用。
返回:
func 的输出形状应与输入匹配,即如果 x 是输入行、列或表(取决于轴),则 func(x).shape==x.shape 应为真。
它与 DataFrame.apply 类似,只是 axis=None 一次将函数应用于整个数据帧,而不是按列或按行。
def highlight_max(x):
return ['background-color: yellow' if v == x.max() else ''
for v in x]
df = pd.DataFrame(np.random.randn(5, 2))
df.style.apply(highlight_max)
apply、map、applymap 对数据类型的支持情况如下:
方法 | DataFrame | Series |
---|---|---|
apply | 支持 | 支持 |
map | - | 支持 |
applymap | 支持 | - |
可以看到,map、applymap 仅支持一种数据类型,apply 全支持。相关方法对比示意图如下:
可以调用 apply()
的对象还有以下对象,除了上边介绍的三种外,其他的逻辑类似:
更新时间:2024-01-29 12:54:17 标签:pandas apply 函数