39  数据特殊结构 数组

本章定位(复习与补充训练层):本章对应正课第5章《NumPy 数组的创建与访问》(章节 5)以及第6章《NumPy 数组运算》(章节 6),并与第47章《创建N维数组》(章节 48)互为补充训练(本章为综合版)。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项;先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。本章不属于必修主线。

39.1 引言数组在数值计算中的核心地位

NumPy数组以连续内存存储同构数据、用向量化运算替代Python循环,是同时处理上千只股票收益率、进行矩阵运算与高频时间序列计算的基础工具。本章用两个平台任务检验你对数组结构与Series创建的复习效果。

39.2 本章学习目标

本章为复习与补充训练,目标是检验你是否能脱离正课独立完成下列任务:

  1. 能独立完成平台任务一:用 np.random.standard_normal((2000, 500)) 生成股票×交易日的涨跌幅矩阵,输出其 shape 与前5只股票、前5个交易日的切片 stock_day[0:5, :5]
  2. 能独立完成平台任务二:用 pd.Series(列表) 从Python列表创建Series并输出
  3. 能对二维数组执行元素访问 arr[i, j]、整行 arr[i, :]、整列 arr[:, j]、子块切片、花式索引与布尔索引 arr[arr > 50]
  4. 能解释切片返回视图、.copy() 返回副本的差异,并用 reshape().Travel() 完成形状操作
  5. 能用 mean()std()np.percentile() 等对收益率数组做描述性统计与年化换算

若上述任务无法独立完成,请回到正课第5、6章对应小节补弱,再回到本章练习。

39.3 NumPy数组基础

在动手创建数组之前,先看Python原生列表在数值计算中的局限,以及NumPy数组对应的设计哲学:

理论背景:为什么需要数组?

Python原生列表的局限性:

  1. 性能问题: 列表存储的是对象的指针,访问需要间接寻址
  2. 内存效率: 每个元素都是完整的Python对象,内存开销大
  3. 类型单一: 列表可以混合类型,但这不利于向量化计算

NumPy数组的设计哲学:

  • 连续内存: 数组元素在内存中连续存储
  • 同构数据: 所有元素类型相同
  • 向量化操作: 通过SIMD指令并行处理

正因如此,数组化的数值计算在金融中的典型用途包括:

  • 向量计算: 同时处理上千只股票收益率
  • 矩阵运算: 投资组合优化、因子模型
  • 时间序列: 高频数据处理、技术指标计算

39.3.1 数组创建方法

补充说明:数组创建的多种策略

  1. 从Python序列转换:
    • np.array([1, 2, 3]): 从列表创建
    • np.arange(10): 类似range()的数组版本
    • 适合:小规模数据或已有数据
  2. 使用NumPy生成函数:
    • np.zeros(): 全零数组
    • np.ones(): 全1数组
    • np.random.*(): 随机数生成
    • 适合:初始化、模拟数据
  3. 从文件读取:
    • np.loadtxt(): 从文本文件
    • np.genfromtxt(): 更灵活的文本读取
    • 适合:导入外部数据

任务要求:用 np.random.standard_normal((2000, 500)) 生成”2000只股票×500个交易日”的标准正态涨跌幅矩阵 stock_day,输出其 shape,并打印前5只股票、前5个交易日的切片 stock_day[0:5, :5];注意本任务不设随机种子,数值每次运行不同。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 39.1: 平台原始代码 (1)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#题目一
import numpy as np
stocks = 2000   # 2000支股票
days =  500  # 两年大约500个交易日

# 生成服从正态分布:均值期望=0,标准差=1的序列
# 注:此处未设置随机种子,每次运行结果不同;如需复现可先执行 np.random.seed(42)
stock_day = np.random.standard_normal((stocks, days))   
print(stock_day.shape)   #打印数据组结构
# 打印出前五只股票,头五个交易日的涨跌幅情况
print(stock_day[0:5, :5])

预期输出(本机实际运行结果,具体以平台运行结果为准;该任务未设置随机种子,矩阵数值每次运行都不同,仅 shape 恒为 (2000, 500)):

(2000, 500)
[[ 0.08610086 -0.27664772  0.53050924  0.73258256  1.28867658]
 [-0.49528115 -0.54012727 -0.85263685  0.6452407  -1.5809226 ]
 [ 0.0584094   0.18808653  1.82617083 -1.76896555  0.62963456]
 [ 0.26441137 -0.45708464  0.84599849 -0.79125639  0.26028481]
 [-0.80536529 -0.1733793  -0.83968245  1.0223388   0.14107777]]

代码深度解析:

  1. shape属性:
    • 返回元组 (n_rows, n_cols)
    • 表示数组的维度
    • 例如 (2000, 500) 表示 2000×500 的矩阵
  2. ndim属性:
    • 返回数组的维度数
    • 1维数组: ndim=1
    • 2维数组: ndim=2
  3. size属性:
    • 返回数组元素总数
    • 等于 shape 各维度的乘积
  4. dtype属性:
    • 表示数组元素的数据类型
    • 常见类型: int32, float64, bool, object

39.3.2 数组的索引与切片

理论背景:多维数组的索引机制

NumPy数组的索引遵循从0开始的规则,切片使用[start:stop:step]语法。

关键概念:

  • 视图(View): 切片返回原数组的视图,共享内存
  • 副本(Copy): 使用.copy()创建独立副本
  • 广播(Broadcasting): 不同形状数组的运算规则
列表 39.2: 数组索引与切片操作
# =============================================================================
# 题目:NumPy数组的索引与切片操作
# =============================================================================
# 本任务演示数组的索引、切片、花式索引和布尔索引
# 在金融数据分析中,索引用于提取特定时间段的数据、筛选满足条件的股票等

# ==================== 创建一维数组用于演示 ====================
# np.array():从列表创建一维数组
# 数组内容:10, 20, 30, ..., 100(10个等差数列)
# 金融应用:可以代表10个交易日的收盘价、10只股票的收益率等
arr_1d = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])

# ==================== 一维数组的基本索引 ====================
print('一维数组索引:')  # 打印标题
# arr_1d[0]:正索引,从0开始,访问第一个元素
# 返回值:10
# 金融应用:获取第一天的股价、第一个月的收益率等
print(f'  arr_1d[0]: {arr_1d[0]}')  # 打印第一个元素

# arr_1d[-1]:负索引,-1表示最后一个元素
# 返回值:100
# 金融应用:获取最新的股价、最近一天的数据
print(f'  arr_1d[-1]: {arr_1d[-1]}')  # 打印最后一个元素

# arr_1d[2:7]:切片操作,语法为[start:stop],start包含,stop不包含
# 2:7表示索引2到6(不包括7)
# 返回值:[30, 40, 50, 60, 70]
# 金融应用:提取特定时间段的数据,如第3天到第7天的股价
print(f'  arr_1d[2:7]: {arr_1d[2:7]}')  # 打印切片(索引2到6)

# arr_1d[::2]:切片操作,语法为[start:stop:step]
# ::2表示从开头到结尾,步长为2(每隔一个取一个)
# 返回值:[10, 30, 50, 70, 90](取奇数位置的元素)
# 金融应用:提取隔日数据、周频数据(从日频数据中提取)
print(f'  arr_1d[::2]: {arr_1d[::2]}')  # 打印步长为2的切片

# ==================== 创建二维数组用于演示 ====================
# 创建3×4的二维数组(3行4列)
# 金融应用:可以代表3只股票在4个交易日的收盘价矩阵
arr_2d = np.array([
    [1, 2, 3, 4],      # 第1行
    [5, 6, 7, 8],      # 第2行
    [9, 10, 11, 12]    # 第3行
])

# ==================== 二维数组的索引 ====================
print('\n二维数组索引:')  # 打印标题
# arr_2d[0, 0]:二维数组索引,语法为[row, col]
# 0, 0表示第0行第0列(第一行第一列)
# 返回值:1
# 金融应用:获取特定股票在特定日期的收盘价
print(f'  arr_2d[0, 0]: {arr_2d[0, 0]}')  # 打印第0行第0列

# arr_2d[1, :]:冒号:表示选择该维度的所有元素
# 1, :表示第1行的所有列(整行)
# 返回值:[5, 6, 7, 8]
# 金融应用:获取某只股票在所有日期的价格序列
print(f'  arr_2d[1, :]: {arr_2d[1, :]}')  # 打印第1行所有列

# arr_2d[:, 2]::表示所有行,2表示第2列
# :, 2表示所有行的第2列(整列)
# 返回值:[3, 7, 11]
# 金融应用:获取所有股票在某一天的价格
print(f'  arr_2d[:, 2]: {arr_2d[:, 2]}')  # 打印所有行第2列

# arr_2d[0:2, 1:3]:切片两个维度
# 0:2表示第0-1行,1:3表示第1-2列
# 返回值:2×2的子矩阵 [[2, 3], [6, 7]]
# 金融应用:提取特定时间范围和特定股票的数据子集
print(f'  arr_2d[0:2, 1:3]:\n{arr_2d[0:2, 1:3]}')  # 打印子矩阵

# ==================== 花式索引(Fancy Indexing) ====================
# 花式索引:使用整数数组作为索引,可以不连续地访问元素
# indices:索引数组,指定要访问的位置 [0, 2, 4]
indices = [0, 2, 4]  # 定义索引列表
# arr_1d[indices]:使用索引数组,提取第0、2、4个元素
# 返回值:[10, 30, 50]
# 金融应用:提取特定日期的数据,如季末交易日、财报发布日
print(f'\n花式索引 arr_1d[{indices}]: {arr_1d[indices]}')  # 打印花式索引结果

# ==================== 布尔索引 ====================
# 布尔索引:使用布尔数组作为索引,提取满足条件的元素
# arr_1d > 50:比较操作,返回布尔数组 [False, False, False, False, False, True, True, True, True, True]
# True表示对应位置的元素>50,False表示≤50
bool_idx = arr_1d > 50  # 生成布尔数组
# arr_1d[bool_idx]:使用布尔数组索引,只保留True位置的元素
# 返回值:[60, 70, 80, 90, 100]
# 金融应用:筛选上涨的股票、收益率为正的交易日、满足特定条件的股票
print(f'布尔索引 arr_1d[arr_1d > 50]: {arr_1d[bool_idx]}')  # 打印大于50的元素

# ==================== 视图(View) vs 副本(Copy) ====================
# 这是NumPy的重要概念:切片返回的是视图还是副本?
# 视图:共享原数组的内存,修改视图会影响原数组
# 副本:独立拷贝,修改副本不影响原数组

# arr_1d[2:5]:切片操作,返回视图(View)
# slice_view是arr_1d的视图,指向索引2-4的元素
slice_view = arr_1d[2:5]  # 创建切片视图

# arr_1d[2:5].copy():显式创建副本(Copy)
# .copy()方法创建独立副本,深拷贝数据
slice_copy = arr_1d[2:5].copy()  # 创建切片副本

# 修改视图的第0个元素(原数组索引2的元素)
slice_view[0] = 999  # 视图修改会影响原数组
# 修改副本的第1个元素(不影响原数组)
slice_copy[1] = 888  # 副本修改不影响原数组

print(f'\n修改视图后原数组: {arr_1d}')  # 视图修改会影响原数组(索引2变为999)
print(f'修改副本后原数组: {arr_1d}')  # 副本修改不影响原数组(索引3仍为40)

代码深度解析:

  1. 切片语法: [start:stop:step]
    • start: 起始索引(包含)
    • stop: 结束索引(不包含)
    • step: 步长
    • 省略时使用默认值
  2. 花式索引:
    • 使用整数数组作为索引
    • 返回新数组的副本
    • 不连续访问的高效方法
  3. 布尔索引:
    • 使用布尔数组作为索引
    • 常用于条件过滤
    • 类似于SQL的WHERE子句

39.4 数组的运算

39.4.1 向量化运算

理论背景:向量化的数学原理

向量化(Vectorization)是指用数组表达式代替显式循环,利用底层C/Fortran实现的SIMD(单指令多数据)指令并行处理。

性能优势:

  • 避免Python循环: 减少解释器开销
  • 内存连续: 利用CPU缓存
  • 并行计算: SIMD指令一次处理多个数据

性能对比示例:

# Python列表: 1000万元素求和 ≈ 150ms
# NumPy数组: 1000万元素求和 ≈ 5ms
# 性能提升: 30倍

Amdahl定律:

\[ S(N) = \frac{1}{(1-p) + \frac{p}{N}} \]

其中:

  • \(S(N)\): 加速比
  • \(p\): 可并行部分比例
  • \(N\): 处理器数量
列表 39.3: 数组向量化运算与性能对比
# =============================================================================
# 题目:向量化运算的性能优势演示
# =============================================================================
# 本任务对比Python循环和NumPy向量化运算的性能差异
# 在金融分析中,向量化运算可以大幅提升计算速度,特别是处理大规模数据时

# ==================== 导入必要的库 ====================
import numpy as np  # NumPy数值计算库
import time  # time模块,用于计时

# ==================== 创建大规模测试数组 ====================
# 定义数组大小:1000万元素
# 金融应用:模拟1000万条交易记录、1000万个价格数据点
n = 10_000_000  # 使用下划线分隔数字,提高可读性(Python 3.6+特性)

# np.random.seed(42):固定随机种子,确保每次运行生成相同的随机数(可复现)
np.random.seed(42)  # 固定随机种子

# np.random.randn(n):生成n个标准正态分布随机数
# arr1和arr2各包含1000万个随机数
# 金融应用:模拟两只股票的1000万个收益率数据
arr1 = np.random.randn(n)  # 生成第一个随机数组
arr2 = np.random.randn(n)  # 生成第二个随机数组

# ==================== 方法1:使用Python循环逐元素相加 ====================
print('方法1:Python循环(慢)')  # 打印方法说明
# time.time():返回当前时间戳(秒数,浮点数)
# 记录开始时间
start = time.time()

# for循环:Python原生循环,逐元素访问
# range(n):生成0到n-1的整数序列
# result_loop.append():将结果追加到列表末尾
# 性能问题:Python循环开销大,每次迭代都有类型检查、函数调用等开销
result_loop = []  # 初始化结果列表
for i in range(n):  # 循环n次(1000万次!)
    result_loop.append(arr1[i] + arr2[i])  # 访问第i个元素并相加

# 计算耗时:结束时间 - 开始时间
time_loop = time.time() - start

# ==================== 方法2:使用NumPy向量化运算 ====================
print('方法2:NumPy向量化(快)')  # 打印方法说明
# 记录开始时间
start = time.time()

# arr1 + arr2:NumPy向量化运算,使用加法运算符
# 这一行代码等价于上面的整个for循环
# 底层实现:C/Fortran优化的SIMD指令,并行处理多个数据
# 性能优势:避免Python解释器开销,利用CPU缓存和并行指令
result_vectorized = arr1 + arr2  # 向量化加法,一次性完成所有元素的加法

# 计算耗时:结束时间 - 开始时间
time_vectorized = time.time() - start

# ==================== 性能对比报告 ====================
print(f'数组大小: {n:,} 元素')  # f-string格式化,:n添加千位分隔符
print(f'Python循环时间: {time_loop:.4f}秒')  # 保留4位小数
print(f'向量化时间: {time_vectorized:.4f}秒')  # 保留4位小数
print(f'性能提升: {time_loop / time_vectorized:.1f}倍')  # 计算加速比

# ==================== 验证结果一致性 ====================
# np.allclose(a, b):检查两个数组的元素是否在容差范围内相等
# 容差:默认相对容差1e-5,绝对容差1e-8
# 返回值:True表示结果一致,False表示不一致
print(f'\n结果一致: {np.allclose(result_loop, result_vectorized)}')
# 预期输出:True,说明两种方法得到相同结果(浮点数可能有微小差异)

金融应用: 计算投资组合日收益率

列表 39.4: 向量化计算投资组合收益率
# =============================================================================
# 题目:向量化计算投资组合的日收益率和风险指标
# =============================================================================
# 本任务演示如何使用向量化运算计算投资组合的收益率、波动率和夏普比率
# 金融应用:现代投资组合理论(MPT),评估投资组合的风险调整后收益

# ==================== 定义投资组合参数 ====================
# n_stocks:投资组合中的股票数量
# 金融应用:A股约有5000只股票,一个分散化的组合通常包含50-200只股票
n_stocks = 100  # 100只股票的组合

# n_days:交易日数量
# A股每年约252个交易日(扣除周末和节假日)
n_days = 252  # 1年的交易日

# ==================== 生成随机权重并归一化 ====================
# np.random.seed(42):设置随机种子,确保结果可复现
# 金融应用:调试模型时需要固定随机数,以便对比不同算法
np.random.seed(42)  # 固定随机种子(在生成权重之前设置)

# np.random.random(n):生成n个[0, 1)均匀分布的随机数
# 返回值:一维数组,长度为n_stocks
# 金融应用:随机生成初始权重,然后进行优化
weights = np.random.random(n_stocks)  # 生成100个随机权重(0-1之间)

# weights / weights.sum():归一化操作,确保权重和为1
# weights.sum():计算权重总和
# 除法:每个权重除以总和,确保权重和为1(100%投资)
# 金融应用:投资组合权重必须满足预算约束(全部资金投资)
weights = weights / weights.sum()  # 归一化权重,使总和为1

# ==================== 生成股票收益率矩阵 ====================
# np.random.randn(n_stocks, n_days):生成标准正态分布的随机矩阵
# 返回值:n_stocks×n_days的矩阵
# * 0.02:乘以0.02(2%),将标准差调整为2%(典型的日收益率波动率)
# 金融应用:模拟100只股票在252个交易日的日收益率
returns = np.random.randn(n_stocks, n_days) * 0.02  # 日收益率矩阵

# ==================== 向量化计算投资组合收益率 ====================
# weights @ returns:矩阵乘法运算符
# weights形状:(100,),一维数组
# returns形状:(100, 252),二维矩阵
# 运算规则:weights自动广播为(1, 100),然后执行矩阵乘法
# 返回值:(252,),一维数组,表示252天的投资组合收益率
# 金融应用:计算投资组合的每日收益率 = Σ(权重i × 股票i的收益率)
portfolio_returns = weights @ returns  # 矩阵乘法,计算组合收益率

# ==================== 计算投资组合统计指标 ====================
print('投资组合统计:')  # 打印标题

# 计算年化收益率
# portfolio_returns.mean():计算日收益率均值(日平均收益率)
# * 252:年化,将日收益率转换为年收益率(假设252个交易日)
# 金融应用:评估投资组合的年度表现
print(f'年化收益率: {portfolio_returns.mean() * 252:.4f}')

# 计算年化波动率
# portfolio_returns.std():计算日收益率标准差(日波动率)
# np.sqrt(252):根号252,用于年化波动率
# 金融公式:年化波动率 = 日波动率 × √252(假设收益率不相关)
# 金融应用:衡量投资组合的风险水平
print(f'年化波动率: {portfolio_returns.std() * np.sqrt(252):.4f}')

# 计算夏普比率(Sharpe Ratio)
# 夏普比率 = (年化收益率) / (年化波动率)
# 简化版:这里假设无风险利率为0
# 标准公式:SR = (Rp - Rf) / σp,其中Rp是组合收益率,Rf是无风险利率
# 金融应用:衡量单位风险的超额收益,评估风险调整后收益
print(f'夏普比率: {(portfolio_returns.mean() * 252) / (portfolio_returns.std() * np.sqrt(252)):.4f}')

# ==================== 计算累计收益率 ====================
# np.cumprod(1 + portfolio_returns):计算累计乘积
# 1 + portfolio_returns:将收益率转换为增长因子(如收益率0.02转换为1.02)
# .cumprod():累计连乘,计算每天的累计收益
# 例如:[1.02, 0.98, 1.03] -> [1.02, 1.02×0.98, 1.02×0.98×1.03]
# - 1:减去1,转换为收益率形式
# 金融应用:跟踪投资组合的累计收益曲线,评估长期表现
cum_returns = np.cumprod(1 + portfolio_returns) - 1  # 计算累计收益率
print(f'\n年累计收益率: {cum_returns[-1]:.4f}')  # 打印最后一天的累计收益率

代码深度解析:

  1. @运算符:
    • 矩阵乘法运算符
    • 等价于 np.matmul()
    • 金融应用:权重×收益率
  2. np.random.seed():
    • 设置随机种子
    • 确保结果可复现
    • 调试时必不可少
  3. 广播机制:
    • 不同形状数组运算时的自动对齐
    • 例如: (100,) @ (100, 252) = (252,)
    • NumPy自动扩展维度

39.5 Pandas Series与NumPy数组

39.5.1 Series的创建与操作

理论背景:Pandas的设计理念

Pandas建立在NumPy之上,提供了两个核心数据结构:

  1. Series: 带标签的一维数组
  2. DataFrame: 带标签的二维表格

Series vs NumPy数组:

特性 NumPy数组 Pandas Series
索引 整数位置(0,1,2…) 自定义标签(日期、股票代码等)
缺失值 整型数组无法存NaN,缺失需以float NaN表示 统一以NaN/pd.NA语义处理缺失
数据对齐 手动对齐 自动对齐(根据索引)
功能 纯数值计算 数据分析、统计、可视化
性能 极快 较慢(但有优化)
列表 39.5: 从列表创建Series与索引操作
# =============================================================================
# 题目:从Python列表创建Pandas Series并进行基本操作
# =============================================================================
# 本任务演示Series的创建、索引操作和统计函数
# 金融应用:Series常用于存储时间序列数据,如股票价格、收益率序列

# ==================== 导入必要的库 ====================
import pandas as pd  # Pandas数据分析库
import numpy as np  # NumPy数值计算库

# ==================== 方法1:从列表创建Series ====================
# 定义一个Python列表,包含6个整数
# 金融应用:可以是6天的股票成交量、6个月的销售额等
lst = [1, 3, 5, 6, 10, 23]  # 创建Python列表

# pd.Series(lst):从列表创建Series
# Series:带标签的一维数组,是Pandas的核心数据结构之一
# 返回值:Series对象,自动生成默认索引(0, 1, 2, ...)
# 金融应用:存储一维时间序列数据,如每日收盘价
s1 = pd.Series(lst)  # 从列表创建Series

# ==================== 显示Series及其属性 ====================
print('Series:')  # 打印标题
print(s1)  # 打印完整Series,左侧是索引,右侧是值

# s1.index:查看Series的索引对象
# 返回值:RangeIndex(start=0, stop=6, step=1),默认整数索引
print(f'\n索引: {s1.index}')  # 打印索引

# s1.values:查看Series的值(NumPy数组)
# 返回值:ndarray,包含所有数据值
# 金融应用:提取数值数组用于NumPy计算
print(f'值: {s1.values}')  # 打印值数组

# s1.dtype:查看Series的数据类型
# 返回值:dtype,如int64、float64、object等
print(f'类型: {s1.dtype}')  # 打印数据类型

# ==================== 方法2:创建带自定义索引的Series ====================
# pd.date_range():生成日期索引
# '2023-01-01':起始日期
# periods=6:生成6个日期
# freq='D':频率,D表示日(Daily)
# 返回值:DatetimeIndex对象
# 金融应用:生成交易日历、财报发布日期等
dates = pd.date_range('2023-01-01', periods=6, freq='D')  # 生成6个连续日期

# pd.Series(data, index=...):创建带自定义索引的Series
# 第一个参数:数据,6个浮点数(股票价格)
# index=dates:指定索引为日期
# 金融应用:存储股票的每日收盘价,索引为交易日期
prices = pd.Series([100.5, 102.3, 101.8, 103.2, 104.5, 103.9], index=dates)

print('\n带日期索引的Series:')  # 打印标题
print(prices)  # 打印Series,左侧显示日期索引

# ==================== 按日期切片 ====================
# prices['2023-01-02':'2023-01-04']:使用日期字符串切片
# '2023-01-02':起始日期(包含)
# '2023-01-04':结束日期(包含,Series切片与列表不同)
# 返回值:子Series,包含3个数据点
# 金融应用:提取特定时间段的股价数据,分析某一周的表现
print(f'\n按日期切片:')  # 打印标题
print(prices['2023-01-02':'2023-01-04'])  # 打印1月2日到1月4日的股价

# ==================== 统计函数 ====================
print(f'\n统计指标:')  # 打印标题

# prices.mean():计算均值(平均价格)
# 返回值:浮点数,所有值的算术平均
# 金融应用:计算股票的平均价格,评估价格水平
print(f'  均值: {prices.mean():.2f}')  # 打印均值,保留2位小数

# prices.std():计算标准差(价格波动率)
# 返回值:浮点数,标准差衡量价格离散程度
# 金融应用:衡量股价波动性,标准差越大波动越大
print(f'  标准差: {prices.std():.2f}')  # 打印标准差

# prices.max():计算最大值(最高价)
# 返回值:浮点数,所有值中的最大值
# 金融应用:找出期间内的最高股价
print(f'  最大值: {prices.max():.2f}')  # 打印最大值

# prices.min():计算最小值(最低价)
# 返回值:浮点数,所有值中的最小值
# 金融应用:找出期间内的最低股价
print(f'  最小值: {prices.min():.2f}')  # 打印最小值

39.5.2 Series与NumPy互操作

任务要求:给定列表 [1, 3, 5, 6, 10, 23],用 pd.Series(列表) 创建序列 s1 并输出;观察Series”左侧默认整数索引、右侧取值”的成对显示格式与底部的 dtype 标注。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 39.6: 平台原始代码 (2)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#题目二
import pandas as pd

#(1)通过列表创建[1,3,5,6,10,23]的Series
lst = [1,3,5,6,10,23]
s1 = pd.Series(lst)  # 创建Series序列s1
print(s1)  # 输出(1)通过列表创建[1,3,5,6,10,23]的Serie

预期输出(本机实际运行结果,具体以平台运行结果为准):

0     1
1     3
2     5
3     6
4    10
5    23
dtype: int64

代码深度解析:

  1. .values属性:
    • 返回Series的NumPy数组表示
    • 丢失索引信息
    • 用于性能关键的数值计算
  2. 自动对齐:
    • Pandas的强大功能
    • 根据索引自动匹配
    • 不匹配位置填充NaN
  3. 索引选择:
    • 位置索引: .iloc[]
    • 标签索引: .loc[]
    • 布尔索引: []内放布尔条件

39.6 数组的统计分析

39.6.1 描述性统计

金融理论:收益率的统计特征

现代投资组合理论假设资产收益率服从正态分布,但实证研究发现:

  1. 尖峰厚尾: 极端收益概率高于正态分布
  2. 偏度: 负偏(下跌更多)或正偏(上涨更多)
  3. 波动聚集: 高波动期后跟随高波动期
列表 39.7: 数组的统计分析
# =============================================================================
# 题目:NumPy数组的描述性统计和分布形状分析
# =============================================================================
# 本任务演示如何计算数组的统计指标,包括均值、标准差、分位数、偏度、峰度
# 金融应用:分析股票收益率的统计特征,评估投资风险和收益分布

# ==================== 导入必要的库 ====================
import numpy as np  # NumPy数值计算库
from scipy.stats import skew, kurtosis  # 从SciPy导入偏度和峰度函数

# ==================== 模拟股票收益率数据 ====================
# np.random.seed(42):设置随机种子,确保结果可复现
np.random.seed(42)  # 固定随机种子

# np.random.randn(252):生成252个标准正态分布随机数
# 252:A股每年的交易日数量
# * 0.02:乘以0.02(2%),将标准差调整为2%(典型的日收益率波动率)
# 返回值:一维数组,包含252个日收益率
# 金融应用:模拟某只股票一年的日收益率序列
returns = np.random.randn(252) * 0.02  # 年化日收益率

# ==================== 计算描述性统计指标 ====================
print('收益率描述统计:')  # 打印标题

# returns.mean():计算均值(平均收益率)
# 返回值:浮点数,所有收益率的算术平均
# 金融应用:计算股票的平均日收益率,评估收益水平
print(f'  均值: {returns.mean():.6f}')  # 打印均值,保留6位小数

# returns.std():计算标准差(收益率波动率)
# 返回值:浮点数,标准差衡量收益率离散程度
# 金融应用:衡量股价波动性,标准差越大风险越大
print(f'  标准差: {returns.std():.6f}')  # 打印标准差

# returns.min():计算最小值(最大单日亏损)
# 返回值:浮点数,所有收益率中的最小值
# 金融应用:找出期间内最大单日跌幅
print(f'  最小值: {returns.min():.6f}')  # 打印最小值

# returns.max():计算最大值(最大单日收益)
# 返回值:浮点数,所有收益率中的最大值
# 金融应用:找出期间内最大单日涨幅
print(f'  最大值: {returns.max():.6f}')  # 打印最大值

# np.median(returns):计算中位数(50%分位数)
# 返回值:浮点数,排序后位于中间的值
# 金融应用:中位数比均值更稳健,不受极端值影响
print(f'  中位数: {np.median(returns):.6f}')  # 打印中位数

# ==================== 计算分位数 ====================
print(f'\n分位数:')  # 打印标题

# np.percentile(returns, 25):计算25%分位数(第一四分位数Q1)
# 参数25:百分位数,25%的数据小于此值
# 返回值:浮点数,25%分位数
# 金融应用:了解收益率的下四分位数,评估较差表现
print(f'  25%分位数(Q1): {np.percentile(returns, 25):.6f}')  # 打印Q1

# np.percentile(returns, 50):计算50%分位数(中位数)
# 参数50:百分位数,50%的数据小于此值
# 返回值:浮点数,中位数(与np.median()相同)
print(f'  50%分位数(中位数): {np.percentile(returns, 50):.6f}')  # 打印中位数

# np.percentile(returns, 75):计算75%分位数(第三四分位数Q3)
# 参数75:百分位数,75%的数据小于此值
# 返回值:浮点数,75%分位数
# 金融应用:了解收益率的上四分位数,评估较好表现
print(f'  75%分位数(Q3): {np.percentile(returns, 75):.6f}')  # 打印Q3

# ==================== 计算分布形状指标 ====================
print(f'\n分布形状:')  # 打印标题

# skew(returns):计算偏度(Skewness)
# 偏度:衡量分布的不对称性
# skew < 0:左偏(长尾在左侧,负向极端值更多)
# skew > 0:右偏(长尾在右侧,正向极端值更多)
# skew = 0:对称(如正态分布)
# 金融应用:股票收益率通常呈现负偏(暴跌更多)
print(f'  偏度: {skew(returns):.4f}')  # 打印偏度

# kurtosis(returns):计算峰度(Kurtosis)
# 峰度:衡量分布的尖峰程度(超峰度,Excess Kurtosis)
# kurtosis < 0:平峰(比正态分布更平坦)
# kurtosis > 0:尖峰(比正态分布更尖,极端值更多)
# kurtosis = 0:与正态分布相同
# 金融应用:股票收益率通常呈现正峰度(极端收益比正态分布预测的更多)
print(f'  峰度: {kurtosis(returns):.4f}')  # 打印峰度

# ==================== 计算年化指标 ====================
# 年化收益率:日均值 × 252
annual_return = returns.mean() * 252  # 计算年化收益率

# 年化波动率:日标准差 × √252
# 公式:σ年化 = σ日 × √252(假设收益率不相关)
# np.sqrt(252):根号252
annual_vol = returns.std() * np.sqrt(252)  # 计算年化波动率

# 夏普比率(简化版):年化收益率 / 年化波动率
# 标准公式:SR = (Rp - Rf) / σp,这里假设无风险利率Rf=0
# 金融应用:衡量单位风险的超额收益,评估风险调整后收益
sharpe_ratio = annual_return / annual_vol  # 计算夏普比率

print(f'\n年化指标:')  # 打印标题
print(f'  年化收益率: {annual_return:.4f} ({annual_return:.2%})')  # 打印年化收益率(小数和百分数)
print(f'  年化波动率: {annual_vol:.4f} ({annual_vol:.2%})')  # 打印年化波动率(小数和百分数)
print(f'  夏普比率: {sharpe_ratio:.4f}')  # 打印夏普比率

代码深度解析:

  1. 偏度(Skewness):
    • 衡量分布的不对称性
    • skew < 0: 左偏(长尾在左侧)
    • skew > 0: 右偏(长尾在右侧)
  2. 峰度(Kurtosis):
    • 衡量分布的尖峰程度
    • 正态分布的峰度=0(超峰度)
    • kurtosis < 0: 平峰(比正态分布更平坦)
    • kurtosis > 0: 尖峰(比正态分布更尖)
  3. 夏普比率:
    • 衡量风险调整后收益
    • SR = (收益率 - 无风险利率) / 波动率
    • 这里简化为收益率/波动率

39.6.2 百分位数与分箱

金融应用:业绩排名与分档

列表 39.8: 百分位数与分箱分析
# =============================================================================
# 题目:使用百分位数和分箱分析基金经理业绩排名
# =============================================================================
# 本任务演示如何使用百分位数进行排名,以及分箱操作将数据分组
# 金融应用:基金业绩评估、股票分级、投资者分层分析

# ==================== 导入必要的库 ====================
import numpy as np  # NumPy数值计算库

# ==================== 模拟基金经理业绩数据 ====================
# np.random.seed(42):设置随机种子,确保结果可复现
np.random.seed(42)  # 固定随机种子

# np.random.randn(100):生成100个标准正态分布随机数
# * 0.15:乘以0.15(15%),标准差调整为15%(基金年化收益率的典型波动)
# + 0.08:加上0.08(8%),均值调整为8%(市场平均收益率)
# 返回值:一维数组,包含100只基金的年化收益率
# 金融应用:模拟100只公募基金的年化收益率
fund_returns = np.random.randn(100) * 0.15 + 0.08  # 100只基金的年化收益率

# ==================== 计算业绩分位数 ====================
print('基金业绩分位数:')  # 打印标题

# np.percentile(fund_returns, 50):计算50%分位数(中位数)
# 参数50:百分位数,50%的基金收益率低于此值
# 返回值:浮点数,中位数收益率
# 金融应用:了解基金行业中位数水平
print(f'  中位数(50%): {np.percentile(fund_returns, 50):.4f}')  # 打印中位数

# np.percentile(fund_returns, 75):计算75%分位数(前25%的门槛)
# 参数75:百分位数,75%的基金收益率低于此值(即前25%的最低门槛)
# 返回值:浮点数,75%分位数
# 金融应用:确定"优秀"基金的门槛(前25%)
print(f'  前25%(75%): {np.percentile(fund_returns, 75):.4f}')  # 打印75%分位数

# np.percentile(fund_returns, 90):计算90%分位数(前10%的门槛)
# 参数90:百分位数,90%的基金收益率低于此值(即前10%的最低门槛)
# 返回值:浮点数,90%分位数
# 金融应用:确定"顶尖"基金的门槛(前10%)
print(f'  前10%(90%): {np.percentile(fund_returns, 90):.4f}')  # 打印90%分位数

# np.percentile(fund_returns, 99):计算99%分位数(前1%的门槛)
# 参数99:百分位数,99%的基金收益率低于此值(即前1%的最低门槛)
# 返回值:浮点数,99%分位数
# 金融应用:确定"明星"基金的门槛(前1%)
print(f'  前1%(99%): {np.percentile(fund_returns, 99):.4f}')  # 打印99%分位数

# ==================== 分箱(Binning)操作 ====================
# 分箱:将连续数值分组到离散的箱子(bins)中
# 金融应用:将基金按业绩分为5档(1星到5星)

# 定义分位数阈值
# bins:要计算的分位数列表
# [0, 0.25, 0.5, 0.75, 1.0]:0%(最小值)、25%、50%、75%、100%(最大值)
bins = [0, 0.25, 0.5, 0.75, 1.0]  # 定义分位数列表

# np.quantile(fund_returns, bins):计算指定分位数的值
# 参数bins:分位数列表
# 返回值:一维数组,包含各分位数对应的收益率值
quantiles = np.quantile(fund_returns, bins)  # 计算分位数阈值

print(f'\n业绩分档阈值:')  # 打印标题
# enumerate(quantiles):遍历分位数数组,同时获取索引和值
# for i, q:i是索引(0-4),q是对应的分位数值
for i, q in enumerate(quantiles):  # 遍历分位数
    # i*25:将索引转换为百分比(0, 25, 50, 75, 100)
    print(f'  {i*25}%分位: {q:.4f}')  # 打印各分位数的阈值

# ==================== 使用digitize进行分箱 ====================
# np.digitize(fund_returns, quantiles):将每个数据点分配到对应的箱子
# fund_returns:要分箱的数据
# quantiles:箱子的边界值
# 返回值:整数数组,digitize的箱子编号为1..len(bins)(本例5个边界对应1..5)
# 注意:减1后箱子编号变为0..4,与下方循环的range(5)一致
ranks = np.digitize(fund_returns, quantiles) - 1  # 将基金分配到各档(0..4档)

print(f'\n基金数量分布:')  # 打印标题
# range(5):生成0-4的序列(5个箱子)
for i in range(5):  # 遍历5个箱子
    # ranks == i:布尔数组,True表示该基金在第i档
    # .sum():统计True的数量(即第i档的基金数量)
    count = (ranks == i).sum()  # 计算第i档的基金数量
    print(f'  第{i+1}档: {count}只基金')  # 打印各档的基金数量

39.7 数组的重塑与转置

39.7.1 形状操作

理论背景:数组重塑的内存视图

重塑(Reshaping)操作通常会返回视图(View)而非副本(Copy),这意味着修改重塑后的数组会影响原数组。

重要概念:

  • C顺序: 行优先(默认,C语言风格)
  • F顺序: 列优先(Fortran风格)
  • 内存连续: flags['C']flags['F']
列表 39.9: 数组的重塑与转置
# =============================================================================
# 题目:NumPy数组的重塑、转置和展平操作
# =============================================================================
# 本任务演示如何改变数组的形状(维度),以及转置和展平操作
# 金融应用:数据预处理、矩阵运算、数据格式转换

# ==================== 导入必要的库 ====================
import numpy as np  # NumPy数值计算库

# ==================== 创建一维数组用于演示 ====================
# np.arange(1, 13):生成1到12的整数序列(不包含13)
# 返回值:一维数组,[1, 2, 3, ..., 12]
# 金融应用:创建12个月的数据序列
arr = np.arange(1, 13)  # 创建1-12的数组

print('原始数组(1维):')  # 打印标题
print(arr)  # 打印一维数组

# ==================== 重塑为3×4矩阵 ====================
# arr.reshape(3, 4):将一维数组重塑为3行4列的二维矩阵
# 参数3:行数
# 参数4:列数
# 注意:3×4=12,元素总数必须匹配(12个元素)
# 返回值:二维数组,形状为(3, 4)
# 金融应用:将月度数据重塑为季度×月度的矩阵
matrix_3x4 = arr.reshape(3, 4)  # 重塑为3×4矩阵

print('\n重塑为3×4矩阵:')  # 打印标题
print(matrix_3x4)  # 打印3×4矩阵

# ==================== 重塑为4×3矩阵 ====================
# arr.reshape(4, 3):将一维数组重塑为4行3列的二维矩阵
# 参数4:行数
# 参数3:列数
# 注意:4×3=12,元素总数必须匹配
# 返回值:二维数组,形状为(4, 3)
matrix_4x3 = arr.reshape(4, 3)  # 重塑为4×3矩阵

print('\n重塑为4×3矩阵:')  # 打印标题
print(matrix_4x3)  # 打印4×3矩阵

# ==================== 重塑为2×6矩阵 ====================
# arr.reshape(2, 6):将一维数组重塑为2行6列的二维矩阵
# 参数2:行数
# 参数6:列数
# 注意:2×6=12,元素总数必须匹配
# 返回值:二维数组,形状为(2, 6)
matrix_2x6 = arr.reshape(2, 6)  # 重塑为2×6矩阵

print('\n重塑为2×6矩阵:')  # 打印标题
print(matrix_2x6)  # 打印2×6矩阵

# ==================== 矩阵转置 ====================
# matrix_4x3.T:矩阵转置运算符,.T是转置属性
# 转置:行变列,列变行
# 原形状:(4, 3) -> 转置后形状:(3, 4)
# 返回值:转置后的矩阵
# 金融应用:收益率矩阵转置,从"日期×股票"转为"股票×日期"
print('\n4×3矩阵的转置:')  # 打印标题
print(matrix_4x3.T)  # 打印转置后的矩阵(3×4)

# ==================== 展平操作 ====================
print('\n展平为一维数组:')  # 打印标题

# matrix_3x4.ravel():将多维数组展平为一维数组
# 返回:视图(View),共享原数组的内存
# 修改返回值会影响原数组
# 性能优势:不复制数据,速度快,内存效率高
print(matrix_3x4.ravel())  # 展平并返回视图

# matrix_3x4.flatten():将多维数组展平为一维数组
# 返回:副本(Copy),独立的新数组
# 修改返回值不会影响原数组
# 性能考虑:需要复制数据,占用额外内存
print(matrix_3x4.flatten())  # 展平并返回副本

# ==================== 多维数组展平 ====================
# np.arange(24):生成0-23的整数序列(24个元素)
# .reshape(2, 3, 4):重塑为3维数组
# 2:第一个维度(如2个样本)
# 3:第二个维度(如3个时间点)
# 4:第三个维度(如4个特征)
# 返回值:3维数组,形状为(2, 3, 4)
# 金融应用:2只股票,3个时间点,4个财务指标的数据立方
arr_3d = np.arange(24).reshape(2, 3, 4)  # 创建3维数组

print(f'\n3维数组形状: {arr_3d.shape}')  # 打印3维数组的形状
print(f'展平后形状: {arr_3d.ravel().shape}')  # 打印展平后的形状(一维,24个元素)

金融应用: 收益率矩阵的宽转长

列表 39.10: 收益率矩阵的宽长转换(转置)
# =============================================================================
# 题目:使用DataFrame转置实现宽格式与长格式的转换
# =============================================================================
# 本任务演示如何通过转置操作在宽格式和长格式之间转换数据
# 金融应用:数据可视化、统计分析、机器学习特征工程

# ==================== 导入必要的库 ====================
import pandas as pd  # Pandas数据分析库
import numpy as np  # NumPy数值计算库

# ==================== 创建宽格式数据 ====================
# 宽格式:行=日期,列=股票,每个单元格是收益率
# 金融应用:这是常见的数据存储格式,方便查看和比较

# np.random.seed(42):固定随机种子,确保收益率矩阵可复现
np.random.seed(42)  # 固定随机种子

# np.random.randn(5, 3):生成5×3的标准正态分布随机矩阵
# 5:5个交易日
# 3:3只股票
# 返回值:二维数组,形状为(5, 3)
returns_wide = np.random.randn(5, 3)  # 生成5天3只股票的收益率矩阵

# pd.date_range('2023-01-01', periods=5):生成5个连续的交易日
# '2023-01-01':起始日期
# periods=5:生成5个日期
# 返回值:DatetimeIndex对象,包含5个日期
dates = pd.date_range('2023-01-01', periods=5)  # 生成日期索引

# 定义股票列表(列名)
# 金融应用:A股典型的权重股
stocks = ['贵州茅台', '五粮液', '招商银行']  # 3只股票的名称

# ==================== 创建宽格式DataFrame ====================
print('宽格式(日期×股票):')  # 打印标题

# pd.DataFrame(returns_wide, index=dates, columns=stocks):创建DataFrame
# returns_wide:数据,5×3的收益率矩阵
# index=dates:行索引,交易日期
# columns=stocks:列名,股票名称
# 返回值:DataFrame对象,宽格式
# 金融应用:宽格式便于查看各股票在各个日期的收益率
df_wide = pd.DataFrame(returns_wide, index=dates, columns=stocks)  # 创建宽格式DataFrame

print(df_wide)  # 打印宽格式DataFrame
# 输出格式:
#            贵州茅台    五粮液  招商银行
# 2023-01-01  0.123   -0.456   0.789
# 2023-01-02  1.234    0.567  -0.890
# ...

# ==================== 转置为长格式 ====================
# df_wide.T:DataFrame转置运算符,.T是转置属性
# 转置:行变列,列变行
# 原格式:行=日期(5行),列=股票(3列)
# 转置后:行=股票(3行),列=日期(5列)
# 返回值:转置后的DataFrame
# 金融应用:长格式便于分析单只股票的时间序列,或用于绘制K线图
print('\n长格式(股票×日期):')  # 打印标题
df_long = df_wide.T  # 转置DataFrame

print(df_long)  # 打印长格式DataFrame
# 输出格式:
#           2023-01-01  2023-01-02  2023-01-03  ...
# 贵州茅台      0.123      1.234      2.345  ...
# 五粮液      -0.456      0.567      0.678  ...
# 招商银行      0.789     -0.890     -0.901  ...

39.8 延伸阅读(选读)

NumPy的历史与重要性:

NumPy(Numerical Python)是Python科学计算生态系统的基础库,最初由Travis Oliphant于2005年创建。它提供了高性能的多维数组对象和用于处理这些数组的工具。

39.9 本章小结

要点:

  • NumPy数组同构、连续内存,shapendimsizedtype 共同描述数组结构
  • 二维索引 arr[行, 列];切片含头不含尾;布尔索引 arr[arr > 50] 是金融条件筛选的常用手段
  • 切片返回视图(与原数组共享内存),.copy() 才生成独立副本
  • reshape(行, 列) 要求元素总数匹配,.T 转置,ravel() 返回视图展平、flatten() 返回副本展平
  • 向量化运算 arr1 + arr2 替代Python循环;weights @ returns 一步算出组合收益率序列
  • pd.Series 是带标签的一维数组,.values 可取出其NumPy数组

易错点:

  • 平台任务一未设置随机种子,每次运行矩阵数值都不同(需复现可先执行 np.random.seed(42)),但 shape 恒为 (2000, 500)
  • 二维切片 arr[0:5, :5] 中间的逗号不能省,漏写会被当作一维切片
  • 修改切片(视图)会同时改动原数组;需要独立修改时必须先 .copy()
  • reshape(3, 4) 与元素总数不符时抛 ValueError

39.10 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 概念自测(不看正课):NumPy数组与Python列表有哪三点关键差异?什么是视图与副本?shapesize 之间是什么关系?

    参考答案(点开前请先独立完成)

    解题思路:三点关键差异——(1)同构性:数组要求全体元素同一 dtype,列表可混合任意类型;(2)运算方式:数组支持向量化运算(整个数组一次算,C 层实现、无需 Python 循环),列表须逐元素循环;(3)维度与内存:数组可多维并用 shape 描述结构、元素连续存储,列表是一维的、元素是分散的对象引用。视图是原数组某块内存的“窗口”(切片返回视图,改视图会同步改到原数组);副本用 .copy() 生成独立内存,改副本不影响原数组。shapesize 的关系:size 等于 shape 各维度取值的乘积(如 (3, 4) 的数组 size 为 12)。

    回扣主线章:数组结构与索引的讲解见第 章节 5 章,向量化运算见第 章节 6 章。

  2. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    import numpy as np
    arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
    print(arr.shape)
    print(arr[1, :])
    print(arr[:, 2])
    print(arr[arr > 6])

    参考答案(先写下你的预测再点开)

    解题思路arr 是 3 行 4 列的二维数组。arr.shape 输出 (3, 4)arr[1, :] 取第 1 行(索引从 0 起)全部列,输出 [5 6 7 8]arr[:, 2] 取全部行的第 2 列,输出 [ 3 7 11]arr[arr > 6] 是布尔索引,把条件为真的元素按行优先顺序抽成一维数组,输出 [ 7 8 9 10 11 12]

    # 验证脚本:原样运行题面代码
    import numpy as np  # 导入numpy并简写为np
    arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])  # 创建3行4列的二维数组
    print(arr.shape)  # 输出数组形状
    print(arr[1, :])  # 输出第1行全部元素
    print(arr[:, 2])  # 输出全部行的第2列
    print(arr[arr > 6])  # 布尔索引筛选大于6的元素

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    (3, 4)
    [5 6 7 8]
    [ 3  7 11]
    [ 7  8  9 10 11 12]

    回扣主线章:二维索引与布尔索引的讲解见第 章节 5 章。

  3. 动手验证:对 a = np.arange(10),先令 v = a[2:5] 并执行 v[0] = 999,输出 a;再令 c = a[2:5].copy() 并执行 c[0] = -1,再次输出 a。用视图与副本的概念解释两次结果的差异。

    参考答案(点开前请先独立完成)

    解题思路:第一次 v = a[2:5] 得到的是视图(与 a 共享内存),v[0] = 999 直接写进原数组,a 变为 [0, 1, 999, 3, 4, 5, 6, 7, 8, 9];第二次 c = a[2:5].copy() 显式复制出独立内存,c[0] = -1 只改副本,a 保持不变。关键理由:切片默认返回视图、.copy() 才返回副本,需要独立修改时必须先复制。

    # 验证脚本:对比视图与副本对原数组的影响
    import numpy as np  # 导入numpy
    a = np.arange(10)  # 生成0~9的一维数组
    v = a[2:5]  # 切片得到视图(共享内存)
    v[0] = 999  # 通过视图修改元素
    print('改视图后a =', a)  # 原数组被同步改动
    c = a[2:5].copy()  # 显式复制得到副本
    c[0] = -1  # 只修改副本
    print('改副本后a =', a)  # 原数组不受影响

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    改视图后a = [  0   1 999   3   4   5   6   7   8   9]
    改副本后a = [  0   1 999   3   4   5   6   7   8   9]

    第二行 a 与第一行完全相同:改视图那一步把 999 写进了原数组并保留下来,而随后的 c[0] = -1 只落在副本上,原数组不再受影响——这正是视图与副本的分野。

    回扣主线章:视图与副本的对比讲解见第 章节 5 章。

  4. 变式任务(平台任务一、二同型改造):仿照任务一,生成形状为 (1000, 250) 的日收益率矩阵,输出其 shape 与前3行、前4列的切片;再仿照任务二,从列表 [2.5, 3.1, 4.8] 创建Series并输出。

    参考答案(点开前请先独立完成)

    解题思路:与平台任务一同型,只是形状改为 (1000, 250)、切片改为前 3 行前 4 列 return_matrix[0:3, 0:4];本变式加 np.random.seed(42) 固定种子,使数值可复现(平台任务未设种子,数值每次不同)。Series 部分与任务二同型,pd.Series(列表) 即得“左侧整数索引、右侧取值”的一维序列。

    # 变式程序:生成日收益率矩阵并切片,再从列表创建Series
    import numpy as np  # 导入numpy
    import pandas as pd  # 导入pandas
    np.random.seed(42)  # 固定随机种子,使本变式输出可复现
    return_matrix = np.random.standard_normal((1000, 250))  # 1000只股票×250个交易日的标准正态涨跌幅矩阵
    print(return_matrix.shape)  # 输出矩阵形状
    print(return_matrix[0:3, 0:4])  # 输出前3行、前4列的切片
    rate_series = pd.Series([2.5, 3.1, 4.8])  # 从列表创建Series
    print(rate_series)  # 输出Series

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准;本变式已固定随机种子,未设种子的平台任务数值每次运行不同):

    (1000, 250)
    [[ 0.49671415 -0.1382643   0.64768854  1.52302986]
     [-1.26088395  0.91786195  2.1221562   1.03246526]
     [ 0.92617755  1.90941664 -1.39856757  0.56296924]]
    0    2.5
    1    3.1
    2    4.8
    dtype: float64

    注意:以上为本题变式的独立代码;列表 39.1列表 39.6 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣主线章:随机矩阵生成与切片的讲解见第 章节 5 章。