Pandas 高效数据处理:apply、向量化与分组
Pandas 高效数据处理:apply、向量化与分组进阶实战(附代码示例)
- 适用人群: 想系统掌握 Pandas 自定义计算、性能优化、分组聚合与转换的同学
- 核心要点: apply 的行列处理、真正向量化 vs 伪向量化、GroupBy 的聚合/转换/apply 全景案例
一、为什么需要这些技术
- apply: 用自定义函数批量处理 Series/DataFrame/GroupBy,替代显式 for 循环。
- 向量化: 利用底层 C/NumPy 实现的批处理(极快);
np.vectorize
提供向量化接口但本质仍是循环(伪向量化)。 - 分组进阶: 统计、对齐转换(transform 类似 SQL 窗口函数)、组级过滤、自定义 apply。
二、apply 入门与典型用法
1)对 Series:逐元素
“”"
apply函数是pandas中自由度(自定义)最高的函数之一,用来对series、dataframe或者分组对象应用自定义函数,他的核心行为是:
1:对series,逐个元素进行处理(输入单个值,输出单个值)
“”"
import pandas as pdprint("----------对series中使用apply--------------")
def my_fun1(x):return x ** 2s = pd.Series([1,2,3,4])
r1 = s.apply(my_fun1)
- 也可传参:
def my_fun2(x, e):return x ** er2 = s.apply(my_fun2, e=3)
2)对 DataFrame:按行或按列传入 Series
print("----------对dataframe中使用apply--------------")
df = pd.DataFrame({'a':[10,20,30], 'b': [20,30,40]})
def my_fun3(x):print(f"x的内容:\n{x}")print(f"x的类型:{type(x)}")df.apply(my_fun3, axis=1) # axis=1 按“行”传入 Series
- 误区提示:函数签名需与传入对象匹配(行/列 Series),否则会报参不匹配错误。
df = pd.read_csv("data/titanic_train.csv")
def count_missing(vec):return pd.isnull(vec).sum()
def prop_missing(vec):return pd.isnull(vec).sum() / vec.size
def prop_complete(vec):return 1-prop_missing(vec)print(df.apply(count_missing)) # 按列
print(df.apply(prop_missing))
print(df.apply(prop_complete))
print(df.apply(count_missing, axis=1)) # 按行
要点:
- axis=0(默认)按列;axis=1 按行。
- 行列粒度输出分别用于字段健康度与行级质量检查。
三、真正向量化 vs 伪向量化(np.vectorize)
教程总结要点如下:
“”"
真正的向量化:底层C/NumPy实现,性能极高,语法简洁,广泛覆盖数学/统计/字符串/日期等。
np.vectorize:伪向量化,内部仍是python循环,接口友好但性能远逊真·向量化。
apply:最灵活,但性能最低。
性能:向量化函数>伪向量化函数>apply(自定义函数)
“”"
- 真·向量化示例(列运算、标量运算、NumPy 通用函数):- `df['C'] = df['A'] + df['B']`- `df['D'] = df['A'] * 10`- `df['E'] = np.log(df['A'])`- `np.vectorize` 适用场景:- 自定义标量函数、多分支复杂逻辑,或多参数且需固定部分参数时。- 注意:它不是性能优化的“银弹”,只是让写法像向量化。实践建议:
- 能用真·向量化就不要用 apply/np.vectorize。
- 需要强自定义且无等价向量化方案时,再考虑 `np.vectorize` 或 `apply`。---## 四、GroupBy 分组进阶:聚合、转换、应用### 1)概念全景
"""
分组:groupby();按列/函数/索引分组
聚合:agg()/aggregate()
转换:transform() 返回与原始数据同大小(组内对齐)
过滤:filter() 基于组属性筛组
应用:apply() 对组DataFrame做任意处理
"""
2)使用 Gapminder 数据做统计实践
df = pd.read_csv("data/gapminder.tsv", sep="\t")
df.groupby("year")
df.groupby(["year", "continent"])
print(df.groupby("year").lifeExp.mean())
print(df.groupby("year")['lifeExp'].mean())
- 常用描述统计:
print(df.groupby("continent").lifeExp.count())
print(df.groupby("continent").lifeExp.describe())
print(df.groupby("continent").lifeExp.agg(lambda x: np.mean(x)))
- 结合全局值做组内“差值”分析(传参 + 自定义聚合):
global_lifeExp_mean = df.lifeExp.mean()
def my_mean_diff(col, lifeExp_mean):return col.mean() - lifeExp_mean
print(df.groupby("continent").lifeExp.agg(my_mean_diff, lifeExp_mean=global_lifeExp_mean))
- 一次性对不同列做不同聚合并改列名:
print(df.groupby("year").agg({'lifeExp':'mean','pop':'max','gdpPercap':'min'
}).rename(columns={'lifeExp':'平均寿命', 'pop':'最大人口', 'gdpPercap':'人均最小GDP'}).reset_index())
3)转换 transform:组内对齐输出(窗口函数思维)
计算 Z-Score(标准分数)并按年分组,结果与原数据等长、可直接对齐赋值:
def my_zscore(x):return (x - x.mean()) / x.std()
print(df.groupby('year').lifeExp.transform(my_zscore))
4)小案例:按性别组均值填充消费总额缺失值
tops_10_df = pd.read_csv("data/tips.csv").sample(n=10, random_state=20)
tops_10_df.loc[np.random.permutation(tops_10_df.index)[:4], 'total_bill'] = np.NaNdef my_fillna(x):return x.fillna(x.mean())r1 = tops_10_df.groupby('sex').total_bill.transform(my_fillna)
- 思路:按
sex
分组 → 对每组total_bill
用组均值填充 →transform
保持原长度,直接对齐回写到原列,避免 merge。
五、选型建议与最佳实践
- 优先顺序(速度):真正向量化 >
np.vectorize
(伪) >apply
- apply 何时用:
- 需要高度灵活的自定义逻辑,且难以映射为向量化表达式
- 分组后的整组 DataFrame 级复杂处理
- transform 常用场景:
- 需要与原表逐行对齐输出(标准化、组均值填充、组排名、比值偏差等)
- 性能注意:
- 尽量减少 Python 层循环;多利用 Pandas/NumPy 内建函数与表达式
- 多列多指标聚合用
agg(dict)
一次完成,减少多次扫描
- 可读性:
- 分组链式写法建议
.groupby(...).agg(...).rename(...).reset_index()
,用于清晰输出
- 分组链式写法建议
六、可直接复用的模板片段
- 组内 Z-Score 并对齐:
df['lifeExp_z'] = df.groupby('year')['lifeExp'].transform(lambda x: (x - x.mean())/x.std())
- 多列多指标聚合:
out = df.groupby('year').agg({'lifeExp':'mean','pop':'max','gdpPercap':'min'
}).rename(columns={'lifeExp':'平均寿命','pop':'最大人口','gdpPercap':'人均最小GDP'}).reset_index()
- 组均值缺失值填充:
df['total_bill'] = df.groupby('sex')['total_bill'].transform(lambda x: x.fillna(x.mean()))
七、结语
本文系统梳理了 Pandas 的 apply、向量化与分组进阶技巧,并通过 gapminder 与 tips 两个数据集展示了典型统计与缺失值处理套路。建议在项目中优先考虑“真·向量化 + groupby/transform”,在必须强自定义时再回退到 np.vectorize
或 apply
。
更新日期:2025年8月25日,后续会持续更新