引言与学习目标
在当今的金融行业和学术研究中,数据分析能力已经成为一项核心竞争力。无论是量化投资策略的开发、上市公司财务报表的分析,还是宏观经济趋势的预测,都离不开对海量数据的高效处理和深入挖掘。Python 凭借其简洁的语法、强大的生态系统和卓越的数据处理能力,已成为全球金融从业者和研究者的首选编程语言。
学习目标
完成本章后,你应能:
用“数据规模—算子—内存—结果物化”四个维度解释一个金融数据任务为何需要 Pandas、Polars、DuckDB 或 Dask;
在终端创建或更新 peter 环境、注册同名 Jupyter 内核,并打印解释器与核心库版本作为验证证据;
运行教师提供的 Python 循环与 NumPy 向量化脚手架,核对同一计算的结果一致性,并把计时结论限定在本机、本次输入与具体算子;
运行教师提供的 Pandas 真实数据脚手架,依据输出识别交易所分布、字段合同与最早上市公司;
按 PEP 8 与意图导向原则检查一段代码的命名、缩进和常量写法。
目标—活动—核心练习/答案映射
条件化选择 Pandas、Polars、DuckDB 或 Dask
小节 1.2.2 与 小节 1.5.1 的任务边界比较
练习 1.4 及参考答案中的四维选择理由
建立并核验统一环境
小节 1.3.1 与 小节 1.3.2 的环境操作
练习 1.1 及参考答案打印的解释器路径、Python 与三项核心库版本
运行并解释循环与向量化脚手架
小节 1.4.1 的等价计算与计时
练习 1.2 对既有输出的一致性核验和条件化解释;不评分 NumPy API
运行并解释真实数据脚手架
小节 1.6.2 的字段、类型与地域检查
练习 1.3 对教师生成表的字段、交易所计数与上市日期解释;不评分 Pandas API
按规范审查代码
小节 1.4.3 的 PEP 8 与意图导向示例
练习 1.5 及参考答案中的问题清单和改写代码
前置知识要求
具备基本的计算机操作能力(文件管理、命令行基础)
不要求已有编程经验;本章只要求能复制终端命令,并会识别文件、文件夹与表格中的行和列
具备基础的金融知识(股票、债券、收益率等概念)
若尚未接触变量、循环或函数,可以先把它们分别理解为“带名字的值”“重复步骤”和“可复用步骤”。第 2 章会正式讲授这些概念,第 3 章讲 NumPy,第 5 章讲 Pandas。本章提前出现的 NumPy/Pandas 数据准备均为教师提供的脚手架:学生只运行、观察和核对,不要求独立编写,也不计入 API 评分 。本章评分只落在已经解释的环境命令、概念边界、输出证据与代码规范。
大数据分析的演进:从 Hadoop 生态到现代 Python 接口数据栈
传统大数据技术栈的局限
在 2010 年代,大数据处理的主流方案是以 Java/JVM 为基础的 Hadoop 生态系统,包括 HDFS、MapReduce、Hive、Spark 等组件。这些工具虽然能够处理 PB 级别的数据,但对于商学院学生和金融分析师而言,存在显著的使用门槛:
部署复杂 :需要配置分布式集群环境,单机学习成本极高
语言壁垒 :核心框架基于 Java/Scala,学习曲线陡峭
资源消耗大 :JVM 的内存开销和启动时间对个人电脑不友好
迭代效率低 :从代码编写到结果查看的反馈周期过长
现代 Python 数据栈的崛起
近年来,得益于 Apache Arrow 等内存标准以及 C++、Rust 等原生执行引擎的发展,一批提供 Python API 的数据处理工具改变了大数据分析的工作方式:
Pandas
成熟的表格分析库,API 丰富
能否适用取决于数据布局、算子和内存预算
Polars
Rust 执行引擎、表达式 API 与多线程
列式查询、惰性执行与流式处理
DuckDB
进程内分析型数据库,支持 SQL
单机大数据 SQL 查询
Dask
Python 原生分布式计算框架
超大规模数据集并行计算
Apache Arrow
列式内存格式标准
在类型和缓冲区兼容时减少跨工具复制
这些工具的共同特点是:
统一的 Python 接口 :多数工具可通过 pip 或 conda 安装;Python 是操作界面,底层实现语言因工具而异
单机高性能 :充分利用现代 CPU 的多核架构和向量化指令集
内存策略多样 :部分工具支持惰性求值或流式处理;能否处理超过内存的数据仍取决于算子、分区、临时结果与输出物化方式
互操作性较强 :Apache Arrow 提供共同的列式表示;是否零拷贝取决于数据类型、缓冲区所有权、分块方式和目标工具
中国金融市场的数据规模参考
以中国 A 股市场为例,数据规模覆盖了从中小型到大规模的各个层次:
日频行情数据 :约 5000 只股票 × 20 年 × 250 交易日 ≈ 2500 万行;Pandas 是否适用要结合列数、dtype、可用内存和具体算子评估
分钟频行情数据 :约 5000 只 × 20 年 × 250 天 × 240 分钟 ≈ 60 亿行,通常需要列投影、分区和按需计算,而非仅凭格式指定唯一工具
Tick 级行情数据 :每只股票每日可达数万条,全市场数据通常需要分块、分区或分布式执行,并严格控制最终结果大小
本教材将循序渐进地引导你掌握处理上述各种规模数据的能力。
技术演进时间线
图 图 1.1 展示本节讨论对象的可视化结果,读图时应结合正文给出的口径与限制。
图中的年份统一表示可由项目方资料核验的具体里程碑,而不是笼统的“技术成熟年”。Apache Spark 的项目历史 记录其 2009 年始于研究项目、2010 年初开源;Apache Arrow 的 0.1.0 发布页 记录首个版本发布于 2016 年;DuckDB 项目历史 区分了 2018 年开始开发与 2019 年公开 v0.1;Polars 的项目回顾 把首次提交定在 2020 年 6 月 23 日;Pandas 2.0 的 release notes 则记录了 2023 年版本里程碑。Hadoop 在这里作为更早的分布式生态背景出现,不再用一个未经本节来源定义的年份概括整个项目。
基础开发环境搭建
Conda 包管理与虚拟环境
Conda 是 Python 数据科学领域最流行的包管理和环境管理工具。与 pip 相比,Conda 能够管理非 Python 依赖(如 C/C++ 库),并提供了强大的虚拟环境隔离机制。
安装 Miniconda
推荐使用 Miniconda(Conda 的最小安装版本),它只包含 Conda 本身和 Python,体积小巧:
访问 Miniconda 官网 下载适合你操作系统的安装包
按照安装向导完成安装
打开终端(Windows 用户打开 Anaconda Prompt),验证安装:
import platform # 识别操作系统,以便全章后续真实数据示例复用统一数据根路径
from pathlib import Path # 为小型快照哈希验证构造跨平台文件路径
DATA_ROOT = 'C:/qiufei/data' if platform.system() == 'Windows' else '/home/ubuntu/r2_data_mount/data' # 为跨平台本地数据读取建立唯一入口
# 在终端中执行以下命令验证 Conda 是否正确安装
# conda --version # 检查 conda 版本号
# python --version # 检查 Python 版本号
print ('Conda 环境已正确配置' ) # 在 Python 中确认环境就绪
创建和管理虚拟环境
虚拟环境是 Python 开发的最佳实践,它能隔离不同项目的依赖,避免版本冲突:
# 以下为终端命令,非 Python 代码
# conda env create --file environment.yml # 首次部署时按仓库清单创建 peter
# conda env update --name peter --file environment.yml --prune # 已存在时按同一清单更新;与上一行二选一
# conda run --no-capture-output --name peter python -m ipykernel install --user --name peter --display-name peter # 将同名内核指向当前环境
# conda run --no-capture-output --name peter jupyter kernelspec list --json # 核对 peter.spec.argv[0] 的解释器路径
本教材的环境配置
本教材以 environment.yml 创建或更新名为 peter 的 Conda 环境。只有完成项目规定的解释器、依赖和内核预检后,下列导入与版本输出才代表本书的目标环境:
import sys # 导入系统模块,用于获取 Python 解释器信息
print (f'Python 版本: { sys. version} ' ) # 打印当前 Python 版本
print (f'Python 路径: { sys. executable} ' ) # 打印 Python 解释器的完整路径
Python 版本: 3.10.20 (main, Mar 11 2026, 17:46:40) [GCC 14.3.0]
Python 路径: /home/ubuntu/miniconda3/envs/peter/bin/python
import pandas as pd # 导入 Pandas 库,Python 最核心的数据分析工具
import numpy as np # 导入 NumPy 库,提供高性能数值计算支持
import matplotlib # 导入 Matplotlib 库,Python 基础绑图引擎
print (f'Pandas 版本: { pd. __version__} ' ) # 确认 Pandas 版本
print (f'NumPy 版本: { np. __version__} ' ) # 确认 NumPy 版本
print (f'Matplotlib 版本: { matplotlib. __version__} ' ) # 确认 Matplotlib 版本
Pandas 版本: 2.3.3
NumPy 版本: 1.26.4
Matplotlib 版本: 3.10.8
Jupyter 生态系统
Jupyter 是交互式数据分析的标准工具,它提供了”代码-输出-文档”三位一体的工作环境。
Jupyter Notebook vs JupyterLab
界面
单文档
多标签页,类 IDE
文件管理
基础
完善的文件浏览器
扩展性
有限
丰富的插件生态
终端
不支持
内置终端
适用场景
快速原型开发
完整的分析项目
Jupyter 的核心概念
Jupyter Notebook 由一系列”单元格(Cell)“组成,每个单元格可以是:
代码单元格 :编写和执行 Python 代码
Markdown 单元格 :编写格式化文档和数学公式
输出区域 :展示代码运行结果(表格、图形等)
教师脚手架(只运行核对,不评分)
下面的 DataFrame 只帮助初学者认识 Jupyter 的表格输出。Pandas 将在第 5 章正式讲授;本章不要求解释或独立编写字典转表、列选择或聚合 API。
import pandas as pd # 导入 Pandas 用于创建示例数据框
sample_data = { # 构建一个简化的 A 股行情数据字典
'股票代码' : ['600276' , '002415' , '600104' ], # 采用三家长三角上市公司的代码作为标签
'股票名称' : ['恒瑞医药' , '海康威视' , '上汽集团' ], # 公司分别位于江苏、浙江和上海
'收盘价' : [45.0 , 32.0 , 15.0 ], # 设置仅用于 DataFrame 展示的假设价格,不作行情事实
'涨跌幅' : [0.015 , - 0.008 , 0.023 ] # 设置仅用于语法演示的假设日涨跌幅
} # 完成假设行情字典,避免把机制常量解释为真实观测
stock_overview = pd.DataFrame(sample_data) # 将字典转换为 Pandas 数据框
stock_overview # 在 Jupyter 中直接显示数据框,自动渲染为格式化表格
0
600276
恒瑞医药
45.0
0.015
1
002415
海康威视
32.0
-0.008
2
600104
上汽集团
15.0
0.023
开发工具推荐
除了 Jupyter 之外,以下工具也是数据分析工作中的得力助手:
VS Code :微软出品的免费代码编辑器,支持 Python 和 Jupyter Notebook 的原生编辑,配合 Copilot AI 插件可以大幅提升编程效率
Quarto :下一代科技出版系统,支持将包含代码的文档渲染为 HTML、PDF、Word 等多种格式,本教材即使用 Quarto 构建
大数据分析的基本思维与代码规范
向量化思维
在同质数值数组的逐元素计算中,一个常见的编程思维转变是从 Python 层循环转向向量化操作 。它常能减少解释器开销,但收益大小取决于数组规模、dtype、内存布局、具体内核和临时数组;含分支、字符串、I/O 或难以向量化的算法不一定更快。
为何向量化如此重要?
Python 层 for 循环会逐次承担解释器开销;NumPy 的许多数值内核由编译代码实现,并可能利用 SIMD(单指令多数据)指令集。Pandas 的高层操作还可能受索引对齐、缺失值、对象 dtype 和内存复制影响,因此应以同一输入上的结果核对与实测为准。
教师脚手架(只运行核对,不评分)
以下三个代码块由教师提供。学生只需依次运行,核对两条路径结果一致,并用条件化语言解释本次计时;数组创建、切片、np.diff() 与 np.allclose() 将在第 3 章讲授,不作为本章评分点。
import numpy as np # 导入 NumPy 用于向量化计算
import time # 导入时间模块用于计时
stock_prices = np.linspace(10.0 , 200.0 , num= 1_000_000 , dtype= np.float64) # 用确定性正值序列承载计时,不把题设数列冒充市场观测
assert stock_prices.shape == (1_000_000 ,) and stock_prices[0 ] == 10.0 and stock_prices[- 1 ] == 200.0 # 固定输入规模与端点,防止两种实现比较不同数据
start_time = time.time() # 记录循环开始时间
returns_loop = [] # 初始化空列表存储逐个计算的收益率
for i in range (1 , len (stock_prices)): # 遍历每个价格(从第2个开始)
daily_return = (stock_prices[i] - stock_prices[i- 1 ]) / stock_prices[i- 1 ] # 计算日收益率
returns_loop.append(daily_return) # 将收益率追加到列表中
loop_elapsed_seconds = time.time() - start_time # 计算循环方式的总耗时
print (f'循环方式耗时: { loop_elapsed_seconds:.4f} 秒' ) # 输出循环耗时
start_time = time.time() # 记录向量化开始时间
returns_vectorized = np.diff(stock_prices) / stock_prices[:- 1 ] # NumPy向量化计算日收益率
assert np.allclose(np.asarray(returns_loop), returns_vectorized) # 两种实现必须在同一固定输入上逐项一致
vectorized_elapsed_seconds = time.time() - start_time # 计算向量化方式的总耗时
vectorized_elapsed_seconds = max (vectorized_elapsed_seconds, 1e-9 ) # 防止除以零(向量化极快时耗时可能为0)
print (f'向量化方式耗时: { vectorized_elapsed_seconds:.4f} 秒' ) # 输出向量化耗时
speedup_ratio = loop_elapsed_seconds / vectorized_elapsed_seconds # 计算加速比
print (f'本机本次加速比: { speedup_ratio:.1f} 倍' ) # 仅报告当前硬件、输入规模和具体算子的观测结果
向量化方式耗时: 0.0457 秒
本机本次加速比: 8.7 倍
惰性求值与流式处理
面对 GB 级甚至 TB 级的数据,将所有数据一次性加载到内存中是不现实的。现代数据处理框架普遍采用两种策略来应对这一挑战:
惰性求值(Lazy Evaluation)
惰性求值的核心思想是:先构建计算图(描述”要做什么”),再统一执行(实际”去做”)。这允许框架在执行前进行全局优化,例如谓词下推(Predicate Pushdown)、投影下推(Projection Pushdown)等。
Polars 的 Lazy API 是惰性求值的典型代表:
# 以下为 Polars 的惰性求值示范(伪代码,后续章节将详细讲解)
# import polars as pl # 导入 Polars 库
# lazy_frame = pl.scan_parquet('huge_stock_data.parquet') # 扫描文件,不立即加载到内存
# result = (
# lazy_frame
# .filter(pl.col('exchange') == 'SSE') # 过滤条件:仅保留上交所股票
# .group_by('industry') # 按行业分组
# .agg(pl.col('market_cap').mean()) # 计算各行业平均市值
# .collect() # 此时才真正执行所有计算
# )
print ('惰性求值:先描述计算,再统一执行' ) # 说明核心思想
流式处理(Streaming)
流式处理将大文件分成小块(Chunk),逐块处理并汇总结果,内存占用始终保持在可控范围内:
# Pandas 的分块读取示范
# import pandas as pd # 导入 Pandas 库
# chunk_reader = pd.read_csv('huge_file.csv', chunksize=100000) # 每次读取10万行
# total_volume = 0 # 初始化成交量累计值
# for chunk in chunk_reader: # 逐块迭代处理
# total_volume += chunk['volume'].sum() # 累加每块的成交量
# print(f'总成交量: {total_volume}') # 输出最终的成交量汇总结果
print ('流式处理:分块读取,逐块计算,汇总结果' ) # 说明核心思想
Python 代码规范
编写规范、可读性强的代码是数据分析的重要素养。本教材遵循以下规范:
PEP 8 核心规范
命名规范 :变量和函数使用 snake_case(小写下划线),类名使用 CamelCase(驼峰命名),常量使用 UPPER_CASE(全大写)
缩进 :统一使用 4 个空格
行长度 :每行不超过 79 个字符
空行 :函数之间空两行,方法之间空一行
数据分析代码的额外规范
import pandas as pd # 导入 Pandas,注意按字母顺序排列 import 语句
TRADING_DAYS_PER_YEAR = 252 # 定义教学年化约定;实际年份的交易日数应按日历核验
def calculate_annualized_return(daily_returns_series): # 函数名清晰描述其功能
"""计算年化收益率。
Args:
daily_returns_series: 包含日收益率的 Pandas Series
Returns:
float: 年化收益率
"""
mean_daily_return = daily_returns_series.mean() # 计算日均收益率
annualized_return = mean_daily_return * TRADING_DAYS_PER_YEAR # 年化(简单年化法)
return annualized_return # 返回年化收益率
print (f'A股年交易日数: { TRADING_DAYS_PER_YEAR} ' ) # 展示常量的使用
变量命名的核心原则——意图导向
变量名应当精准传达其业务含义,而非使用无意义的泛型名称:
x
stock_price
明确表示这是股价
data
daily_return_series
说明数据的具体内容
temp
filtered_sse_stocks
描述过滤条件和结果
flag
is_trading_day
布尔值使用 is_ 前缀
list1
yangtze_delta_tickers
体现业务领域语境
本教材的结构与学习路径
本教材按照由浅入深、循序渐进的原则,将内容组织为五个篇章:
教材内容架构
图 图 1.2 展示本节讨论对象的可视化结果,读图时应结合正文给出的口径与限制。
学习路径建议
根据你的学习目标和时间安排,推荐以下学习路径:
路径一:数据分析基础(4-6 周)
适合刚接触 Python 的商学院学生,重点掌握 Pandas 的核心操作:
第 1 章 → 第 2 章 → 第 3 章 → 第 5 章 → 第 6 章 → 第 10 章
路径二:完整数据分析(8-12 周)
适合有一定编程基础的学生,全面掌握结构化数据分析:
第 1-10 章(按顺序学习)
路径三:大数据与量化金融(16 周完整课程)
适合对量化金融和大数据技术感兴趣的学生:
第 1-19 章(完整学习)
中国 A 股市场数据概览
本教材的案例和练习大量使用中国 A 股市场的真实数据。在正式开始学习之前,让我们先了解一下这些数据的基本特征。
数据来源与存储
本教材实际读取的数据族、相对路径、覆盖期、schema/复权、SHA-256、提供方证据和再分发边界统一记录在项目 readme.md 的『实际读取数据族 manifest』中。普通渲染只对小文件做完整哈希,并对巨型文件做 key、schema、行数或固定日期样本检查;发布审计才重算巨型文件哈希。
教师脚手架(只运行核对,不评分)
以下目录表和真实数据读取由教师提供。学生本章只需识别文件名、覆盖期、行列与字段输出;Pandas 的 DataFrame、HDF5 读取、筛选和分组不计分。
print (f'本章统一数据根路径: { DATA_ROOT} ' ) # 复用章首跨平台入口,避免不同示例悄然切换数据源
本章统一数据根路径: /home/ubuntu/r2_data_mount/data
import pandas as pd # 导入 Pandas 以读取 HDF 节点元数据并展示目录
catalog_records = [ # 逐项抄录 readme manifest 的路径、键、覆盖期与精确行数
('公司基本信息' , 'stock/stock_basic_data.h5' , '/stock_basic_info' , '静态证券截面' , 5_524 ), # 固定公司表合同
('季度财务报表' , 'stock/financial_statement.h5' , '/financial_data' , '2005Q1—2026Q1' , 306_019 ), # 固定财务表合同
('前复权日行情' , 'stock/stock_price_pre_adjusted.h5' , '/data' , '2005-01-04—2025-12-31' , 15_368_843 ), # 固定前复权合同
('后复权日行情' , 'stock/stock_price_post_adjusted.h5' , '/data' , '2005-01-04—2025-12-31' , 15_368_843 ), # 固定后复权合同
('不复权日行情' , 'stock/stock_price_no_adjust.h5' , '/data' , '2005-01-04—2025-12-31' , 15_368_843 ), # 固定原始价格合同
('季度估值因子' , 'stock/valuation_factors_quarterly_15_years.h5' , '/valuation_factors' , '2011-03-31—2025-12-31' , 207_600 ), # 固定估值表合同
('指数日行情' , 'index/indexes.h5' , '/indexes' , '2005-01-04—2026-01-30' , 22_408_975 ), # 使用实际单一指数文件路径
] # 完成七个实际 HDF 数据族的权威目录
data_catalog = pd.DataFrame(catalog_records, columns= ['数据集' , '相对路径' , 'HDF key' , '覆盖期' , '预期行数' ]) # 形成可审计目录
data_catalog # 展示与 readme manifest 同源的精确目录
表 表 1.1 给出与项目 manifest 同源的目录;代码块 列表 1.1 随后只读取 HDF 元数据,逐项验证文件、key、布局和行数。覆盖期来自同一 manifest,不因文件存在就被重新解释为可公开取得的数据。
快速预览 A 股数据
让我们先快速预览一下上市公司基本信息数据:
import pandas as pd # 导入 Pandas 用于数据读取
import hashlib # 对体量较小的公司基本表执行完整字节哈希
stock_basic_path = f' { DATA_ROOT} /stock/stock_basic_data.h5' # 拼接上市公司基本信息文件路径
stock_basic_sha256 = hashlib.sha256(Path(stock_basic_path).read_bytes()).hexdigest() # 在固定输出计算前验证 1.6 MB 快照
assert stock_basic_sha256 == '15a3be1d6bb949e4b202741ca6e4b17a8a6f2fffee6e24726f5738f5e4e162be' # 阻止基础表静默漂移
stock_basic_data = pd.read_hdf(stock_basic_path, key= '/stock_basic_info' ) # 按 manifest 的精确 HDF key 读取公司基本信息
assert stock_basic_data.shape[0 ] == 5_524 # 核验公司表与 manifest 的精确记录数一致
print (f'数据维度: { stock_basic_data. shape} ' ) # 输出数据的行数和列数
print (f'数据列名: { list (stock_basic_data.columns)} ' ) # 输出所有字段名称
数据维度: (5524, 24)
数据列名: ['order_book_id', 'industry_code', 'market_tplus', 'symbol', 'special_type', 'exchange', 'status', 'type', 'de_listed_date', 'listed_date', 'sector_code_name', 'abbrev_symbol', 'sector_code', 'round_lot', 'trading_hours', 'board_type', 'industry_name', 'issue_price', 'trading_code', 'office_address', 'province', 'purchasedate', 'citics_2019_l1_code', 'citics_2019_l1_name']
表 表 1.2 汇总本节的计算或审计结果,解释时应遵循正文给出的口径与限制。
stock_basic_data.head() # 显示前5行数据,快速了解数据结构
长三角地区上市公司概览
本教材优先使用长三角地区(上海、江苏、浙江、安徽)的上市公司作为案例。让我们看看这些地区有多少上市公司:
import pandas as pd # 导入 Pandas 用于数据处理
stock_basic_data = pd.read_hdf(f' { DATA_ROOT} /stock/stock_basic_data.h5' , key= '/stock_basic_info' ) # 按 manifest key 读取公司基本信息
required_stock_fields = {'order_book_id' , 'province' } # 声明地域统计不可缺少的 schema 字段
missing_stock_fields = required_stock_fields.difference(stock_basic_data.columns) # 计算实际 schema 的缺失字段
assert not missing_stock_fields, f'公司表缺少必要字段: { sorted (missing_stock_fields)} ' # 合同不满足时显式失败
yangtze_delta_provinces = ['上海市' , '江苏省' , '浙江省' , '安徽省' ] # 使用公司表 province 字段的真实四省市取值
assert set (yangtze_delta_provinces).issubset(set (stock_basic_data['province' ].dropna())) # 核验四个地域值确实存在
表 表 1.3 汇总本节的计算或审计结果,解释时应遵循正文给出的口径与限制。
本章小结
本章介绍了以下核心内容:
大数据分析的技术演进 :从 Hadoop/JVM 生态到具有 Python API 的现代数据栈。Pandas 主要由 C/Cython 加速,Polars 由 Rust 实现,DuckDB 由 C++ 实现,Dask 负责任务调度;“可以从 Python 调用”不等于“底层由纯 Python 执行”
开发环境搭建 :Conda 虚拟环境管理和 Jupyter 生态系统是 Python 数据分析的标准工具链
大数据分析思维 :向量化操作、惰性求值和流式处理是高效处理大规模数据的三大核心理念
代码规范 :遵循 PEP 8 标准和意图导向的命名规范,编写可读性强、可维护的分析代码
教材结构 :本书分为五篇十九章,从基础到进阶,从单机到分布式,覆盖完整的数据分析技术栈
从下一章开始,我们将正式进入 Python 编程的世界,深入学习内置数据结构、函数和文件操作。
练习题
练习 1.1 :安装 Miniconda,根据仓库 environment.yml 新建或更新本教材统一使用的 peter 环境,注册同名内核,然后在 Jupyter Notebook 中打印当前解释器路径、Python 版本、三项核心库的完整版本号和模块路径,作为内核指向正确环境的验证证据。
解答 :
# 在终端中执行以下命令:
# conda env create --file environment.yml # peter 不存在时按权威清单新建环境
# conda env update --name peter --file environment.yml --prune # peter 已存在时更新;与上一行二选一
# conda run --no-capture-output --name peter python -m ipykernel install --user --name peter --display-name peter # 注册同名内核并绑定 peter 解释器
# conda run --no-capture-output --name peter jupyter notebook # 从同一环境启动 Jupyter Notebook
# 在 notebook 中运行以下代码,核对内核实际指向的解释器和依赖版本:
import sys # 读取当前 Jupyter 内核实际使用的 Python 解释器与版本
import pandas as pd # 读取本教材表格计算核心库的版本
import numpy as np # 读取本教材数组计算核心库的版本
import matplotlib # 读取本教材绘图核心库的版本而不依赖 pyplot 状态
print (f'解释器路径: { sys. executable} ' ) # 路径应落在 peter 环境中,否则需重新选择内核
print (f'Python: { sys. version. split()[0 ]} ' ) # 报告可复核的完整 Python 语义版本号
print (f'Pandas: { pd. __version__} ' ) # 报告 Pandas 完整版本号以固定运行环境
print (f'NumPy: { np. __version__} ' ) # 报告 NumPy 完整版本号以固定数值语义
print (f'Matplotlib: { matplotlib. __version__} ' ) # 报告 Matplotlib 完整版本号以固定绘图环境
print (f'Pandas 模块路径: { pd. __file__ } ' ) # 证明 Pandas 与解释器来自同一 peter 环境
print (f'NumPy 模块路径: { np. __file__ } ' ) # 证明 NumPy 没有从用户目录或旧环境串入
print (f'Matplotlib 模块路径: { matplotlib. __file__ } ' ) # 证明绘图库与已注册内核一致
解释器路径: /home/ubuntu/miniconda3/envs/peter/bin/python
Python: 3.10.20
Pandas: 2.3.3
NumPy: 1.26.4
Matplotlib: 3.10.8
Pandas 模块路径: /home/ubuntu/miniconda3/envs/peter/lib/python3.10/site-packages/pandas/__init__.py
NumPy 模块路径: /home/ubuntu/miniconda3/envs/peter/lib/python3.10/site-packages/numpy/__init__.py
Matplotlib 模块路径: /home/ubuntu/miniconda3/envs/peter/lib/python3.10/site-packages/matplotlib/__init__.py
练习 1.2 :依次运行教师提供的两个代码块,让 Python 循环和 NumPy 向量化计算同一组 100 万个确定性数值的平方和。提交两项结果的一致性证据、本机两段耗时,并用一句话说明为何该耗时比不能推广到所有任务。评分只看运行证据与解释,不考查 NumPy API 的独立编写。
解答 :
教师脚手架(只运行核对,不评分 API)
import numpy as np # 导入 NumPy 用于生成随机数和向量化计算
import time # 导入 time 模块用于计时
numeric_values = np.linspace(- 1.0 , 1.0 , 1_000_000 ) # 构造可复现的确定性数值网格
start_time = time.time() # 记录循环开始时间
sum_of_squares_loop = 0 # 初始化平方和为0
for number in numeric_values: # 遍历每个数值
sum_of_squares_loop += number ** 2 # 累加平方值
loop_elapsed = time.time() - start_time # 计算循环耗时
print (f'循环结果: { sum_of_squares_loop:.2f} , 耗时: { loop_elapsed:.4f} 秒' ) # 输出结果和耗时
循环结果: 333334.00, 耗时: 0.2039秒
start_time = time.time() # 记录向量化开始时间
sum_of_squares_vectorized = np.sum (numeric_values ** 2 ) # 一行代码完成向量化平方和计算
vectorized_elapsed = time.time() - start_time # 计算向量化耗时
vectorized_elapsed = max (vectorized_elapsed, 1e-9 ) # 防止除以零(向量化极快时耗时可能为0)
np.testing.assert_allclose(sum_of_squares_loop, sum_of_squares_vectorized, rtol= 1e-10 ) # 先核对两种求和路径在浮点容差内一致
print (f'向量化结果: { sum_of_squares_vectorized:.2f} , 耗时: { vectorized_elapsed:.6f} 秒' ) # 输出结果和耗时
print (f'本机本次算子的耗时比: { loop_elapsed / vectorized_elapsed:.0f} 倍' ) # 只描述当前输入、硬件与平方和算子的测量结果
向量化结果: 333334.00, 耗时: 0.001032秒
本机本次算子的耗时比: 198倍
练习 1.3 :运行教师提供的 stock_basic_data.h5 读取与汇总脚手架。根据输出报告各交易所普通股数量、最早上市的 5 家公司,并指出支撑答案的四个字段。评分只看是否正确读取输出、区分普通股口径并引用字段证据,不考查 read_hdf()、布尔筛选、日期转换或 Pandas 聚合 API。
解答 :
教师脚手架(只运行核对,不评分 API)
import pandas as pd # 导入 Pandas 用于数据操作
stock_basic_data = pd.read_hdf(f' { DATA_ROOT} /stock/stock_basic_data.h5' ) # 读取上市公司基本信息
required_columns = {'exchange' , 'listed_date' , 'symbol' , 'order_book_id' } # 声明完成题目所必需的字段集合
assert required_columns.issubset(stock_basic_data.columns) # 在统计前验证本地数据合同,避免静默漏列
common_stock_df = stock_basic_data.loc[ # 仅保留普通股和题目所需字段以控制内存
stock_basic_data['type' ].eq('CS' ), # 用本地类型字段排除基金、指数等非普通股对象
['exchange' , 'listed_date' , 'symbol' , 'order_book_id' ] # 保留交易所、日期、名称与证券代码
].copy() # 创建独立结果,避免后续日期转换修改原始表
common_stock_df['listed_date' ] = pd.to_datetime( # 统一上市日期类型以支持可靠排序
common_stock_df['listed_date' ], errors= 'coerce' # 将无法解析的日期显式标为缺失,便于随后排除
) # 完成上市日期的统一类型转换
exchange_counts = ( # 构造按交易所汇总的可读结果表
common_stock_df['exchange' ] # 选择交易所分类字段
.value_counts() # 统计每个交易所的普通股公司数
.rename_axis('exchange' ) # 为分类索引赋予业务名称
.to_frame('company_count' ) # 将计数 Series 转为便于展示的 DataFrame
) # 完成交易所计数表
earliest_five = common_stock_df.dropna(subset= ['listed_date' ]).nsmallest( # 排除无日期记录后选择最早公司
5 , 'listed_date' # 按上市日期选出最早的五条有效记录
) # 完成最早上市公司结果表
print (exchange_counts) # 输出各交易所普通股公司数以回答第一问
earliest_five # 展示最早上市五家公司及其证券代码和日期以回答第二问
company_count
exchange
XSHE 3075
XSHG 2449
3
XSHE
1990-12-01
*ST国华
000004.XSHE
4
XSHE
1990-12-10
ST星源
000005.XSHE
3589
XSHG
1990-12-19
方正科技
600601.XSHG
3590
XSHG
1990-12-19
云赛智联
600602.XSHG
3639
XSHG
1990-12-19
飞乐音响
600651.XSHG
exchange_counts 给出各交易所普通股公司数;earliest_five 同时保留公司名称、证券代码和上市日期,因此完整回答了题目的两个子问。由于数据快照会更新,教材不硬编码某个计数,而要求代码在当前本地数据上重新计算。
练习 1.4 :分别为下列任务选择首要工具,并从“数据规模—算子—内存—结果物化”四个维度说明理由。任务 A 是内存可容纳的 20 万行交互式清洗;任务 B 是对多份 Parquet 做列投影、过滤和 SQL 聚合且只返回百行结果;任务 C 是单机上对可流式执行的宽表做惰性表达式计算;任务 D 是已有 Python 函数必须跨多个分区并行执行。工具候选为 Pandas、DuckDB、Polars 与 Dask。
解答 :任务 A 首选 Pandas,因为数据可放入内存、算子以交互式清洗为主且结果规模小;任务 B 首选 DuckDB,因为 SQL 聚合可直接下推到 Parquet,避免物化全部输入;任务 C 首选 Polars,因为惰性计划和可流式算子有机会控制单机内存,但仍须确认算子可流式且最终结果可容纳;任务 D 首选 Dask,因为任务需要保留 Python 函数并进行分区调度。四项选择都不是仅由行数决定:若算子不可下推、分区严重倾斜或最终结果本身过大,就必须重新设计任务并实测峰值内存。
练习 1.5 :审查语句 TotalValue=price*100。指出命名、运算符空格、常量含义和业务意图方面的问题,并改写为能让读者理解“100”来源的代码。
解答 :TotalValue 不符合 PEP 8 的 snake_case 命名,运算符两侧缺空格,裸常量 100 没有单位或业务来源,变量名也没有说明计算的是持仓市值。可改写为:
share_price_cny = 12.50 # 用人民币计价的题设每股价格,明确数值单位
position_shares = 100 # 记录题设持股数量,避免无法解释的裸常量
position_value_cny = share_price_cny * position_shares # 计算持仓市值并在名称中保留币种与业务含义
print (f'持仓市值: { position_value_cny:.2f} 元' ) # 输出可人工复核的题设计算结果