尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

NumPy数组切片全攻略:语法、视图与性能优化

NumPy数组切片全攻略:语法、视图与性能优化 NumPy的数组切片是我日常用Python处理数据时躲不开的一个操作。不管是清洗数据、提取特征、切分训练集还是处理图像、做时间序列分析几乎每一步都在和切片打交道。可以说切片语法掌握得牢不牢直接决定了你写数据处理代码时是行云流水还是磕磕绊绊。这篇内容我就从实际使用角度出发把NumPy数组切片的语法规则、底层逻辑、常见坑点和实战技巧一次讲透希望能帮那些刚接触NumPy或者用了一段时间但总觉得切片这块还差点意思的朋友把这块短板彻底补上。我会先讲清楚切片最核心的语法规则和直观理解方式再深入多维数组切片、视图与副本的关系然后聊一聊布尔索引和花式索引这些进阶玩法最后整理一份我自己平时写代码时会注意的避坑清单和性能优化经验。整个内容会更适合已经有一点Python基础、想系统掌握NumPy切片玩法的人来读当然如果你是完全零基础只要跟着代码示例敲一遍也完全能上手。1. 切片语法基础一维数组的核心规则与直观理解1.1 最基本的切片形式start:stop:stepNumPy里最基础的切片语法继承自Python原生的序列切片形式是arr[start:stop:step]。我第一次用的时候总觉得这玩意儿像是切西瓜你得告诉别人从哪一头开始切、切到哪一块结束、每隔几刀取一块。三个参数里有两个需要特别留意start起始位置的索引包含这个位置本身可以不写不写就默认从开头开始。stop结束位置的索引注意这个位置是不包含的不写就默认一直到末尾。step步长也就是每隔多少个元素取一个不写就默认是1。这里最重要的就是左闭右开规则。写arr[1:5]表示的是取索引1、2、3、4这些位置上的元素注意不包括索引5。这个规则跟Python的range()函数、list切片完全一致所以如果你写过Python列表的切片这部分基本是无缝迁移。import numpy as np arr np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 取索引2到索引6不含6之间的元素 print(arr[2:6]) # 输出: [2 3 4 5] # 从开头取到索引5不含5 print(arr[:5]) # 输出: [0 1 2 3 4] # 从索引3取到末尾 print(arr[3:]) # 输出: [3 4 5 6 7 8 9] # 整个数组复制一个引用不是拷贝 print(arr[:]) # 输出: [0 1 2 3 4 5 6 7 8 9]有个面试和学习中常被拿来考的经典区别就是arr[2]和arr[2:3]不一样。前者返回的是数组里第2个位置的标量一个普通的数值后者返回的是一个长度为1的数组。这个区别在后续拼接数组、做逻辑判断的时候特别容易踩坑后面我会专门展开说。1.2 步长step的进阶玩法隔点取样与反转step参数平时用1比较多但真正体现切片威力的是不连续取样和反转操作。arr[::2]能从数组里每隔一个元素取一个这在处理大量数据需要降采样的时候特别实用。比如你有一个每秒采样的时间序列数据想把它降成每两秒采一次arr[::2]一步就能搞定完全不用写循环。arr np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 每隔一个元素取一个 print(arr[::2]) # 输出: [0 2 4 6 8] # 每隔两个元素取一个 print(arr[::3]) # 输出: [0 3 6 9] # 整个数组反转 print(arr[::-1]) # 输出: [9 8 7 6 5 4 3 2 1 0]反转这个操作我几乎天天用。做时间序列分析时经常需要把数据变成最新在前、最早在后做数据可视化画图时有时也需要把坐标轴数据翻转一下。arr[::-1]这一步比写循环或者用np.flip都来得更直接。这里要注意当step为负数时start和stop的默认值会发生变化。默认情况下arr[::-1]的意思是从末尾开始往前取此时start默认是数组末尾stop默认是开头之前。所以如果你写arr[5:1:-1]意思是从索引5开始往前取一直取到索引1不含1。换句话说负步长情况下左闭右开规则依然成立但方向完全反过来了。arr np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 从索引7开始往前取到索引2不含2 print(arr[7:2:-1]) # 输出: [7 6 5 4 3] # 从索引8开始往前取每隔一个元素取一个取到索引3不含3 print(arr[8:3:-2]) # 输出: [8 6 4]1.3 省略start和stop时的默认行为切片的时候如果省略参数很多人默认觉得就是取全部但在负步长的情况下判断会出错。arr[:]取全部没问题arr[::-1]也是取全部顺序相反这两者比较好理解。容易搞混的是arr[:5]和arr[5:]这类携带一个位置的情况——arr[:5]是从头取到索引5不含arr[5:]是从索引5取到末尾。还有一个隐藏得比较深的行为如果切片参数超出数组边界NumPy不会报错而是自动按边界截断。比如数组只有10个元素你写arr[3:100]得到的就是索引3到末尾的所有元素。这一点跟Python列表的行为一致但如果是其他编程语言转过来的朋友一开始可能会觉得不习惯。arr np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 超出边界的切片不会报错 print(arr[3:100]) # 输出: [3 4 5 6 7 8 9] # start超出边界结果为空数组 print(arr[100:200]) # 输出: []2. 多维数组切片真正拉开差距的地方2.1 逗号分隔各维度arr[行, 列]的含义一维切片的逻辑搞清楚后多维数组切片的核心是在各个维度上分别应用切片规则维度之间用逗号分隔。一个二维数组arr的形状是(m, n)那arr[i, j]就是第i行、第j列的元素arr[i, :]是第i行的所有列arr[:, j]是所有行的第j列。我当年学到这里时突然明白了为什么NumPy要独立设计一套切片语义而不是直接用Python的list套嵌——因为在高维数据里按维度切片的写法实在太自然了。你会觉得你不是在操作一段抽象的内存而是在操作一张表、一块矩阵、一个三维立体数据。# 创建一个3行4列的二维数组 arr_2d np.array([ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ]) # 取第1行所有列 print(arr_2d[1, :]) # 输出: [5 6 7 8] # 取所有行的第2列 print(arr_2d[:, 2]) # 输出: [3 7 11] # 取第0行到第2行不含2第1列到第3列不含3 print(arr_2d[0:2, 1:3]) # 输出: [[2 3] # [6 7]]2.2 多维切片的实操场景图像区域提取多维切片最常见的实际应用是图像处理。一张彩色图片在NumPy里通常表示成三维数组形状是(height, width, channels)其中channels通常是3RGB或4RGBA。你想裁剪图像中间的一个区域时不需要调用任何图像处理库直接用多维切片就能完成。# 假设img是形状为(480, 640, 3)的彩色图像数组 img np.random.randint(0, 255, size(480, 640, 3), dtypenp.uint8) # 裁剪出图像中央区域的坐标范围行100:400列200:500 cropped img[100:400, 200:500, :] # 把RGB三个通道翻转成BGROpenCV的经典需求 bgr img[:, :, ::-1] # 单独取出红色通道得到形状为(480, 640)的二维灰度图 red_channel img[:, :, 0]这里img[:, :, ::-1]这种写法就是三个维度同时使用切片语法前两个维度取全部最后一个维度用步长-1做通道翻转。这种一行代码完成的操作如果不用切片而用循环去写代码量会多出好几倍还很容易出错。三维数组切片时有一个容易混淆的点img[:, :, 0]返回的是二维数组去掉了通道维度而img[:, :, 0:1]返回的是三维数组通道维度保留只是长度变成1。如果你的后续操作比如np.concatenate拼接要求维度匹配这个区别会直接决定你的代码能不能跑通。2.3 标量索引、切片索引和省略号混用多维切片时经常要混合使用标量索引和切片索引。arr_2d[1, 0:2]的意思是取第1行第0列到第2列不含2结果是长度为2的一维数组。arr_2d[0:2, 1]是取第0行到第2行不含2的第1列结果也是长度为2的一维数组。这两种写法虽然结果形状相同但在更高维度数据里不同位置使用标量索引会把那个维度“挤掉”这个行为会直接影响你拿到的结果维度。三维数组里就更好理解了。假设有一个形状为(2, 3, 4)的三维数组arr[1, :, :]得到的是形状为(3, 4)的二维数组arr[1, 2, :]得到的是形状为(4,)的一维数组arr[1, 2, 3]则直接是一个标量。NumPy还提供了一个特殊的省略号对象...可以帮助你在有多个维度时简化切片。它表示“在这里展开所有未显式指定的维度”效果跟写一串冒号等价。比如arr[..., 1]等价于arr[:, :, 1]只需要三个维度时两种写法没啥区别但如果是十维数组用省略号能省去一大段冒号。arr_3d np.arange(24).reshape(2, 3, 4) # 等价写法取所有批次、所有行的第2列 a arr_3d[:, :, 2] b arr_3d[..., 2] # 等价写法取第1个批次的所有数据 c arr_3d[1, :, :] d arr_3d[1, ...] # 取最后一个维度的最后一列 e arr_3d[..., -1]我个人的使用习惯是维度不超过3时尽量写得明确一点把每个维度的冒号都写出来可读性更好维度超过4时就用省略号不然一长串冒号看着累也容易数错维度数。3. 视图与副本切片最容易踩的坑3.1 基本切片返回的是视图不是拷贝这是NumPy切片和Python列表切片之间最本质的区别也是最容易被忽视的一个特性。基础切片用冒号写的切片返回的是原数组的一个视图也就是说新数组和原数组共享同一块底层数据。修改新数组里的元素原数组也会跟着变。arr np.array([0, 1, 2, 3, 4, 5]) # 对数组做基础切片 view arr[1:4] # 修改视图里的元素 view[0] 100 # 原数组也会跟着变 print(arr) # 输出: [0 100 2 3 4 5]这个特性刚接触时特别容易造成难以发现的bug。我曾经在处理一批科学实验数据时把一个切片结果存成变量后续做了一堆归一化处理等回头检查原始数据时发现原始数据已经被改得面目全非了。当时排查了很久才意识到问题不在算法逻辑而是切片返回的是视图我在不知情的情况下把原始数据污染了。3.2 哪些切片操作会返回副本那么什么情况下能得到一份独立的数据呢有两种常用方法一是显式调用copy()方法二是使用高级索引比如布尔索引、整数数组索引。高级索引后面我会单独讲这里先记住一个关键点尽量在需要独立数据时显式copy不要把副本这件事交给运气。arr np.array([0, 1, 2, 3, 4, 5]) # 显式拷贝 copy_view arr[1:4].copy() # 修改拷贝不影响原数组 copy_view[0] 100 print(arr) # 输出: [0 1 2 3 4 5] # 布尔索引返回的是副本 bool_result arr[arr 3] # 输出: [4 5] bool_result[0] 999 print(arr) # 输出: [0 1 2 3 4 5] 原数组没变这里还要提一个容易被忽略的细节无论是arr[:]还是arr[::]只要是用冒号做的基础切片返回的都是视图不是拷贝。如果你只是想把数组整个复制一份做后续实验直接写arr[:]是不够的必须写arr[:].copy()或者arr.copy()。3.3 视图机制的性能优势和内存优势视图机制虽然有改原数组的风险但它带来的好处是性能上的巨大提升。因为视图不复制数据只创建一个新的数组对象来引用同一份底层数据所以对大数组做切片几乎是零开销操作。一个几GB的数组切片操作耗时就几微秒完全不会因为数据量大而变慢。我在处理大规模数据集时就特别依赖这个特性。比如一个内存占用接近极限的超大矩阵我需要频繁提取其中某些行列做中间计算。如果不是视图机制每次提取都要复制一份内存很快就爆了有了视图我可以放心地对同一个底层数据做无数个“窗口”操作内存占用始终维持在一份数据的水平上。在数据流水线里这个特性也很有用。你可以在不复制数据的情况下对原数组的不同子区域做并行或顺序处理所有中间结果都共享底层存储。但前提是你必须心里有数哪些操作是视图哪些操作是拷贝不然很容易在不知不觉中留下状态污染。3.4 防止视图污染的安全操作习惯既然视图有污染风险那实际项目中如何平衡安全和性能我的经验是两个原则第一只读场景下放心用视图。如果你只是做查询、分析、提取统计量不打算修改切片结果就用基础切片零拷贝效率最高。第二要修改切片结果时先.copy()再操作。特别是当你拿到一个切片后不知道后续逻辑会不会修改它最稳妥的做法是在赋值给变量时立即决定这个变量是要做独立数据的还是只做参考的。如果是前者立刻.copy()绝不拖延。# 推荐做法需要独立数据时立即copy train_data full_data[:8000].copy() val_data full_data[8000:10000].copy() test_data full_data[10000:].copy() # 不推荐的做法等到后面修改时才发现是引用 # train_data full_data[:8000] # ... 做了一堆操作后 # train_data[0] xxx # 这里是修改原数组bug源4. 高级索引技巧布尔掩码与花式索引4.1 布尔索引按条件筛选数据基础切片的索引都是基于位置的但实际工作中我们经常需要按条件筛选数据。比如从一个成绩列表里筛出所有及格的学生、从传感器数据中挑出所有超过某个阈值的读数。这种需求用布尔索引来做是最直观的。布尔索引的用法是将一个布尔数组放在方括号里NumPy会保留所有布尔值为True的位置上的元素返回结果是一个一维数组即使原数组是多维的。arr np.array([12, 5, 8, 21, 3, 17]) mask arr 10 print(mask) # 输出: [ True False False True False True] # 用布尔掩码筛选满足条件的元素 print(arr[mask]) # 输出: [12 21 17] # 更简洁的写法 print(arr[arr 10]) # 输出: [12 21 17]布尔索引支持与、或、非逻辑的组合。多维数组做布尔索引时也遵循同样的逻辑但需要特别注意的是掩码数组的形状必须跟原数组一致或者至少能广播兼容。如果不匹配NumPy会抛出IndexError这在代码调试时是个高频报错。arr np.array([12, 5, 8, 21, 3, 17]) # 多个条件组合 print(arr[(arr 5) (arr 20)]) # 输出: [12 8 17] print(arr[(arr 20) | (arr 5)]) # 输出: [21 3] # 注意 和 | 必须加括号不能写 arr 5 arr 20 # 因为运算符优先级会导致语法错误4.2 花式索引用整数数组定位元素花式索引指的是用一个整数数组作为索引取对应位置上的元素。跟基础切片不同花式索引返回的是副本不是视图。它可以实现很多基础切片做不到的取数操作比如按特定顺序取元素、取不同行不同列的组合等。arr np.array([10, 20, 30, 40, 50]) # 用整数数组指定取数顺序 idx np.array([4, 2, 0]) print(arr[idx]) # 输出: [50 30 10] # 同一个索引可以重复使用 print(arr[[1, 1, 2, 2]]) # 输出: [20 20 30 30]二维数组的花式索引更复杂一些。当你写arr[[0, 2], [1, 3]]时NumPy会把这两个索引数组看作坐标的配对取的是(0, 1)和(2, 3)这两个位置上的元素而不是取所有行列的交叉乘积。想取交叉乘积需要用np.ix_函数帮忙。arr_2d np.array([ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ]) # 配对取数取(0, 1)和(2, 3)两个位置 print(arr_2d[[0, 2], [1, 3]]) # 输出: [2 12] # 用np.ix_取行和列的交叉 print(arr_2d[np.ix_([0, 2], [1, 3])]) # 输出: [[2 4] # [10 12]]4.3 高级索引与基础切片的混合使用实际项目中很少只用纯基础切片或纯高级索引更多时候是两者混着用。比如处理一批图像数据时数据形状是(N, H, W, C)你可能想取前100张图的左上角50x50区域同时只保留R通道。这里就用到了基础切片和高级索引的组合。img_batch np.random.randint(0, 255, size(100, 64, 64, 3), dtypenp.uint8) # 取前50张图左上角32x32区域只取R通道 region img_batch[:50, :32, :32, 0]混合使用时有一个要特别注意的点如果在一个切片表达式中同时使用基础切片和高级索引结果的维度顺序可能会有变化。NumPy的官方文档里专门有大量篇幅描述这种混合索引的维序规则。实际开发中为了避免困惑我常用的方式是分两步走先用基础切片提取区域再用高级索引做筛选。这样每一步的结果都容易理解调试也更友好。# 分步操作先提取前景区域再做条件筛选 frame img_batch[10:20, :, :, :] bright_pixels frame[frame.max(axis-1) 200]4.4 用切片的扩展技巧实现数据增强布尔索引和花式索引在很多领域都有广泛用途我这里举一个比较实用的案例数据增强中的随机采样。在训练深度学习模型时经常需要从数据集中随机抽取一批样本同时做打乱操作。用花式索引配合np.random.permutation可以一行代码完成。data np.arange(100).reshape(50, 2) # 随机打乱行顺序 perm np.random.permutation(data.shape[0]) shuffled data[perm] # 随机抽样20行 sample_indices np.random.choice(data.shape[0], size20, replaceFalse) sample data[sample_indices]这里最需要注意的是np.random.permutation返回的是打乱后的整数数组它能作为花式索引直接使用np.random.choice返回的抽样索引也同理。两者本质都是整数数组索引返回的都是副本所以不会污染原数据。这种用法在数据管线的预处理环节里几乎成了我固定的代码模板。5. 常见问题与性能优化实录5.1 高频报错排查速查表我整理了一下自己带新人和看开源代码时遇到的、关于NumPy切片最常见的坑做成一张速查表。这里的每一条我都实际碰到过不是从文档里抄来的空话。报错类型典型场景原因解决方案IndexError: too many indices for array对一维数组写arr[1:3, 2]维度数量超过了数组本身维度检查数组形状确认每个逗号分隔的切片对应一个存在的维度IndexError: boolean index did not match indexed array布尔掩码形状跟数组不一致布尔数组和原数组形状不匹配打印掩码形状和原数组形状对齐后再用ValueError: shape mismatch多维数组配对索引时数组长度不相等花式索引传入了长度不匹配的索引数组确认所有索引数组长度一致或使用np.ix_构造网格索引修改切片影响了原数组修改arr[1:5]后原数组也变了基础切片返回的是视图数据共享底层存储需要独立数据时显式调用.copy()IndexError: arrays used as indices must be of integer (or boolean) type把浮点数数组当索引索引数组必须是整数类型或布尔类型用astype(np.int64)显式转换索引数组结果形状低于预期用arr[:, 0]取列却得到一维数组标量索引会“挤掉”对应维度想保留维度时改用arr[:, 0:1]或arr[:, [0]]这些报错信息看着吓人其实定位起来都不难。我的排查习惯是先打印数组的形状再打印切片器的形状确认维度匹配后再看类型。绝大多数问题都出在这两步检查没做扎实。5.2 切片与内存布局为什么连续切片更快视图机制不仅在语义上有影响在性能上也有深层影响。NumPy数组在内存中的存储方式主要有两种C连续行优先和F连续列优先。切片操作的结果可能会改变数组的内存连续性进而影响后续计算的性能。arr np.arange(10000).reshape(100, 100) # 取所有行的一列 col_view arr[:, 5] # 检查是否为C连续 print(col_view.flags[C_CONTIGUOUS]) # 很可能输出 False当数组不再是连续内存时很多底层优化比如向量化运算、BLAS库调用就无法发挥最大效率。如果某个切片结果要参与大规模循环计算但又不需要依赖原数组的共享内存把切片转成连续数组往往能带来性能提升。# 对切片结果做连续化处理提升后续计算性能 col_view arr[:, 5] col_contig np.ascontiguousarray(col_view)需要提醒的是np.ascontiguousarray在数组已经是连续内存时不会复制数据只有在需要时才复制所以这个操作的开销通常很小。在写高性能计算代码时这个细节往往是压垮性能的最后一根稻草——一个看似普通的列切片会让后续循环变慢好几倍。5.3 切片赋值的高效玩法切片不仅能用于读取数据还能高效地批量赋值。这个特性在做数据填充、预处理时非常有用。比如把数组中某个区域的数据统一换成固定值、按区域执行不同的变换逻辑用切片赋值可以比逐元素循环快几个数量级。arr np.zeros((10, 10)) # 把第二行到第四行、第三列到第五列的区域全部填成1 arr[2:5, 3:6] 1 # 用另一个数组给切片区域赋值需要形状匹配 arr[0:2, 0:2] np.array([[5, 6], [7, 8]]) # 用切片布尔索引做条件赋值 arr[arr 5] 0切片的赋值和读取都遵循同样的视图语义——把一个值赋给一个切片区域实际上是在修改原数组的对应区域。这个特性用来做数据清洗特别顺手比如把所有异常值统一替换为np.nan一行代码就能完成。sensor_data np.random.randn(1000, 5) # 把所有超过3倍标准差的异常值替换为缺失值 threshold 3 * sensor_data.std() sensor_data[sensor_data threshold] np.nan5.4 从真实项目里提炼的切片性能建议最后分享几个我在真实项目里总结出来的切片性能建议。这些经验不一定能在文档里直接找到但对写高效的数据处理代码很有帮助。建议一能用切片就用切片别写循环。NumPy的切片操作在底层是用C语言实现的效率远高于Python层级的for循环。一个100万元素的数组用切片提取子集只需要微秒级时间而用循环可能要几十毫秒甚至更慢。建议二批量提取时用多维切片不要用多个一维切片叠加。比如需要取一个矩阵的所有奇数行和所有偶数列直接写arr[1::2, ::2]比先取行再取列要高效因为后者可能会产生中间数组。建议三对超大数组做切片后如果结果要长期保留且不再修改原数组及时.copy()并释放原引用。因为视图会让原数组的底层数据一直驻留在内存中哪怕原变量已经被删除只要视图还在底层数据就得不到释放。在一个很大的数据集上反复做视图操作而不copy很容易导致内存泄漏。建议四想要性能排查时用timeit对比不同写法的耗时。我经常在两种写法之间犹豫时直接拿一小段数据测一下让数据说话。比如arr[:, 1]和arr[:, 1:2]虽然看着差别不大但在某些计算链路里维度差一位会导致后续广播行为完全不同最终影响性能。建议五切片参数尽量用变量而不是硬编码数字。比如切片时用batch_size而不是直接写64用row_start而不是写100。硬编码数字在代码写出来的一刻看起来简洁一旦需要改动就得翻遍全项目找数字还容易找漏。我自己有一阵子就因为硬编码导致改了数据集大小后有几处切片越界的问题没有立刻暴露等到数据量变化时才在线上环境爆出来那叫一个头疼。5.5 一个综合案例时间序列的滑动窗口提取把这篇文章提到的知识点串起来我用一个实际场景做演示从一段长时间序列数据中提取滑动窗口。这是股票价格分析、脑电信号处理、气象预测等各类时间序列建模任务中都会遇到的需求。# 假设有一段1000个时间步的温度传感器数据 temperature np.sin(np.linspace(0, 20, 1000)) np.random.normal(0, 0.1, 1000) # 窗口大小为20步长为1提取所有滑动窗口 window_size 20 # 用切片步长实现窗口提取 windows np.array([temperature[i:i window_size] for i in range(0, len(temperature) - window_size 1)]) print(windows.shape) # 输出: (981, 20)这里用了一个列表推导式配合切片来生成所有窗口。990个窗口每个20个数据点用切片提取是线性复杂度速度很快。如果数据量到达百万级别还可以用np.lib.stride_tricks.sliding_window_view来生成视图形式的滑动窗口完全避免复制数据内存占用会大幅下降。# 用stride_tricks生成视图形式的滑动窗口不复制数据 from numpy.lib.stride_tricks import sliding_window_view windows_view sliding_window_view(temperature, window_size) print(windows_view.shape) # 输出: (981, 20) # 这个窗口数组的每个元素都是原数组数据的视图这种高级窗口提取方式本质上就是利用了NumPy数组的底层内存布局——相邻窗口之间共享大量数据所以用视图表达最合适。不过要提醒一下sliding_window_view返回的也是一个视图修改它同样会影响原数组如果你后续要在窗口数据上做独写操作尽量拷贝一份再处理。写在最后的个人经验NumPy切片这块内容我真是边用边学边踩坑踩过来的。现在回头看最关键的不是记住每个语法规则而是建立两个直觉第一个直觉是切片操作的结果到底是视图还是副本这个判断能避免绝大多数数据被莫名污染的bug第二个直觉是结果到底是一维还是二维、是标量还是数组这个判断能避免很多接口调用时的维度报错。如果你正在学习NumPy我建议拿到任何数组后都强制自己先打印.shape和.ndim再去做切片操作。把这两个属性变成条件反射之后你会发现自己写数据处理代码的速度快一倍debug的时间少三倍。切片这件事练多了自然就熟练了——找一份真实的数据集把“提取第N行”、“筛选满足条件的行”、“对某列做变换”这些操作各写几遍一周之内就能形成肌肉记忆。
返回列表