5 NumPy 数组的创建与访问
5.1 引言NumPy在金融计算中的核心地位
NumPy(Numerical Python)是Python科学计算的基础包,提供了高性能的多维数组对象和相关工具。在金融数据分析中,NumPy的地位无可替代,它不仅是Pandas、Matplotlib等库的基础,其本身也提供了强大的数值计算能力。
5.2 本章学习目标
通过本章学习,你将能够:
- 使用
np.array()从列表与嵌套列表创建一维、二维股价数组,并用shape、ndim、size、dtype查看数组属性 - 使用
[start:end:step]切片、负数索引与布尔索引访问一维数组,用array[row, col]与:索引二维数组 - 使用
np.concatenate()(及vstack/hstack)按行或按列拼接数组,用hsplit/vsplit分割数组 - 区分数组的视图与副本,安全使用
.copy()、reshape()与.T完成形状变换 - 借助广播机制完成标量折扣、列向量调整与收益率标准化,并说明向量化运算相对 Python 循环的性能优势
5.3 数组的创建
理论背景:数组与列表的本质区别
在动手创建第一个数组之前,需要先明确NumPy数组与第2章学习的Python列表有什么根本性差异。从数据结构的角度来看,两者的对比如下:
| 特性 | Python列表 | NumPy数组 |
|---|---|---|
| 内存存储 | 分散存储对象引用 | 连续内存存储 |
| 元素类型 | 可以不同 | 必须相同 |
| 性能 | 解释执行,较慢 | 编译优化,极快 |
| 向量化 | 不支持 | 原生支持 |
| 内存效率 | 较低(每个元素8字节+对象开销) | 较高(紧凑存储) |
5.3.1 基础创建方法
NumPy提供了多种创建数组的方法:
任务要求:已知股票1、股票2 各 5 个交易日的收盘价数组 stock1、stock2,依次输出三项内容:股票1数组的形状 stock1.shape、股票2周二(索引 1)的股价、两数组按 2×5 方式沿 axis=0 拼接后的矩阵 stock_con。请将代码原样输入教学平台(注释除外),判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import numpy as np # 导入NumPy数值计算库
# 已知股价数据
stock1=np.array([22.91,22.89,23.38,23.09,22.90])
stock2=np.array([41.14,40.99,41.29,40.81,40.97]) # 创建NumPy数组stock2
# 1. 查看股票1的属性值情况,并输出。
print(stock1.shape)
# 2. 输出股票2周二的股价
print(stock2[1])
# 3. 连接股票1与股票2的数据(按照2X5的方式连接),并输出
stock_con=np.concatenate(([stock1],[stock2]),axis=0)
print(stock_con) # 输出两只股票收盘价合并后的2×5矩阵预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):
(5,)
40.99
[[22.91 22.89 23.38 23.09 22.9 ]
[41.14 40.99 41.29 40.81 40.97]]
判读要点:(5,) 是含一个元素的元组,表示一维数组有 5 个元素,注意它与 (1, 5)、(5, 1) 的区别;拼接结果为 2 行 5 列的二维数组,第 1 行是 stock1、第 2 行是 stock2。
代码深度解析:
np.array()的参数:np.array(object, dtype=None, copy=True, order=None, ndmin=0)object: 列表、元组或其他数组dtype: 指定数据类型(如np.float64,np.int32)copy: 是否复制数据
数据类型的金融意义:
float32: 节省内存,适合大规模数据float64: 双精度,金融计算推荐int64: 整数(如成交量、日期)
内存布局:
- C顺序(C-style): 行优先(默认)
- F顺序(Fortran-style): 列优先
5.3.2 二维数组的创建
# ==================== 创建二维数组 ====================
# np.array()可以从嵌套列表创建二维数组
# 外层列表包含2个元素(2行)
# 每个元素是一个包含5个数值的列表(5列)
# 第一行存储股票1的5日收盘价
# 第二行存储股票2的5日收盘价
stocks_2d = np.array([ # 创建二维数组(2行5列)
[22.91, 22.89, 23.38, 23.09, 22.90], # 第1行:股票1的收盘价
[41.14, 40.99, 41.29, 40.81, 40.97] # 第2行:股票2的收盘价
])
# ==================== 查看数组形状 ====================
# .shape属性返回数组的维度
# 对于二维数组,返回(行数, 列数)
print("二维数组形状:", stocks_2d.shape) # 输出:(2, 5)
# .shape[0]访问第一个维度(行数)
print("行数:", stocks_2d.shape[0]) # 输出:2
# .shape[1]访问第二个维度(列数)
print("列数:", stocks_2d.shape[1]) # 输出:5
# ==================== 查看数组属性 ====================
# .ndim属性返回数组的维度数(秩)
# 2表示这是一个二维数组
print(f"\n数组维度: {stocks_2d.ndim}") # 输出:2
# .size属性返回数组中元素的总数
# 2×5=10个元素
print(f"数组大小: {stocks_2d.size}") # 输出:10
# .dtype属性返回数组的数据类型
# float64表示64位浮点数
print(f"数据类型: {stocks_2d.dtype}") # 输出:float64补充说明:shape属性的理解
shape属性返回一个元组,描述数组的维度:
- 一维数组(5,): 5个元素
- 二维数组(2, 5): 2行5列
- 三维数组(2, 3, 4): 2个3×4的矩阵
在金融应用中:
- 一维: 单只股票的时间序列
- 二维: 多只股票的时间序列
- 三维: 多个市场、多只股票、多日数据(面板数据)
5.4 数组的访问与切片
5.4.1 一维数组的索引
# ==================== 任务2:输出股票2周二的股价 ====================
# 假设索引0=周一,1=周二,2=周三,3=周四,4=周五
# 方括号[]用于索引访问
# stock2[1]访问数组中索引为1的元素(第2个元素)
tuesday_price_stock2 = stock2[1] # 获取索引1的元素(周二股价)
# f-string格式化输出,.2f保留2位小数
print(f"股票2周二股价: {tuesday_price_stock2:.2f}元") # 输出:40.99元
# ==================== 多种索引方式 ====================
# 切片语法:[start:end:step]
# start是起始索引(包含)
# end是结束索引(不包含)
# step是步长
# stock2[0:3]获取索引0,1,2的元素(前3个)
print(f"\n股票2第1-3天: {stock2[0:3]}") # 输出前3天价格
# 负数索引从末尾开始计数
# -1是最后一个元素,-2是倒数第2个
# stock2[-2:]从倒数第2个到末尾
print(f"股票2最后2天: {stock2[-2:]}") # 输出后2天价格
# 步长为2,每隔一个取一个元素
# stock2[::2]取索引0,2,4的元素
print(f"股票2偶数天: {stock2[::2]}") # 输出奇数日的价格(索引0,2,4)
# ==================== 布尔索引(高级用法) ====================
# stock2 > 41.0返回布尔数组
# 比较运算符>逐元素比较
# 结果是[False, False, True, False, False]
# 使用布尔数组作为索引,返回True对应的元素
high_price_days = stock2[stock2 > 41.0] # 筛选价格大于41的元素
print(f"\n股价>41元的交易日: {high_price_days}") # 输出:[41.29]索引规则总结
[start:end:step]start包含,end不包含- 负数索引从末尾计数
-1是最后一个元素 - 省略start默认从0开始,省略end到末尾
::step或[::-1]用于反向
5.4.2 二维数组的索引
# ==================== 获取单个元素 ====================
# 二维数组索引语法:array[row, col]
# row是行索引,col是列索引
# stocks_2d[0, 2]表示第1行(索引0),第3列(索引2)
element = stocks_2d[0, 2] # 获取第1行第3列的元素
print(f"stocks_2d[0, 2] = {element}") # 输出:23.38
# ==================== 获取整行 ====================
# :表示选择该维度的所有元素
# stocks_2d[1, :]表示第2行(索引1)的所有列
row_1 = stocks_2d[1, :] # 获取第2行的所有元素
print(f"\n股票2所有交易日价格: {row_1}") # 输出第2行数据
# ==================== 获取整列 ====================
# stocks_2d[:, 2]表示所有行的第3列(索引2)
# :表示选择所有行
col_3 = stocks_2d[:, 2] # 获取第3列的所有元素
print(f"周三所有股票价格: {col_3}") # 输出第3列数据
# ==================== 切片:获取子矩阵 ====================
# stocks_2d[:, 1:4]表示所有行,第2-4列
# 1:4表示列索引1,2,3(不包含4)
# 结果是一个2×3的子矩阵
sub_matrix = stocks_2d[:, 1:4] # 获取所有行的第2-4列
print(f"\n周二到周四的价格:\n{sub_matrix}") # 输出子矩阵金融应用场景:
- 单只股票时间序列:
arr[stock_id, :] - 单日所有股票:
arr[:, day_id] - 特定时间段:
arr[:, start:end]
5.5 数组的拼接与分割
5.5.1 concatenate()数组拼接
# ==================== 任务3:连接股票1与股票2 ====================
# np.concatenate()沿指定轴连接数组
# 参数1:([stock1], [stock2])是要连接的数组序列
# 注意:[stock1]和[stock2]必须包装在列表中
# axis=0指定沿第0轴(行方向)拼接
stock_con = np.concatenate(([stock1], [stock2]), axis=0) # 垂直拼接两个一维数组
# ==================== 输出拼接结果 ====================
print("拼接后的数组:") # 输出标题
print(stock_con) # 输出拼接后的2×5数组
print(f"形状: {stock_con.shape}") # 输出:(2, 5)
# ==================== 理解axis参数 ====================
# axis=0:沿行方向拼接(垂直堆叠)
# axis=1:沿列方向拼接(水平连接)
# ==================== 水平拼接示例 ====================
# 创建新的价格数据
# prices_aug包含2个新价格
prices_aug = np.array([22.95, 23.02]) # 待追加的价格数据
# np.concatenate()沿axis=0拼接
# 将stock2和prices_aug首尾相接
# 结果是包含7个元素的一维数组
stock2_aug = np.concatenate((stock2, prices_aug), axis=0) # 拼接数组
print(f"\n扩充后的股票2: {stock2_aug}") # 输出拼接后的数组
# ==================== 二维数组的拼接 ====================
# np.vstack()垂直堆叠(沿行方向)
# 等价于np.concatenate(..., axis=0)
# 将stock1和stock2作为两行堆叠
stock_vstack = np.vstack((stock1, stock2)) # 垂直堆叠
# np.hstack()水平连接(沿列方向)
# 等价于np.concatenate(..., axis=1)
# 将stock1和stock2首尾相接成一行
stock_hstack = np.hstack((stock1, stock2)) # 水平连接
# ==================== 输出拼接结果 ====================
print(f"\n垂直堆叠:\n{stock_vstack}") # 输出2×5数组
print(f"水平连接:\n{stock_hstack}") # 输出包含10个元素的一维数组拼接函数对比:
| 函数 | 功能 | axis参数 | 等价操作 |
|---|---|---|---|
concatenate |
通用拼接 | 指定轴 | - |
vstack |
垂直堆叠 | 固定0 | concatenate(..., axis=0) |
hstack |
水平连接 | 固定1 | concatenate(..., axis=1) |
stack |
创建新维度 | 新增 | - |
5.5.2 数组的分割
# ==================== 水平分割(按列) ====================
# np.hsplit()水平分割数组(按列切分)
# stocks_2d是要分割的二维数组
# [2, 4]指定分割位置(在索引2和4之后分割)
# 结果是3个数组:列0-1,列2-3,列4
stocks_split = np.hsplit(stocks_2d, [2, 4]) # 在第2和第4列后分割
# ==================== 输出分割结果 ====================
print("按列分割结果:") # 输出标题
# enumerate()遍历分割后的数组列表
# i是索引,part是分割后的数组部分
for i, part in enumerate(stocks_split): # 遍历每个部分
# .shape获取数组形状
print(f" 部分{i+1}: {part.shape}") # 输出每部分的形状
# ==================== 垂直分割(按行) ====================
# np.vsplit()垂直分割数组(按行切分)
# stocks_2d是要分割的二维数组
# 参数2表示分成2行
# 结果是2个数组,每行一个
stocks_vsplit = np.vsplit(stocks_2d, 2) # 分成2行
# ==================== 输出分割结果 ====================
print(f"\n按行分割:") # 输出标题
# enumerate()遍历分割后的数组列表
for i, part in enumerate(stocks_vsplit): # 遍历每一行
print(f" 部分{i+1}: {part}") # 输出每行的内容金融应用:
- 分割训练/测试集: 时间序列数据的分割
- 按市场分组: 沪深股票数据分离
- 按时间段: 日内/隔夜数据分离
5.6 数组的视图与副本
关键概念:理解视图(View)和副本(Copy)的区别对避免意外数据修改至关重要。
# ==================== 创建原始数组 ====================
# np.array()创建一个包含5个元素的数组
original = np.array([1, 2, 3, 4, 5]) # 原始数组
# ==================== 切片返回视图 ====================
# 切片操作[1:4]返回视图(View)
# 视图共享原始数组的内存
# 修改视图会影响原数组
view = original[1:4] # 创建切片视图
# 修改视图的第1个元素
view[0] = 999 # 修改视图的元素
# 观察到原数组也被修改
print(f"修改视图后,原数组: {original}") # 输出:[1, 999, 3, 4, 5]
# ==================== 创建副本 ====================
# .copy()方法创建数组的副本(Copy)
# 副本拥有独立的内存
# 修改副本不影响原数组
copy = original[1:4].copy() # 创建副本
# 修改副本的第1个元素
copy[0] = 888 # 修改副本的元素
# 原数组不受影响
print(f"修改副本后,原数组: {original}") # 输出:[1, 999, 3, 4, 5]
# ==================== 重置原数组 ====================
# 重新赋值创建新数组
original = np.array([1, 2, 3, 4, 5]) # 重置为原始值
# ==================== Fancy Indexing ====================
# 使用列表作为索引称为Fancy Indexing
# [[0, 2, 4]]指定要访问的索引位置
# Fancy Indexing返回副本,不共享内存
fancy = original[[0, 2, 4]] # Fancy Indexing创建副本
# 修改副本的第1个元素
fancy[0] = 777 # 修改副本的元素
# 原数组不受影响
print(f"Fancy索引修改副本,原数组: {original}") # 输出:[1, 2, 3, 4, 5]规则总结:
- 切片 → 视图(共享内存)
- Fancy索引 → 副本(独立内存)
- 布尔索引 → 副本
.copy()→ 强制副本
最佳实践: 当不确定时,使用.copy()创建副本以避免意外修改。
5.7 数组的形状操作
# ==================== 创建一维数组 ====================
# np.arange()创建0-9的一维数组
# 包含10个元素
arr_1d = np.arange(10) # 创建一维数组
print(f"一维数组: {arr_1d}") # 输出:[0 1 2 3 4 5 6 7 8 9]
# ==================== reshape改变形状 ====================
# .reshape()改变数组的形状而不改变数据
# (2, 5)表示转换为2行5列的二维数组
# 元素总数必须保持不变(10=2×5)
arr_2x5 = arr_1d.reshape(2, 5) # 转换为2×5数组
print(f"\n2×5数组:\n{arr_2x5}") # 输出二维数组
# ==================== reshape(-1)自动计算维度 ====================
# -1表示自动计算该维度的大小
# reshape(-1, 2)表示自动计算行数
# 10个元素,每行2列,自动计算为5行
arr_5x2 = arr_1d.reshape(-1, 2) # 自动转换为5×2数组
print(f"\n5×2数组:\n{arr_5x2}") # 输出5×2数组
# ==================== ravel和flatten(展平数组) ====================
# .ravel()将多维数组展平为一维
# 返回视图(可能),不保证创建副本
flat_ravel = arr_2x5.ravel() # 展平为数组(可能是视图)
# .flatten()将多维数组展平为一维
# 返回副本,总是创建新数组
flat_flatten = arr_2x5.flatten() # 展平为数组(总是副本)
# ==================== 输出展平结果 ====================
print(f"\nravel结果: {flat_ravel}") # 输出展平后的数组
print(f"flatten结果: {flat_flatten}") # 输出展平后的数组
# ==================== 转置 ====================
# .T属性返回数组的转置
# 行列互换:2×5变为5×2
transpose = arr_2x5.T # 转置数组
print(f"\n转置后({transpose.shape}):\n{transpose}") # 输出转置后的数组5.8 广播(Broadcasting)机制
广播是NumPy的强大特性,允许不同形状的数组进行算术运算。
# ==================== 场景1:标量与数组 ====================
# prices是包含5个价格的一维数组
prices = np.array([10, 20, 30, 40, 50]) # 价格数组
# discount是标量(单个数值)
discount = 0.9 # 打9折的折扣率
# 标量与数组运算时,标量自动"广播"到每个元素
# 相当于[10*0.9, 20*0.9, 30*0.9, 40*0.9, 50*0.9]
discounted_prices = prices * discount # 广播乘法
print(f"折扣后价格: {discounted_prices}") # 输出折扣后价格
# ==================== 场景2:不同形状的数组 ====================
# base_prices是3只股票的基准价格
base_prices = np.array([10, 20, 30]) # 3只股票价格(一维)
# adjustments是3×1的二维数组(列向量)
# 代表3种不同的调整情景
adjustments = np.array([[1.1], [0.9], [1.0]]) # 调整系数(列向量)
# 广播机制:
# base_prices形状(3,)广播为(3, 3)
# adjustments形状(3, 1)广播为(3, 3)
# 结果是3×3矩阵
result = base_prices * adjustments # 广播计算
print(f"\n调整后的价格矩阵:\n{result}") # 输出3×3矩阵
# ==================== 场景3:标准化收益率 ====================
# returns是2×3的收益率数组
# 2只股票,3个时间点
returns = np.array([[0.05, 0.03, 0.04], [0.06, 0.02, 0.05]]) # 收益率矩阵
# 沿axis=1计算每行的均值
# keepdims=True保持维度(结果为2×1而非(2,))
mean = returns.mean(axis=1, keepdims=True) # 计算均值并保持维度
# 沿axis=1计算每行的标准差
# keepdims=True保持维度
std = returns.std(axis=1, keepdims=True) # 计算标准差并保持维度
# 标准化:(收益率-均值)/标准差
# 广播机制使mean和std自动扩展到与returns相同的形状
normalized = (returns - mean) / std # 标准化计算
print(f"\n标准化后的收益率:\n{normalized}") # 输出标准化后的数据广播规则:
- 从尾部(最右边)开始比较维度
- 维度相同或其中一个为1则兼容
- 缺失的维度视为1
金融应用:
- 批量调整: 对多只股票应用统一系数
- 风险调整: 按波动率调整收益率
- 组合优化: 权重向量与收益率矩阵相乘
5.9 性能优化向量化 vs 循环
为什么NumPy更快?
- 连续内存: 缓存友好,减少内存访问延迟
- 类型一致: 避免动态类型检查
- 向量化操作: 底层C/Fortran实现,利用SIMD指令
- 广播机制: 自动对齐不同形状的数组
下面的实验用同样的计算量对比两种写法的耗时:
# ==================== 导入必要的库 ====================
# import time导入时间模块,用于性能测试
import time # 导入时间模块
np.random.seed(42) # 固定随机种子,保证每次运行生成相同的模拟数据
# ==================== 生成大数据集 ====================
# n定义数据规模
n = 1000000 # 100万个数据点
# np.random.randn()生成标准正态分布的随机数
# * 10 + 100将数据调整为均值100,标准差10
# 模拟股价数据
prices = np.random.randn(n) * 10 + 100 # 生成模拟股价数据
# ==================== 方法1:Python循环(慢) ====================
# 记录开始时间
start = time.time() # 获取开始时间
# 使用for循环计算收益率
# []创建空列表,用于存储结果
returns_loop = [] # 初始化空列表
# range(1, len(prices))生成1到n-1的索引
for i in range(1, len(prices)): # 遍历每个时间点
# 计算简单收益率:(当前价格-前一日价格)/前一日价格
ret = (prices[i] - prices[i-1]) / prices[i-1] # 计算收益率
returns_loop.append(ret) # 添加到列表
# 计算循环耗时
loop_time = time.time() - start # 计算耗时
# ==================== 方法2:NumPy向量化(快) ====================
# 记录开始时间
start = time.time() # 获取开始时间
# NumPy向量化计算
# prices[1:]获取第2个到最后的元素
# prices[:-1]获取第1个到倒数第2个的元素
# 数组运算自动向量化,无需显式循环
returns_vec = (prices[1:] - prices[:-1]) / prices[:-1] # 向量化计算收益率
# 计算向量化耗时
vec_time = time.time() - start # 计算耗时
# ==================== 输出性能对比 ====================
# f-string格式化输出,:.4f保留4位小数
print(f"循环时间: {loop_time:.4f}秒") # 输出循环耗时
print(f"向量化时间: {vec_time:.4f}秒") # 输出向量化耗时
# 计算性能提升倍数
print(f"性能提升: {loop_time/vec_time:.1f}倍") # 输出加速比5.10 本章小结
要点:
np.array()从(嵌套)列表创建数组;shape、ndim、size、dtype描述数组的结构与元素类型- 切片
[start:end:step]含头不含尾;二维索引用array[row, col],:可选取整行或整列 - 布尔索引按条件筛选元素;
concatenate/vstack/hstack负责拼接,hsplit/vsplit负责分割 - 切片返回视图(共享内存),Fancy 索引、布尔索引与
.copy()返回副本 reshape(-1, ...)自动推算维度,.T转置;广播从尾部维度对齐,向量化运算远快于循环
易错点:
(5,)、(1, 5)、(5, 1)是三个不同形状,拼接或矩阵运算前应先检查.shape- 修改切片结果会同时改动原数组;需要独立数据时必须显式
.copy() np.concatenate(([stock1],[stock2]), axis=0)中一维数组要先升维;直接拼接两个同长一维数组得到的是更长的一维数组- 切片
1:4只取索引 1、2、3;负数索引从-1开始计数
5.11 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
import numpy as np a = np.array([22.91, 22.89, 23.38, 23.09, 22.90]) print(a.shape) b = a[1:4] print(b) b[0] = 0.0 print(a) c = a[[0, 4]] c[0] = 0.0 print(a) print(a.reshape(5, 1).shape)参考答案(先写下你的预测再点开)
解题思路:逐行推演。
a是含 5 个元素的一维数组,a.shape输出(5,)(注意是含一个元素的元组,不是 5)。b = a[1:4]是切片,含头不含尾,取索引 1、2、3 的元素,得到[22.89, 23.38, 23.09];切片返回视图,与a共享内存,因此b[0] = 0.0会把a中索引 1 的位置一并改掉,a变为[22.91, 0.0, 23.38, 23.09, 22.9]。c = a[[0, 4]]是 Fancy 索引,取索引 0、4 的元素,得到[22.91, 22.9];Fancy 索引返回副本,c[0] = 0.0不影响a,再次打印a仍是[22.91, 0.0, 23.38, 23.09, 22.9]。最后a.reshape(5, 1)把 5 个元素重排成 5 行 1 列的二维数组,.shape输出(5, 1)。另注意 NumPy 打印数组时小数末尾的 0 会被省略,0.0 显示为0.、22.90 显示为22.9。# 验证脚本:验证切片视图与Fancy索引副本的差异 import numpy as np # 导入NumPy库 a = np.array([22.91, 22.89, 23.38, 23.09, 22.90]) # 创建一维股价数组 print(a.shape) # 输出形状(一维,5个元素) b = a[1:4] # 切片取索引1、2、3,得到视图 print(b) # 输出切片结果 b[0] = 0.0 # 修改视图的首个元素 print(a) # 原数组同步被修改(视图共享内存) c = a[[0, 4]] # Fancy索引取索引0、4,得到副本 c[0] = 0.0 # 修改副本的首个元素 print(a) # 原数组不受影响(副本独立) print(a.reshape(5, 1).shape) # 重排为5行1列后输出形状预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
(5,) [22.89 23.38 23.09] [22.91 0. 23.38 23.09 22.9 ] [22.91 0. 23.38 23.09 22.9 ] (5, 1)回扣本章:对应本章小结“要点”第 4 条与“易错点”第 2 条——切片返回视图(共享内存),Fancy 索引、布尔索引与
.copy()返回副本;修改切片结果会同时改动原数组。概念辨析:
(5,)、(1, 5)、(5, 1)三种形状各有几个维度、各适合表示什么金融数据?np.concatenate((stock1, stock2))与np.concatenate(([stock1], [stock2]), axis=0)的结果有何不同?再说明:切片得到的b与 Fancy 索引得到的c,修改其中元素后原数组a的状态为何不一样。参考答案(点开前请先独立完成)
解题思路:逐点作答。第一,
(5,)是一维(ndim=1),5 个元素排成一行,适合表示单只股票 5 个交易日的时间序列;(1, 5)是二维(ndim=2),1 行 5 列,适合表示“1 只股票 × 5 天”的矩阵行(可沿axis=0继续堆叠其他股票);(5, 1)也是二维,5 行 1 列,适合作为列向量(如 5 个样本对同一资产的特征列)。三者元素个数都是 5,但形状不同,不能直接参与需要形状匹配的矩阵运算,拼接前应先检查.shape。第二,本机实测:np.concatenate((stock1, stock2))把两个各含 5 个元素的一维数组首尾相接,得到长度为 10 的一维数组(形状(10,));而np.concatenate(([stock1], [stock2]), axis=0)先用[stock1]、[stock2]把一维数组升维成(1, 5)的二维行,再沿axis=0逐行堆叠,得到(2, 5)的二维矩阵——一维数组必须先升维才能按行拼接成矩阵。第三,切片与 Fancy 索引的差别源于内存:切片b = a[1:4]返回视图,b与a共享同一段内存,只是 viewing 的窗口不同,改b就是改a;Fancy 索引c = a[[0, 4]]要按任意次序取元素,无法用一段连续内存表示,NumPy 因此返回一份新开辟内存的副本,改c与a无关。需要独立数据时应显式.copy()。回扣本章:对应本章小结“易错点”第 1、3 条与“要点”第 4 条——
(5,)、(1, 5)、(5, 1)是三个不同形状;一维数组拼接成矩阵前要先升维;切片是视图、Fancy 索引是副本。变式任务(平台任务同型改造):已知三只股票各 5 个交易日的收盘价
s1 = np.array([22.91, 22.89, 23.38, 23.09, 22.90])、s2 = np.array([41.14, 40.99, 41.29, 40.81, 40.97])、s3 = np.array([10.5, 10.62, 10.58, 10.71, 10.66])。创建 3×5 的二维数组并输出其shape与dtype;输出股票2周四(索引 3)的股价;输出周三(列索引 2)三只股票的价格;再用布尔索引筛选出s3中大于 10.60 的价格。参考答案(点开前请先独立完成)
解题思路:用
np.array([s1, s2, s3])把三个一维数组作为三行组成(3, 5)二维数组,dtype 为float64;股票2 周四的股价直接s2[3],得到40.81;周三(列索引 2)三只股票的价格用整列切片stocks_3x5[:, 2],得到[23.38, 41.29, 10.58];布尔索引s3[s3 > 10.60]先由比较运算生成布尔数组,再筛选出True位置的价格,s3中大于 10.60 的有 10.62、10.71、10.66 三个。# 变式程序:三只股票收盘价的二维数组构建与访问 import numpy as np # 导入NumPy库 s1 = np.array([22.91, 22.89, 23.38, 23.09, 22.90]) # 股票1的5日收盘价 s2 = np.array([41.14, 40.99, 41.29, 40.81, 40.97]) # 股票2的5日收盘价 s3 = np.array([10.5, 10.62, 10.58, 10.71, 10.66]) # 股票3的5日收盘价 stocks_3x5 = np.array([s1, s2, s3]) # 三行堆叠成3×5二维数组 print(stocks_3x5.shape, stocks_3x5.dtype) # 输出形状与元素类型 print(s2[3]) # 输出股票2周四(索引3)的股价 print(stocks_3x5[:, 2]) # 输出周三(列索引2)三只股票的价格 print(s3[s3 > 10.60]) # 布尔索引筛选s3中大于10.60的价格预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
(3, 5) float64 40.81 [23.38 41.29 10.58] [10.62 10.71 10.66]注意:以上为本题变式的独立代码;列表 5.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应本章小结“要点”第 2、3 条——二维索引用
array[row, col],:选取整列;布尔索引按条件筛选元素。动手验证:对第 3 题得到的 3×5 数组,分别用
reshape(5, 3)、reshape(-1, 1)、.T变换形状并打印.shape,观察元素总数不变的前提下行列如何重排;再用np.hsplit把它按列分成前 2 列与后 3 列两部分。参考答案(点开前请先独立完成)
解题思路:原数组
(3, 5)共 15 个元素,三种变换都不改变元素总数与 C 序(行优先)下的元素顺序,只改变行列划分:reshape(5, 3)得到(5, 3),5 行 3 列;reshape(-1, 1)中-1表示自动推算该维度,15 个元素每列 1 个,自动算出 15 行,得到(15, 1)的列向量;.T转置把行列互换,得到(5, 3)——同样显示(5, 3),但转置是“原来的列变成行”,元素排布与reshape(5, 3)不同(转置第 1 行是原第 1 列的三个价格)。np.hsplit(stocks_3x5, [2])以索引 2 为分割点按列切分,前一部分含列 0、1(形状(3, 2)),后一部分含列 2、3、4(形状(3, 3))。# 验证脚本:形状变换与按列分割(沿用第3题的stocks_3x5数组) import numpy as np # 导入NumPy库 stocks_3x5 = np.array([[22.91, 22.89, 23.38, 23.09, 22.90], # 三只股票收盘价矩阵 [41.14, 40.99, 41.29, 40.81, 40.97], [10.5, 10.62, 10.58, 10.71, 10.66]]) print(stocks_3x5.reshape(5, 3).shape) # 重排为5行3列 print(stocks_3x5.reshape(-1, 1).shape) # -1自动推算为15行1列 print(stocks_3x5.T.shape) # 转置:行列互换 parts = np.hsplit(stocks_3x5, [2]) # 在第2列后按列分割 print(parts[0].shape, parts[1].shape) # 输出两部分的形状预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
(5, 3) (15, 1) (5, 3) (3, 2) (3, 3)回扣本章:对应本章小结“要点”第 5 条——
reshape(-1, ...)自动推算维度,.T转置,hsplit/vsplit负责分割;元素总数不变的前提下只重排行列。