查看全课大纲(第 38 / 52 节)
  1. 1.量化分析基本概念:从咖啡馆到华尔街
  2. 2.量化交易员学习路线:从零基础到策略开发
  3. 3.量化策略生命周期:数据分析的五大流程
  4. 4.量化数据类型:数值型与类别型数据
  5. 5.量化沙盒搭建(1):Python与Anaconda安装
  6. 6.量化沙盒搭建(2):Jupyter Notebook与库配置
  7. 7.本地多源数据:量化历史回测的基石
  8. 8.TXT数据流操作:处理金融交易日志
  9. 9.JSON数据流操作:解析股票行情API响应
  10. 10.CSV行情文件操作:读取与存储历史K线
  11. 11.Excel报表操作:清洗财报与财务指标
  12. 12.量化数据库:SQL常用查询语法
  13. 13.量化数据库:Python连接与写入
  14. 14.量化多表连接:关联股票信息与行情
  15. 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
  16. 16.量化数据采集:爬虫工作原理与合规
  17. 17.量化爬虫核心:URL管理与去重策略
  18. 18.量化网页下载:防反爬与请求头伪装
  19. 19.网页解析(1):使用XPath定位财经新闻
  20. 20.网页解析(2):使用CSS选择器与ItemLoader
  21. 21.Scrapy框架:构建工业级量化爬虫
  22. 22.Scrapy实战(1):新建项目与数据模型定义
  23. 23.Scrapy实战(2):编写财经爬虫逻辑
  24. 24.Scrapy实战(3):启用数据管道清洗行情
  25. 25.Scrapy实战(4):防反爬中间件配置
  26. 26.实战:构建每日热门个股行情自动采集系统
  27. 27.NumPy与SciPy:加速量化矩阵计算
  28. 28.NumPy数组:构建股票价格矩阵
  29. 29.数组切片:提取特定股票与交易日数据
  30. 30.NumPy常用方法:计算均值与波动率
  31. 31.向量化计算:摆脱缓慢的For循环
  32. 32.通用函数ufunc:计算对数收益率与累计收益
  33. 33.实战:股票组合资产收益率与协方差矩阵计算
  34. 34.Pandas Series:管理单只股票收盘价
  35. 35.Pandas DataFrame:构建多只股票行情面板
  36. 36.Pandas Index:时间序列索引与对齐
  37. 37.DataFrame索引操作:loc与iloc定位行情
  38. 38.索引总结:量化多字段筛选与定位
  39. 39.运算与对齐:合并不同交易日的数据
  40. 40.Pandas map:对股票代码与状态做映射
  41. 41.Pandas apply:滚动计算技术指标
  42. 42.排序操作:按市盈率与收益率对股票排名
  43. 43.数据清洗:填充与剔除停牌股票缺失值
  44. 44.数据清洗:剔除重复报价与异常交易数据
  45. 45.常用统计:量化最大回撤与分位数风险评估
  46. 46.极值定位:寻找历史最高价与最低价的发生日
  47. 47.风险与累计收益:计算年化波动率与累计回报
  48. 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
  49. 49.Series索引操作:按位置与标签访问行情数据
  50. 50.文件读写操作:CSV行情数据导入导出
  51. 51.数据清洗:替换异常报价数据
  52. 52.常用统计:sum/mean/median/count聚合指标

索引总结:量化多字段筛选与定位

约 8 分钟

小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街

Pandas 是量化交易员最核心的武器。本章你将通过 Series 和 DataFrame 深度处理时间序列行情数据,掌握 loc/iloc 定位筛选、缺失值与重复值清洗、以及最大回撤与年化波动率等核心指标统计计算。

💡 核心导读

本节将系统讲解 Pandas 的核心索引操作技术,包括基础索引、高级索引和多级索引的使用方法。通过本节学习,您将掌握:1) 使用 loc 和 iloc 进行精确数据定位 2) 布尔索引实现条件筛选 3) MultiIndex 结构化数据操作技巧。所有知识点均配有可立即执行的代码示例,帮助您快速构建索引操作的肌肉记忆。


基础索引操作

位置索引与标签索引

import pandas as pd

data = {
    '产品': ['A', 'B', 'C', 'D'],
    '销量': [120, 95, 210, 80],
    '单价': [15.5, 22.0, 9.9, 30.0]
}
df = pd.DataFrame(data, index=['Q1', 'Q2', 'Q3', 'Q4'])

print(df.iloc[1:3, 0:2])
print()
print(df.loc['Q2':'Q4', ['产品', '单价']])

执行结果对比:

产品   销量
Q2  B   95
Q3  C  210

   产品    单价
Q2  B  22.0
Q3  C   9.9
Q4  D  30.0

布尔索引技巧

import pandas as pd

data = {
    '产品': ['A', 'B', 'C', 'D'],
    '销量': [120, 95, 210, 80],
    '单价': [15.5, 22.0, 9.9, 30.0]
}
df = pd.DataFrame(data, index=['Q1', 'Q2', 'Q3', 'Q4'])

high_sales = df[df['销量'] > 100]
condition = (df['销量'] > 90) & (df['单价'] < 20)
filtered = df[condition]

print("高销量产品:")
print(high_sales)
print("\n复合条件筛选:")
print(filtered)
高销量产品:
   产品   销量    单价
Q1  A  120  15.5
Q3  C  210   9.9

复合条件筛选:
   产品   销量    单价
Q1  A  120  15.5
Q3  C  210   9.9

高级索引技术

多级索引操作

import pandas as pd

arrays = [
    ['苹果公司', '苹果公司', '谷歌公司', '谷歌公司'],
    ['家电', '数码', '家电', '数码']
]
multi_index = pd.MultiIndex.from_arrays(arrays, names=('股票标的', '品类'))

sales_data = pd.DataFrame({
    '1月': [120, 85, 90, 110],
    '2月': [95, 88, 102, 125]
}, index=multi_index)

print("一级索引(苹果公司):")
print(sales_data.loc['苹果公司'])
print("\n二级索引(品类为'数码'):")
print(sales_data.xs('数码', level='品类'))
一级索引(苹果公司):
     1月  2月
品类
家电  120  95
数码   85  88

二级索引(品类为'数码'):
       1月   2月
股票标的
苹果公司   85   88
谷歌公司  110  125

索引重置与设置

import pandas as pd

arrays = [
    ['苹果公司', '苹果公司', '谷歌公司', '谷歌公司'],
    ['家电', '数码', '家电', '数码']
]
multi_index = pd.MultiIndex.from_arrays(arrays, names=('股票标的', '品类'))

sales_data = pd.DataFrame({
    '1月': [120, 85, 90, 110],
    '2月': [95, 88, 102, 125]
}, index=multi_index)

reset_df = sales_data.reset_index()
print("重置索引后(前两行):")
print(reset_df.head(2))

new_index_df = reset_df.set_index(['股票标的', '1月'])
print("\n新索引结构(前两行):")
print(new_index_df.head(2))
重置索引后(前两行):
   股票标的  品类   1月  2月
0  苹果公司  家电  120  95
1  苹果公司  数码   85  88

新索引结构(前两行):
          品类  2月
股票标的 1月
苹果公司 120  家电  95
     85   数码  88


索引优化策略

性能对比实验

import pandas as pd
import numpy as np

np.random.seed(42)
big_data = pd.DataFrame(np.random.rand(10, 5), columns=list('ABCDE'))

loc_result = big_data.loc[5]
iloc_result = big_data.iloc[5]
bool_result = big_data[big_data['A'] > 0.8]

print("标签索引 (loc[5]) 前两列:")
print(loc_result[['A', 'B']])
print("\n位置索引 (iloc[5]) 前两列:")
print(iloc_result[['A', 'B']])
print("\n布尔索引 (A > 0.8) 形状:")
print(bool_result.shape)
print("(布尔索引通常比位置索引慢,因为需要逐元素比较)")
标签索引 (loc[5]) 前两列:
A    0.785176
B    0.199674
Name: 5, dtype: float64

位置索引 (iloc[5]) 前两列:
A    0.785176
B    0.199674
Name: 5, dtype: float64

布尔索引 (A > 0.8) 形状:
(1, 5)
(布尔索引通常比位置索引慢,因为需要逐元素比较)

最佳实践原则

  1. 大数据集优先使用 iloc 位置索引
  2. 复杂条件查询先构建布尔掩码
  3. 多级索引查询使用 xs() 方法

📝 动手练一练

第 1 题 · 概念应用

假设你正在分析一个长表结构的日行情 DataFrame price_df:索引为日期,列包括 ticker(股票代码)、close(收盘价)。你想给“收盘价高于其 20 日移动平均线”的记录打一个 above_ma 标记,于是写出了下面的链式赋值代码:

above_20ma = price_df.groupby('ticker')['close'].transform(lambda s: s > s.rolling(20).mean())
price_df[above_20ma]['above_ma'] = True

运行后标记没有稳定写回 price_df,Pandas 还给出了 SettingWithCopyWarning(pandas 3 中这类“悬空赋值”默认直接抛 ChainedAssignmentError)。以下哪一项是导致该问题的核心原因,以及最推荐的修正方法?

  • A. 警告是因为 rolling(20).mean() 计算时包含了未来数据,存在前视偏差。应改为使用 .shift(1) 来避免。
  • B. 警告是因为在大型数据集上连续使用布尔索引效率低下。应先将条件赋值给变量,再多做一次布尔筛选。
  • C. 链式的两次方括号索引中,第一次筛选返回的可能是副本,对它的赋值无法保证写回原 DataFrame。应使用单次 .loc:price_df.loc[above_20ma, 'above_ma'] = True。
  • D. 警告是因为分组后的布尔序列索引与原 DataFrame 对不齐。应先 price_df = price_df.reset_index() 再赋值。
👉 点击查看参考答案

答案:C

解析:

  1. price_df[above_20ma] 是一次布尔筛选,返回的可能是原 DataFrame 的副本;紧接其后的 ['above_ma'] = True 就落在了这个临时副本上,Pandas 无法判断你的本意是不是修改原表,于是发出 SettingWithCopyWarning,pandas 3 干脆默认抛错以防赋值静默丢失。
  2. 修正方法是用单次 .loc[行条件, 列名] = 值 显式地在原 DataFrame 上完成“筛选 + 赋值”,没有歧义、不会产生中间副本。
  3. 选项 A 讨论的是策略逻辑的前视偏差(本题均线只用了历史数据),与该警告无关;选项 B 把问题归结为性能,且“多筛选一次”仍可能返回副本;选项 D 的 reset_index 不改变链式赋值的悬空本质。

第 2 题 · 动手题(代码填空)

你管理着一个投资组合,数据存储在 DataFrame portfolio 中。现在需要根据最新的市场分析,同时更新满足以下两个条件的持仓记录:1) 股票代码(ticker)为 'MSFT';2) 所属行业(sector)为 '科技'。需要将这部分持仓的“目标权重”(target_weight)字段上调 5 个百分点(即增加 0.05)。

请补全下面的代码,使用 安全且高效的单步 .loc 索引 来完成此更新。

import pandas as pd
import numpy as np

# 模拟投资组合数据
portfolio = pd.DataFrame({
    'ticker': ['AAPL', 'MSFT', 'GOOGL', 'MSFT', 'JPM'],
    'sector': ['科技', '科技', '科技', '云服务', '金融'],
    'target_weight': [0.20, 0.15, 0.25, 0.10, 0.30]
})

print("更新前:")
print(portfolio)

# 使用 .loc 进行条件筛选并更新目标权重
selection_mask = (portfolio['ticker'] == 'MSFT') & (portfolio['sector'] == '科技')
portfolio.loc[______, 'target_weight'] += 0.05

print("\n更新后:")
print(portfolio)
👉 点击查看参考答案

空位应填:selection_mask

补全后的完整可运行代码如下:

import pandas as pd
import numpy as np

# 模拟投资组合数据
portfolio = pd.DataFrame({
    'ticker': ['AAPL', 'MSFT', 'GOOGL', 'MSFT', 'JPM'],
    'sector': ['科技', '科技', '科技', '云服务', '金融'],
    'target_weight': [0.20, 0.15, 0.25, 0.10, 0.30]
})

print("更新前:")
print(portfolio)

# 使用 .loc 进行条件筛选并更新目标权重
selection_mask = (portfolio['ticker'] == 'MSFT') & (portfolio['sector'] == '科技')
portfolio.loc[selection_mask, 'target_weight'] += 0.05

print("\n更新后:")
print(portfolio)
更新前:
  ticker sector  target_weight
0   AAPL     科技           0.20
1   MSFT     科技           0.15
2  GOOGL     科技           0.25
3   MSFT    云服务           0.10
4    JPM     金融           0.30

更新后:
  ticker sector  target_weight
0   AAPL     科技           0.20
1   MSFT     科技           0.20
2  GOOGL     科技           0.25
3   MSFT    云服务           0.10
4    JPM     金融           0.30

解释:代码首先创建了一个布尔掩码 selection_mask,用于标识出所有 ticker 为 'MSFT' 且 sector 为 '科技' 的行。然后,通过 .loc[selection_mask, 'target_weight'] 精确定位到这些行的 target_weight 列,并对其进行 += 0.05 的操作。这种方法避免了链式索引,是 Pandas 官方推荐的修改数据的方式。

本章小结

📋 行动清单

  • 使用 loc 和 iloc 分别提取某DataFrame的第3-5行和’price’列
  • 创建一个包含多级索引的DataFrame,并练习xs()跨层级查询
  • 对10万行以上的数据集进行索引性能测试,比较不同方法的耗时差异

—— 小象教研组