NumPy vs Pandas vs Tensor 切片索引对比图解
文章面向学习了 Python、 NumPy、 Pandas 、 PyTorch 列表切片,但对三种语法感到混乱的人。
例如:同样是[1:3],为什么 Pandas 比 NumPy 多选了一行?切完之后修改数据,为什么有时候原数组变了,有时候没变?
- NumPy vs Pandas vs Tensor 切片索引对比图解
- 一、疑惑之处
- 二、坑 1 — 左闭右开 vs 左闭右闭
- NumPy / Tensor:和 Python 列表一致,左闭右开
- Pandas .loc:左闭右闭(特殊)
- 数学线段区间端点对比
- 三、坑 2 — 视图 vs 副本
- NumPy 切片:返回视图,原数组会变
- Pandas 切片:返回副本(大多数情况),原数据不变
- PyTorch Tensor 切片:基础切片也是视图
- 三者对比:切片后修改,谁会影响原数据?
- 四、常用操作 — 多维切片:怎么切出我想要的部分?
- 2D 数组:行和列同时切
- 常用 2D 切片示例
- Tensor 3D 切片:形状为 (样本数,行,列)
- Tensor 中 unsqueeze 与 squeeze — 维度的增与减
- squeeze():删除指定位置长度为 1 的维度
- unsqueeze():在指定位置插入长度为 1 的维度
- 五、常用操作 — 高级索引:花式索引与布尔索引
- 六、流程图
- 七、速查表:三者全对比
- 八、 常见错误写法
- 针对 Pandas DataFrame
- 针对 PyTorch Tensor
- 九、对比
- 方法对比:同一操作的三种实现
- 切片性能对比
- 优势对比
- 总结
一、疑惑之处
你大概遇到过这样的情况:
# Python 列表 —— 左闭右开,不含最后一个元素
lst = [10, 20, 30, 40, 50]
lst[1:3] # → [20, 30] # NumPy —— 看起来一样,左闭右开
arr = np.array([10, 20, 30, 40, 50])
arr[1:3] # → [20, 30] # Pandas loc —— 突然不一样了!
df.loc[1:3] # → 含索引 1, 2, 3 共 3 行! ⚠️ 左闭右闭
这并不是 bug,是设计上的刻意区别。本文将把三者的规则梳理清楚,并配图说明。
二、坑 1 — 左闭右开 vs 左闭右闭
NumPy / Tensor:和 Python 列表一致,左闭右开
arr = np.array([10, 20, 30, 40, 50])
arr[1:3] # → [20, 30] index 1 和 2,不含 3
选中元素示意:

Pandas .loc:左闭右闭(特殊)
假设 DataFrame 的行索引是 0, 1, 2, 3, 4:
df[1:3] # 按行位置, 左闭右开 → 行 1, 行 2
df.loc[1:3] # 按标签, 左闭右闭 → 行 1, 行 2, 行 3 ⚠️
df.iloc[1:3] # 按位置, 左闭右开 → 行 1, 行 2
三种写法选中的行对比示意:

数学线段区间端点对比
Python / NumPy / Pandas .iloc
[1 , 3) → 包含索引 1, 2 ●────────○
Pandas .loc (标签切片)
[1 , 3] → 包含索引 1, 2, 3 ●────────●
loc用标签 = 像日常语言 "从 1 到 3",是闭区间;
iloc用位置 = 找 Python 下标,是开区间。
如果索引是连续整数且从 0 开始,iloc更直观;如果索引是自定义标签(如日期、字符串),loc更安全。不确定时可以打印df.index确认。
另外,同时选行和列时:
df.loc[1:3, 'name':'age'] # 行 1-3,列从'name'到'age' (行闭列闭)
df.iloc[1:3, 0:2] # 行 1-2,列 0-1 (行开列开)
三、坑 2 — 视图 vs 副本
切片后赋值给变量 b,然后修改 b,那原数据 a 也会变吗?
NumPy 切片:返回视图,原数组会变
a = np.array([1, 2, 3, 4])
b = a[1:3] # b 是 a 的视图,共享内存
b[0] = 99
print(a) # [1, 99, 3, 4] 原数组的“2”变成“99”了!
内存示意图:

Pandas 切片:返回副本(大多数情况),原数据不变
df2 = df.iloc[1:3].copy() # 显式 copy(),确保独立
df2.iloc[0, 0] = 99
# df 原始数据不受影响
⚠️ Pandas 链式赋值常遇常踩的坑:
df.iloc[0:3]['age'] = 0← 这行很可能不生效,因为先切片返回副本,再赋值给副本。
正确写法:df.iloc[0:3, 2] = 0(一次性指定行和列)
“薛定谔的视图”
SettingWithCopyWarning底层原因:
Pandas 是构建在 NumPy 之上的,因此它关于视图和副本的规则,很大程度上源于 NumPy 数组的内存布局。当 NumPy 能以步长(stride)方式“查看”原数组时,就能高效返回视图;一旦这种连续性被打破,就必须创建副本。
因此,显式调用.copy()是唯一可以消除不确定的办法。
但好消息是:布尔索引或花式索引筛选出的数据在内存中通常是非连续的。Pandas 无法通过简单的指针偏移(stride)来表示这些零散的行,因此必须创建一个新的内存块来存放结果,即副本。总结如下所示:
| 索引类型 | 示例 | 返回类型确定性 |
|---|---|---|
| 基础切片(单类型数据) | df.iloc[1:3] |
❌ 不确定(可能是视图) |
| 布尔索引 | df[df['age'] > 18] |
✅ 确定是副本 |
| 花式索引(列表) | df[['A', 'B']] |
✅ 确定是副本 |
注:单列切片(如
df['A'])在 Pandas 中返回视图,多列切片(如df[['A','B']])返回副本。
PyTorch Tensor 切片:基础切片也是视图
t = torch.tensor([1, 2, 3, 4])
s = t[1:3] # 也是视图,和 NumPy 一样
s[0] = 99
print(t) # → tensor([1, 99, 3, 4]) ← 原张量也变了! # 想要副本:
s = t[1:3].clone() # 独立副本,修改不影响 原始 t
三者对比:切片后修改,谁会影响原数据?
| 库 | 基础切片 [:] | 高级/花式索引 | 确保独立副本 |
|---|---|---|---|
| NumPy | 🔗 视图(会影响原数组) | 📋 副本 | arr[1:3].copy() |
| Pandas | 📋 副本(大多数情况) | 📋 副本 | df.iloc[1:3].copy() |
| Tensor | 🔗 视图(会影响原张量) | 📋 副本 | t[1:3].clone() |
四、常用操作 — 多维切片:怎么切出我想要的部分?
2D 数组:行和列同时切
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) arr[0:2, 1:3] # 行 0-1,列 1-2
# [[2, 3],
# [5, 6]]
选中区域示意(行列均从 0 开始索引):

常用 2D 切片示例
arr[:, 1] # 取所有行,第 1 列 → [2, 5, 8]
arr[0, :] # 取第 0 行,所有列 → [1, 2, 3]
arr[::2, :] # 每隔一行取一次 → 行 0, 行 2
Tensor 3D 切片:形状为 (样本数,行,列)
t = torch.zeros(3, 4, 5) # 3 个样本,每个样本 4 行 5 列 t[0] # 第 0 个样本,shape=(4,5)
t[0, :, :] # 等价写法,同上
t[:, 0, :] # 所有样本的第 0 行,shape=(3,5)
t[:, :, 0] # 所有样本的第 0 列,shape=(3,4)
Tensor 中 unsqueeze 与 squeeze — 维度的增与减
在深度学习中,经常遇到差一个维度的问题,这就需要 加套 unsqueeze() 和 解套 squeeze(),有些类似于切片及复原。
squeeze():删除指定位置长度为 1 的维度
只能删除 size 是 1 的维度,其他维度不受影响。
⚠️ 注:对长度 ≠ 1 的维度 squeeze 不会报错,只是保持原样不变。如果你发现 squeeze 后 shape 没变,需要检查一下目标维度的长度是否真的是 1。
t = torch.zeros(1, 3, 1, 4)
t.shape # → torch.Size([1, 3, 1, 4])t.squeeze().shape # → torch.Size([3, 4])
# 删除了所有长度为1的维度:dim0(1) 和 dim2(1)t.squeeze(0).shape # → torch.Size([3, 1, 4])
# 只删除 dim0t.squeeze(2).shape # → torch.Size([1, 3, 4])
# 只删除 dim2t.squeeze(1).shape # → torch.Size([1, 3, 1, 4])
# dim1 的长度是3 ≠ 1,无法删除,原样返回
图示:

unsqueeze():在指定位置插入长度为 1 的维度
unsqueeze(dim=k) 在第 k 维之前插入一个大小为 1 的维度:
unsqueeze(0)→ 在数组最外面包一层 → 常用于添加 batch 维度unsqueeze(1)→ 在每行前面加一个维度 → 常用于添加特征维度unsqueeze(2)→ 在每个元素最后加一格 → 常用于添加通道维度
t = torch.zeros(3, 4)t.shape # → torch.Size([3, 4])t.unsqueeze(0).shape # → torch.Size([1, 3, 4])
# 在 dim0 前面插入,变为:(1, 3, 4)t.unsqueeze(1).shape # → torch.Size([3, 1, 4])
# 在 dim1 位置插入,变为:(3, 1, 4)t.unsqueeze(2).shape # → torch.Size([3, 4, 1])
# 在 dim2(末尾)位置插入,变为:(3, 4, 1)t.unsqueeze(-1).shape # → torch.Size([3, 4, 1])
# -1 表示最后一个维度,等价于 unsqueeze(2)
图示:

注:unsqueeze(2) 中每个元素后面多了一个维度,而非"交替排列"。
五、常用操作 — 高级索引:花式索引与布尔索引
arr = np.array([10, 20, 30, 40, 50]) # 花式索引(传列表,指定位置)
arr[[0, 2, 4]] # → [10, 30, 50] 返回副本 # 布尔索引(传条件,筛选满足条件的元素)
arr[arr > 25] # → [30, 40, 50] 返回副本 即:arr[Flase,Flase,True,True,True]# Pandas 布尔索引
df[df['age'] > 18] # 等价写法:df.loc[df['age'] > 18] # 返回副本# Tensor 高级索引
indices = torch.tensor([0, 2, 4])
torch.index_select(t, dim=0, index=indices) # 返回副本
注:为什么不直接用
t[[0, 2, 4]]?
多维且组合索引时行为易混淆;可能意外触发广播/配对;可读性稍差。
index_select()语义完全明确,维度指定清晰,无歧义。
六、流程图
七、速查表:三者全对比
| 操作 | NumPy | Pandas | PyTorch Tensor | |
|---|---|---|---|---|
| 基础切片区间 | 🏷️ 左闭右开 | .loc 📌 左闭右闭.iloc 🏷️ 左闭右开df[:] 🏷️ 左闭右开(仅行) |
🏷️ 左闭右开 | |
| 取某一列 | arr[:, 1] |
df['col'] 或 df.iloc[:, 1] |
t[:, 1] 或 t[:, 1, :] |
|
| 取某一行 | arr[1] 或 arr[1, :] |
df.iloc[1] 或 df.loc[label] |
t[1] 或 t[1, :] |
|
| 基础切片返回 | 🔗 视图(修改影响原数组) | 📋 副本(大多数情况) | 🔗 视图(修改影响原张量) | |
| 花式/高级索引 | arr[[0,2]] → 📋 副本 |
df[['a','b']] → 📋 副本 |
index_select() → 📋 副本 |
|
| 强制取副本 | arr[1:3].copy() |
df.iloc[1:3].copy() |
t[1:3].clone() |
|
| 布尔筛选 | arr[arr > 0] |
df[df['x'] > 0] |
t[t > 0] |
八、 常见错误写法
针对 Pandas DataFrame
❌
df.iloc[0:3]['age'] = 0
链式赋值:先切片返回副本,再给副本的列赋值,原 DataFrame 不变。✅
df.iloc[0:3, df.columns.get_loc('age')] = 0
或
df.loc[df.index[0:3], 'age'] = 0
❌ 混淆
.loc和.iloc导致多选/少选行
如:DataFrame 行首的 0, 1, 2... 看起来像位置,其实可能是标签。✅ 标签用
loc(闭区间),位置用iloc(开区间)。
不确定时可打印df.index查看:
- 索引对象的类型:
Index、RangeIndex、DatetimeIndex等。- 具体标签值:可能是数字、字符串、日期时间等。
针对 PyTorch Tensor
❌
s = t[1:3]; s = s + 1(以为会修改原张量)✅
s = t[1:3]; s.add_(1)
或
t[1:3] += 1(原地操作才会影响原张量)
九、对比
方法对比:同一操作的三种实现
| 任务 | NumPy | Pandas | PyTorch Tensor |
|---|---|---|---|
| 创建数组 | np.array([1,2,3]) |
pd.Series([1,2,3]) |
torch.tensor([1,2,3]) |
| 取前 2 个元素 | arr[:2] |
series.iloc[:2] |
t[:2] |
| 条件筛选 | arr[arr > 1] |
series[series > 1] |
t[t > 1] |
| 求和 | arr.sum() |
series.sum() |
t.sum() |
| 变形 | arr.reshape(3,1) |
- | t.view(3,1) |
| 转置 | arr.T |
df.T |
t.t() |
切片性能对比
| 操作 | 说明 |
|---|---|
| NumPy 基础切片 | 最快,视图操作 |
| Pandas .iloc | 较快 |
| Pandas .loc | 标签查找开销 |
| Tensor 切片 | 与 NumPy 相当 |
💡 建议:在性能敏感场景优先使用 NumPy 或 Tensor,Pandas 适合数据探索阶段。
优势对比
NumPy 优势
- 性能最优,底层 C 实现
- 语法简洁,接近原生 Python
- 生态系统核心(Pandas/Tensor 都基于它)
- 适合数值计算和矩阵运算
Pandas 优势
- 标签索引,可读性强
- 处理缺失值方便
- 内置数据对齐和广播
- 适合数据清洗和探索性分析
Tensor 优势
- GPU 加速,适合大规模计算
- 自动微分,支持梯度反向传播
- 与 PyTorch 生态无缝集成
- 神经网络训练必备
总结
三个库的切片语法高度相似,但其实最需要注意的是以下三点:
df.loc[1:3]是左闭右闭,和其他所有写法都不一样- NumPy 和 Tensor 的基础切片是视图,改了切片会改原数据, Pandas 大多是副本。
- Pandas 链式赋值
df.iloc[0:3]['col'] = 0可能不生效,用df.iloc[0:3, n] = 0
📢 声明:本文借助AI辅助工具进行资料整理与初稿生成,所有内容均经过作者本人的详细核对、修改与编排,文责自负。
