NumPy vs Pandas vs 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, 4df[1:3] # 按行位置, 左闭右开 → 行 1, 行 2df.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] 99print(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] 99print(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, 行 2Tensor 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(dimk)在第 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, dim0, indexindices) # 返回副本注为什么不直接用t[[0, 2, 4]]多维且组合索引时行为易混淆可能意外触发广播/配对可读性稍差。index_select()语义完全明确维度指定清晰无歧义。六、流程图是否是否是否我要切片需要按「位置序号」选数据使用 iloc / NumPy切片 / Tensor切片区间规则: 左闭右开示例: df.iloc[1:3] , arr[1:3]需要按「标签/列名」选数据使用 .loc区间规则: 左闭右闭示例: df.loc[a:c] 包含 a,b,c需要按「条件」筛选使用 布尔索引示例: df[df[age]18] , arr[arr0]默认规则备忘录NumPy / Tensor 基础切片 视图Pandas 切片 副本⚠️ 避免链式赋值: df.iloc[][]...七、速查表三者全对比操作NumPyPandasPyTorch 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(原地操作才会影响原张量)九、对比方法对比同一操作的三种实现任务NumPyPandasPyTorch 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.Tdf.Tt.t()