查看全课大纲(第 41 / 52 节)
- 1.量化分析基本概念:从咖啡馆到华尔街
- 2.量化交易员学习路线:从零基础到策略开发
- 3.量化策略生命周期:数据分析的五大流程
- 4.量化数据类型:数值型与类别型数据
- 5.量化沙盒搭建(1):Python与Anaconda安装
- 6.量化沙盒搭建(2):Jupyter Notebook与库配置
- 7.本地多源数据:量化历史回测的基石
- 8.TXT数据流操作:处理金融交易日志
- 9.JSON数据流操作:解析股票行情API响应
- 10.CSV行情文件操作:读取与存储历史K线
- 11.Excel报表操作:清洗财报与财务指标
- 12.量化数据库:SQL常用查询语法
- 13.量化数据库:Python连接与写入
- 14.量化多表连接:关联股票信息与行情
- 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
- 16.量化数据采集:爬虫工作原理与合规
- 17.量化爬虫核心:URL管理与去重策略
- 18.量化网页下载:防反爬与请求头伪装
- 19.网页解析(1):使用XPath定位财经新闻
- 20.网页解析(2):使用CSS选择器与ItemLoader
- 21.Scrapy框架:构建工业级量化爬虫
- 22.Scrapy实战(1):新建项目与数据模型定义
- 23.Scrapy实战(2):编写财经爬虫逻辑
- 24.Scrapy实战(3):启用数据管道清洗行情
- 25.Scrapy实战(4):防反爬中间件配置
- 26.实战:构建每日热门个股行情自动采集系统
- 27.NumPy与SciPy:加速量化矩阵计算
- 28.NumPy数组:构建股票价格矩阵
- 29.数组切片:提取特定股票与交易日数据
- 30.NumPy常用方法:计算均值与波动率
- 31.向量化计算:摆脱缓慢的For循环
- 32.通用函数ufunc:计算对数收益率与累计收益
- 33.实战:股票组合资产收益率与协方差矩阵计算
- 34.Pandas Series:管理单只股票收盘价
- 35.Pandas DataFrame:构建多只股票行情面板
- 36.Pandas Index:时间序列索引与对齐
- 37.DataFrame索引操作:loc与iloc定位行情
- 38.索引总结:量化多字段筛选与定位
- 39.运算与对齐:合并不同交易日的数据
- 40.Pandas map:对股票代码与状态做映射
- 41.Pandas apply:滚动计算技术指标
- 42.排序操作:按市盈率与收益率对股票排名
- 43.数据清洗:填充与剔除停牌股票缺失值
- 44.数据清洗:剔除重复报价与异常交易数据
- 45.常用统计:量化最大回撤与分位数风险评估
- 46.极值定位:寻找历史最高价与最低价的发生日
- 47.风险与累计收益:计算年化波动率与累计回报
- 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
- 49.Series索引操作:按位置与标签访问行情数据
- 50.文件读写操作:CSV行情数据导入导出
- 51.数据清洗:替换异常报价数据
- 52.常用统计:sum/mean/median/count聚合指标
Pandas apply:滚动计算技术指标
约 15 分钟
Pandas 函数应用进阶:apply 轴向计算与全表映射(原 applymap)
小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街
上一节我们用 Series.map 处理了单列的值映射。这一节要解决的是更常见的批量计算场景:一张持仓表里多列之间要交叉运算、整张行情表的每个单元格要统一格式��。这正是 apply 与 map(原课讲授的 applymap)出场的地方。学完本节,你能清晰判断「什么时候用 map、什么时候用 apply」,并写出在现代 pandas 环境里可直接运行的批量计算代码。
💡 核心导读
- 三个函数应用 API 的适用对象:
Series.map只作用于单列;DataFrame.apply沿指定轴(行或列)批量计算;DataFrame.map作用于整张表的所有单元格。 apply的轴向语义:axis=1表示沿行横向计算(多列交叉运算,如收盘价 × 持仓量 = 市值),axis=0表示沿列纵向聚合。- 一次重要的版本变更:原课讲授的
applymap在 pandas 3.0 中已被移除,官方统一为DataFrame.map。本讲义一律使用新写法,并说明如何迁移旧代码。 - 全部代码锁定现代环境实跑(Python 3.14 + pandas 3.0),输出可直接核对。
1. 三个函数应用 API 的定位边界
Pandas 提供了三层递进的函数应用能力,先厘清各自的适用范围:
┌────────────────────────────────────────────────────────────┐
│ 函数应用 API 选区(量化数据处理场景) │
├──────────────┬─────────────────────────────────────────────┤
│ Series.map │ 仅单列:元素级的值替换 / 映射 │
│ DataFrame.apply │ 沿 axis=0(列)或 axis=1(行)批量计算 │
│ DataFrame.map│ 整张表:对每个单元格做元素级变换 │
└──────────────┴─────────────────────────────────────────────┘其中 apply 的关键在于轴向:axis=1(沿行)会把每一行当作一个 Series 传入函数,适合做「多列交叉」的计算(收盘价 × 持仓量 = 持仓市值);axis=0(沿列,默认)会把每一列当作 Series 传入,适合做列级聚合(如求均值、极差)。
下面在一张持仓快照上同时演示这两种轴向:
import pandas as pd
# 某量化组合的持仓与行情快照(硬编码小样例,输出确定)
positions = pd.DataFrame({
"stock_code": ["600519", "000858", "601318", "600036"],
"stock_name": ["贵州茅台", "五粮液", "中国平安", "招商银行"],
"close": [1682.0, 142.5, 48.6, 36.2],
"quantity": [100, 2000, 5000, 8000],
})
# axis=1:沿行横向计算——每一行以 Series 传入,用于多列交叉运算
positions["market_value"] = positions.apply(
lambda row: row["close"] * row["quantity"], axis=1
)
print("持仓市值(axis=1 逐行计算):")
print(positions[["stock_name", "close", "quantity", "market_value"]])持仓市值(axis=1 逐行计算):
stock_name close quantity market_value
0 贵州茅台 1682.0 100 168200.0
1 五粮液 142.5 2000 285000.0
2 中国平安 48.6 5000 243000.0
3 招商银行 36.2 8000 289600.0注意这里的语义:函数体里用列名 row["close"]、row["quantity"] 取数,必须配 axis=1。如果误写成 axis=0,每一列会被当成一个 Series 传入,row["close"] 立刻抛 KeyError——这是量化数据处理里最常见的轴向错误。
再看 axis=0 的列级聚合:
import pandas as pd
# 三只标的的日收益率(硬编码小样例,输出确定)
returns = pd.DataFrame({
"600519": [0.012, -0.008, 0.005, 0.011],
"000858": [-0.015, 0.006, 0.009, -0.004],
"601318": [0.003, 0.002, -0.006, 0.007],
})
# axis=0:沿列纵向聚合——对每只标的分别求平均日收益率
print("各标的平均日收益率(axis=0 列级聚合):")
print(returns.apply(lambda col: col.mean()))各标的平均日收益率(axis=0 列级聚合):
600519 0.0050
000858 -0.0010
601318 0.0015
dtype: float64把两者对照起来看:axis=1 的结果长度等于行数(每行一个值),axis=0 的结果长度等于列数(每列一个值)。判断口诀:看你要的结果是「每行一个」还是「每列一个」。
2. 全表元素级映射:DataFrame.map(原 applymap)
量化数据里常见的另一类需求是格式统一,比如把收益率矩阵里的小数统一转成百分比字符串。原课使用的 DataFrame.applymap 正是完成这件事的经典函数,但在 pandas 3.0 中 applymap 已被移除,官方给出的等价写法是 DataFrame.map——语义完全一致,只是方法名与 Series.map 统一了。因此本讲义一律使用 DataFrame.map。
import pandas as pd
# 收益率矩阵:3 只标的 × 4 个交易日(硬编码小样例,输出确定)
returns = pd.DataFrame({
"600519": [0.012, -0.008, 0.005, 0.011],
"000858": [-0.015, 0.006, 0.009, -0.004],
"601318": [0.003, 0.002, -0.006, 0.007],
})
# DataFrame.map:pandas 3.0 起取代已移除的 applymap,逐单元格变换
pct = returns.map(lambda x: f"{x * 100:.2f}%")
print("逐单元格转百分比(DataFrame.map):")
print(pct)
# Series.map:仅单列,按字典做值映射
s = pd.Series(["A股", "港股", "A股", "美股"])
print("\n市场分类编码(Series.map):")
print(s.map({"A股": "A", "港股": "H", "美股": "US"}))逐单元格转百分比(DataFrame.map):
600519 000858 601318
0 1.20% -1.50% 0.30%
1 -0.80% 0.60% 0.20%
2 0.50% 0.90% -0.60%
3 1.10% -0.40% 0.70%
市场分类编码(Series.map):
0 A
1 H
2 A
3 US
dtype: strDataFrame.map 的关键特点是逐单元格、无视列名:它把函数依次作用到每一个格子,函数只关心「这个值是什么」,不关心「它在哪一列」。上面把小数收益率转成带百分号的字符串,就是它的典型用途。
版本提示:若你在旧教程或原课视频中看到
df.applymap(...),只需把方法名替换为df.map(...)即可,参数与返回行为不变。这是 pandas 3.0 的一次统一命名变更,不影响数据分析思路。
3. 三者的选择经验与常见坑
实际工作中按下面的顺序判断即可:
- 只处理一列的值映射(如把「市场分类」换成代码)→ 用
Series.map; - 需要沿行或沿列把多列组合成一个结果(如按行算持仓市值、按列算平均收益)→ 用
DataFrame.apply; - 需要对整张表的每个单元格做同一个轻量变换(转百分比、统一格式化)→ 用
DataFrame.map。
因为 DataFrame.map 无视列名,对混合类型表格要格外小心:如果表里既有数值列又有文本列,函数必须能同时处理两种类型,否则会抛异常。下面的写法用类型判断做防护,这在因子权重表这类混合表里很常见:
import pandas as pd
# 因子权重表:权重列是小数,因子名列是文本(硬编码小样例)
weights = pd.DataFrame({
"factor": ["momentum", "value", "quality"],
"weight": [0.35, 0.45, 0.20],
})
# DataFrame.map 对全表逐元素生效,故需按类型分支处理
formatted = weights.map(
lambda v: f"{v:.2f}" if isinstance(v, float) else v
)
print("因子权重表格式化结果:")
print(formatted)因子权重表格式化结果:
factor weight
0 momentum 0.35
1 value 0.45
2 quality 0.20两个常见坑:
- 轴向写反:
axis=1与axis=0的语义完全不同。报错时第一件事就是检查轴向——尤其是函数体里用列名取数却传了axis=0,会直接KeyError。 - 性能预期:
apply/map本质上是把 Python 函数逐元素或逐切片地作用上去,属于 Python 级循环,适合「逻辑复杂但数据量不大」的场景。当计算本身可以用 NumPy 向量化表达时(例如整列统一加减乘除),应优先用向量化写法——在百万行级别的日频数据上,两者速度差别非常明显。
📝 动手练一练
概念辨析题:请用一句话分别说明
Series.map、DataFrame.apply、DataFrame.map的适用对象。👉 点击查看参考答案
参考答案:
Series.map:只作用于单个 Series(一列),做元素级的值替换或映射;DataFrame.apply:作用于 DataFrame,沿axis=0(按列)或axis=1(按行)批量计算;DataFrame.map:作用于整张 DataFrame,对每一个单元格做元素级变换(即原applymap)。
编程练习题:给定
df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}),请用apply沿axis=1新增一列row_sum,值为每行 a 与 b 之和。👉 点击查看参考答案
参考答案:
import pandas as pd df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) df['row_sum'] = df.apply(lambda row: row['a'] + row['b'], axis=1) print(df)
a b row_sum
0 1 4 5
1 2 5 7
2 3 6 9迁移练习题:原课用
applymap完成了「把权重矩阵的小数统一格式化为两位小数字符串」。请写出 pandas 3.0 下的等价写法,并说明需要改动方法名的原因。👉 点击查看参考答案
参考答案:
import pandas as pd weights = pd.DataFrame({"w1": [0.1234, 0.5678], "w2": [0.9, 0.1]}) print(weights.map(lambda v: f"{v:.2f}"))w1 w2
0 0.12 0.90 1 0.57 0.10
原因:`DataFrame.applymap` 在 pandas 3.0 中已被移除,官方将其更名为 `DataFrame.map`,语义与参数完全不变,因此只需把方法名 `applymap` 换成 `map` 即可,**不要再使用 `applymap`**。
</details>
---
## 本章小结
- 掌握了 `DataFrame.apply` 的 `axis=0`(列级聚合)与 `axis=1`(行级交叉计算)两种轴向用法,并记住判断口诀:**要「每行一个结果」用 `axis=1`,要「每列一个结果」用 `axis=0`**;
- 掌握了 `DataFrame.map` 对整张表逐元素变换的方法,并知晓它是 **pandas 3.0 中 `applymap` 的替代写法**(原 `applymap` 已移除,练习与小结中均不应再出现该名称);
- 厘清了 `map`、`apply`、`map`(原 `applymap`)三者的选择边界与版本差异。
### 📋 行动清单
- [ ] 在本地执行本节代码,观察 `axis=0` 与 `axis=1` 两种输出的差异。
- [ ] 找一个旧教程里的 `applymap` 例子(含练习题),把它改写成 `DataFrame.map` 并跑通。
- [ ] 检查自己手上的量化项目里是否还有 `applymap` 调用,一并迁移。
—— 小象教研组