ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Numpy核心指南:从环境搭建到向量化实战,掌握Python数据分析基石

Numpy核心指南:从环境搭建到向量化实战,掌握Python数据分析基石 1. 从“数据搬运工”到“数据操盘手”为什么Numpy是绕不开的基石如果你刚开始接触Python数据分析或者已经用Pandas、Matplotlib画过一些图表但总感觉在处理数据时有点“使不上劲”比如循环慢、内存大、代码啰嗦那么你大概率是时候回头好好认识一下Numpy了。很多人把Numpy看作一个“数学库”或“数组库”这没错但理解浅了。在我十多年的数据处理经历里Numpy更像是一个高性能的数据结构引擎它定义了现代Python科学计算和数据分析的底层游戏规则。Pandas的DataFrame底层是Numpy数组Scikit-learn的模型输入输出是Numpy数组OpenCV的图像在内存里也是Numpy数组。不理解Numpy你就永远在调用别人的“黑盒”函数一旦遇到性能瓶颈或需要自定义复杂计算时就会束手无策。最近的热搜词很有意思“python安装numpy库的方法”、“pycharm安装numpy时提示‘pip...’”、“numpy版本与python版本的关系”。这恰恰反映了新手入门的第一道坎环境。而“numpy 布尔索引”、“numpy 计算matplotlib画的方块邻居元素之和”则指向了核心应用场景。至于“attributeerror: module ‘numpy’ has no attribute ‘product’”那是典型的版本兼容性坑。今天我们不只讲怎么安装更要讲清楚Numpy的核心设计哲学、那些让你代码效率提升十倍的“向量化”操作以及如何避开实际项目中常见的那些坑。无论你是要做销售数据分析、商业智能报表还是搞机器学习、图像处理这篇文章都会帮你把Numpy这把“瑞士军刀”磨得又快又亮。2. 环境搭建与版本管理的“隐形陷阱”安装Numpy听起来是pip install numpy一句话的事但为什么那么多人会卡在这里因为Python的环境管理本身就是一门学问。那些“无法识别pip命令”、“安装失败”的错误根源往往不在Numpy本身。2.1 Python版本与Numpy版本的“联姻”首先必须明确一个原则Numpy的版本严重依赖于你的Python解释器版本。这不是建议而是强制约束。Numpy作为一个包含大量C语言扩展的高性能库其预编译的轮子whl文件是针对特定的Python版本和操作系统编译的。例如你用Python 3.11新建了一个环境却试图安装一个仅支持到Python 3.8的旧版Numpy那么pip很可能找不到兼容的预编译包转而尝试从源代码编译。对于绝大多数用户来说从源码编译Numpy是一个灾难性的过程因为它依赖C编译器如Windows上的Visual Studio Build Tools和科学计算库如BLAS/LAPACK极易失败。我的实操心得对于新手最稳妥的方案是使用最新的、稳定的版本组合。截至当前Python 3.10或3.11搭配Numpy 1.24是一个广泛兼容且性能良好的选择。在创建新项目时首先用python --version确认解释器版本然后再安装Numpy。2.2 虚拟环境非可选是必选直接在系统Python里安装包是万恶之源。今天装Numpy明天装TensorFlow版本冲突、依赖污染会让你未来的每一步都举步维艰。虚拟环境Virtual Environment是Python项目的“隔离病房”。对于数据分析项目我强烈推荐使用conda尤其是通过Miniconda或Anaconda安装来管理环境。conda不仅管理Python包还能管理Python解释器本身和非Python依赖如MKL数学库这对于科学计算栈的兼容性至关重要。# 使用conda创建并激活一个名为data_analysis的环境并指定Python版本 conda create -n data_analysis python3.10 conda activate data_analysis # 在激活的环境中安装numpyconda会自动解决依赖通常会安装与Intel MKL集成的版本以获得更好性能 conda install numpy如果你坚持使用纯pip那么venv是标准选择# 创建虚拟环境 python -m venv my_venv # 激活环境Windows my_venv\Scripts\activate # 激活环境macOS/Linux source my_venv/bin/activate # 安装numpy pip install numpy2.3 解决“pip无法识别”与安装失败热搜词里的错误提示“无法将‘pip’项识别为 cmdlet、函数、脚本文件或可运行程序”根本原因是系统Path环境变量中没有包含pip所在的脚本目录。解决方案链条确认Python已正确安装并添加到Path安装Python时务必勾选“Add Python to PATH”。如果已经安装但未勾选需要手动将Python安装目录和Python安装目录\Scripts添加到系统的环境变量Path中。重启终端修改环境变量后必须关闭并重新打开命令行终端CMD、PowerShell、Terminal新的Path才会生效。使用模块方式调用pip如果环境变量问题一时难以解决可以使用python -m pip来替代pip命令。这是最可靠的调用方式因为它明确指定了使用当前Python解释器下的pip模块。python -m pip install numpy使用国内镜像源加速网络超时是安装失败的另一个常见原因。使用国内镜像可以极大提升成功率。pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple2.4 验证安装与探索环境安装成功后不要急着写代码。先做一个快速验证和探索。# 验证安装和版本 import numpy as np print(np.__version__) # 输出如1.24.3 print(np.__config__.show()) # 查看numpy的编译配置比如是否使用了MKL加速库 # 创建一个简单的数组测试基本功能 arr np.array([1, 2, 3]) print(arr)这个np.__config__.show()非常重要。它会告诉你Numpy底层使用的数学库。如果你看到mkl_info或blas_mkl_info恭喜你你的Numpy正在使用Intel Math Kernel Library (MKL)进行加速这在处理大规模矩阵运算时会有显著性能优势。如果看到openblas_info那也是性能不错的BLAS实现。如果显示library_dirs []可能使用的是参考实现性能会差一些。3. 理解核心ndarray与“向量化”思维安装只是第一步理解Numpy的核心对象ndarrayN-dimensional arrayN维数组及其设计哲学才是从“会用”到“精通”的关键。这是与Python原生列表list最根本的区别。3.1 ndarray不只是“列表的升级版”Python的列表非常灵活可以存放任意类型的对象。但这种灵活性是有代价的每个列表元素都是一个完整的Python对象包含值、类型信息、引用计数等元数据。当进行数值计算时CPU需要不断地进行类型检查和间接寻址效率极低。Numpy的ndarray则完全不同同质数据类型数组中的所有元素必须是相同的数据类型如int32,float64。这允许Numpy在内存中分配一块连续的内存空间来存储数据。预编译操作Numpy的核心函数如np.add,np.multiply是用C语言编写的它们直接在这块连续的内存上进行操作避免了Python层的循环和类型检查开销。广播机制这是Numpy“向量化”运算的魔法所在允许不同形状的数组进行数学运算。让我们看一个经典的性能对比解释“为什么不用循环”import numpy as np import time # 创建一千万个元素的数据 size 10_000_000 python_list list(range(size)) numpy_array np.arange(size) # Python循环求和 start time.time() sum_list 0 for i in python_list: sum_list i py_time time.time() - start print(fPython loop sum: {sum_list}, time: {py_time:.4f}s) # Numpy向量化求和 start time.time() sum_np numpy_array.sum() np_time time.time() - start print(fNumpy vectorized sum: {sum_np}, time: {np_time:.4f}s) print(fSpeedup: {py_time / np_time:.1f}x)在我的测试机上Numpy的速度通常是Python循环的50到100倍。这个差距随着数据量增大会更加惊人。所以第一条黄金法则尽可能将操作转化为对整个数组的向量化操作避免显式的Python循环。3.2 创建数组的多种姿势与内存考量创建数组是第一步但不同的创建方式有不同的含义和内存影响。# 1. 从列表/元组创建最常用 arr1 np.array([1, 2, 3, 4]) # 一维数组 arr2 np.array([[1, 2], [3, 4]]) # 二维数组 arr3 np.array([1, 2.0, 3]) # 注意包含浮点数整个数组会向上转型为float64 # 2. 使用内置函数创建高效避免中间列表 arr_zeros np.zeros((3, 4)) # 创建3行4列的全0数组默认float64 arr_ones np.ones((2, 3, 4), dtypenp.int32) # 创建2x3x4的全1数组指定int32类型 arr_full np.full((2, 2), 7) # 创建2x2的全为7的数组 arr_eye np.eye(5) # 创建5x5的单位矩阵 # 3. 生成序列替代range arr_arange np.arange(0, 10, 2) # [0, 2, 4, 6, 8]类似于range但生成数组 arr_linspace np.linspace(0, 1, 5) # [0., 0.25, 0.5, 0.75, 1.]等间隔数列包含终点 # 4. 从已有数据创建注意视图与拷贝 original np.array([1, 2, 3, 4, 5]) view_of_original original[1:4] # 这是一个“视图”共享底层数据 copy_of_original original[1:4].copy() # 这是一个“拷贝”数据独立这里需要深入理解视图view和拷贝copy。Numpy为了效率很多切片操作返回的是原数据的视图而不是一份新拷贝。这意味着修改视图原始数据也会变这既是性能优化的利器也是滋生bug的温床。a np.array([1, 2, 3, 4, 5]) b a[1:4] # b是a的一个视图 b[0] 999 print(a) # 输出[ 1 999 3 4 5] 原始数据被修改了 # 如果你不希望修改原始数据必须显式拷贝 c a[1:4].copy() c[0] 0 print(a) # 输出[ 1 999 3 4 5]原始数据不变3.3 索引与切片高效数据访问的钥匙Numpy的索引功能强大到令人发指远超普通列表。基础切片和Python列表类似但可以同时处理多个维度arr np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) print(arr[0, 1]) # 输出 2第0行第1列 print(arr[1]) # 输出 [5, 6, 7, 8]第1行一个一维数组 print(arr[:, 2]) # 输出 [ 3 7 11]所有行的第2列 print(arr[0:2, 1:3]) # 输出 [[2 3] # [6 7]]一个2x2的子数组布尔索引Boolean Indexing这是热搜词里提到的一个核心特性也是数据分析中过滤数据的利器。arr np.array([1, 2, 3, 4, 5, 6]) # 创建一个布尔数组条件为真时选择对应元素 mask arr 3 print(mask) # 输出 [False False False True True True] filtered arr[mask] # 或直接 arr[arr 3] print(filtered) # 输出 [4 5 6] # 更复杂的条件组合 filtered_complex arr[(arr 2) (arr 6)] # 注意必须用 , |, ~而不是 and, or, not print(filtered_complex) # 输出 [3 4 5]花式索引Fancy Indexing使用整数数组进行索引可以一次性获取任意位置的元素。arr np.array([10, 20, 30, 40, 50]) indices [0, 2, 4] print(arr[indices]) # 输出 [10 30 50] # 在多维数组中更强大 arr2d np.array([[1, 2], [3, 4], [5, 6]]) row_idx [0, 1, 2] col_idx [1, 0, 1] print(arr2d[row_idx, col_idx]) # 输出 [2, 3, 6] (取(0,1), (1,0), (2,1)位置的元素)4. 核心运算与广播机制向量化的魔法掌握了索引我们就可以对数据进行操作了。Numpy的运算分为两类元素级运算和矩阵运算。4.1 元素级运算Element-wise这是最自然的运算方式运算符,-,*,/,**或函数np.sin,np.exp会应用到数组的每一个元素上。a np.array([1, 2, 3]) b np.array([4, 5, 6]) print(a b) # [5 7 9] print(a * b) # [4 10 18] print(a ** 2) # [1 4 9] print(np.sin(a)) # [0.84147098 0.90929743 0.14112001]这里没有循环但得到了循环的结果。CPU的SIMD指令集可以一次性对多个数据执行相同的操作这是向量化性能提升的硬件基础。4.2 广播机制Broadcasting打破形状限制广播是Numpy中最强大也最容易让人困惑的特性之一。它的核心规则是当两个数组的形状在某些维度上不匹配时Numpy会自动扩展“广播”较小数组的维度使其与较大数组的形状兼容以便进行元素级运算。规则可以简化为两条从尾部维度最右边开始对齐维度大小要么相等要么其中一个为1。缺失的维度在较小数组的左边可以视为1。看例子比看规则更直观# 案例1标量与数组最简单 arr np.ones((3, 4)) print(arr 5) # 标量5被广播为形状(3,4)的全5数组 # 案例2一维数组与二维数组 arr np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3) row_vector np.array([10, 20, 30]) # 形状 (3,) # 对齐 (2,3) 和 (3,) - 将(3,)视为(1,3) - 广播为(2,3) print(arr row_vector) # 输出[[11 22 33] # [14 25 36]] # 案例3列向量与二维数组 col_vector np.array([[10], [20]]) # 形状 (2, 1) # 对齐 (2,3) 和 (2,1) - 将(2,1)广播为(2,3) print(arr col_vector) # 输出[[11 12 13] # [24 25 26]] # 案例4无法广播的例子 bad_vec np.array([10, 20]) # 形状 (2,) # 尝试对齐 (2,3) 和 (2,) # 从右对齐3 和 2 不相等且都不是1 - 报错 # arr bad_vec # ValueError: operands could not be broadcast together...广播机制使得我们无需编写循环就能优雅地实现诸如“矩阵的每一行减去该行的均值”、“为图像的每个颜色通道加上一个常数”等操作。理解广播是写出高效、简洁Numpy代码的关键。4.3 聚合函数从数据中提取信息数据分析离不开统计。Numpy提供了丰富的聚合函数用于沿某个轴axis或整个数组进行计算。arr np.array([[1, 2, 3], [4, 5, 6]]) print(np.sum(arr)) # 21所有元素的和 print(np.mean(arr)) # 3.5所有元素的均值 print(np.std(arr)) # 标准差 print(np.min(arr), np.max(arr)) # 1, 6 # 指定轴axis操作 # axis0 表示沿着行的方向向下即对每一列进行操作 print(np.sum(arr, axis0)) # [5 7 9] (14, 25, 36) # axis1 表示沿着列的方向向右即对每一行进行操作 print(np.sum(arr, axis1)) # [6 15] (123, 456)记住axis的一个技巧指定的轴axis会被“消灭”或“压缩”。arr.shape是(2,3)np.sum(arr, axis0)后第0维大小为2被聚合掉了结果的形状是(3,)。5. 实战从数据处理到解决具体问题理论说再多不如动手解决一个实际问题。我们结合热搜词“numpy 计算matplotlib画的方块邻居元素之和”来设计一个场景。假设我们有一个由Matplotlib绘制的热力图本质是一个二维数组我们需要计算图中每个元素的“邻居”上下左右之和。5.1 问题定义与数组模拟首先我们模拟一个简单的5x5网格数据可以想象成一张小型图像或地形高度图import numpy as np # 模拟一个5x5的网格数据 grid np.arange(25).reshape(5, 5) print(原始网格) print(grid)输出原始网格 [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19] [20 21 22 23 24]]我们的目标是对于grid中的每一个元素grid[i, j]计算其四个直接邻居上[i-1, j]、下[i1, j]、左[i, j-1]、右[i, j1]的和。边界上的元素邻居不足我们采用最简单的处理方式忽略不存在的邻居即视为加0。5.2 低效的循环实现反面教材新手可能会用双重循环def neighbor_sum_loop(grid): rows, cols grid.shape result np.zeros_like(grid) for i in range(rows): for j in range(cols): total 0 if i 0: # 上邻居 total grid[i-1, j] if i rows - 1: # 下邻居 total grid[i1, j] if j 0: # 左邻居 total grid[i, j-1] if j cols - 1: # 右邻居 total grid[i, j1] result[i, j] total return result result_loop neighbor_sum_loop(grid) print(循环计算结果) print(result_loop)这个方法逻辑清晰但效率极低。如果grid是1000x1000的图片就需要10亿次循环判断慢得无法接受。5.3 高效的向量化实现核心技巧思路是利用数组切片和移位操作一次性计算所有位置的上、下、左、右邻居和。def neighbor_sum_vectorized(grid): # 初始化一个全零数组用于存放结果 result np.zeros_like(grid) rows, cols grid.shape # 计算上邻居的和将grid整体向下移动一行第一行没有上邻居用0填充 # grid[1:, :] 取第1行到最后一行即原网格去掉第一行 # 将其加到result的[0:-1, :]位置即原网格去掉最后一行 result[0:-1, :] grid[1:, :] # 上邻居贡献 # 计算下邻居的和将grid整体向上移动一行最后一行没有下邻居 result[1:, :] grid[0:-1, :] # 下邻居贡献 # 计算左邻居的和将grid整体向右移动一列第一列没有左邻居 result[:, 0:-1] grid[:, 1:] # 左邻居贡献 # 计算右邻居的和将grid整体向左移动一列最后一列没有右邻居 result[:, 1:] grid[:, 0:-1] # 右邻居贡献 return result result_vec neighbor_sum_vectorized(grid) print(向量化计算结果) print(result_vec)输出向量化计算结果 [[ 6 12 18 24 20] [20 30 36 42 38] [40 60 72 84 76] [60 90 108 126 114] [56 88 106 124 112]]验证一下中心点grid[2,2]12它的邻居是7, 11, 13, 17和为48。看result_vec[2,2]72不对等等我们算错了。仔细看我们的逻辑result初始为0然后分别加上了四个移位后的grid。这意味着对于中心点我们加上了它作为别人邻居时的值而不是它的邻居的值。我们需要重新思考对于位置(i,j)它的上邻居是grid[i-1, j]。在我们的方法中grid[1:, :]是原网格去掉第一行当i0时grid[1:, :][i-1, j]实际上等于grid[i, j]。我们加错了对象。5.4 修正向量化实现正确的逻辑是将原网格的四个移位版本分别代表上、下、左、右方向的邻居视图相加。但要注意边界处理我们需要对移位后的数组进行填充。def neighbor_sum_vectorized_correct(grid): rows, cols grid.shape # 创建四个数组分别代表上、下、左、右方向的邻居视图边界处用0填充 # 上邻居视图将grid向下移一行最上面一行补0 up np.zeros_like(grid) up[1:, :] grid[:-1, :] # 原grid的第0行到倒数第二行放到up的第1行到最后 # 下邻居视图将grid向上移一行最下面一行补0 down np.zeros_like(grid) down[:-1, :] grid[1:, :] # 原grid的第1行到最后一行放到down的第0行到倒数第二行 # 左邻居视图将grid向右移一列最左边一列补0 left np.zeros_like(grid) left[:, 1:] grid[:, :-1] # 右邻居视图将grid向左移一列最右边一列补0 right np.zeros_like(grid) right[:, :-1] grid[:, 1:] # 求和 result up down left right return result result_correct neighbor_sum_vectorized_correct(grid) print(修正后的向量化计算结果) print(result_correct) # 手动验证中心点(2,2) print(f中心点grid[2,2] {grid[2,2]}) print(f上邻居grid[1,2] {grid[1,2]}) print(f下邻居grid[3,2] {grid[3,2]}) print(f左邻居grid[2,1] {grid[2,1]}) print(f右邻居grid[2,3] {grid[2,3]}) print(f邻居和 {grid[1,2] grid[3,2] grid[2,1] grid[2,3]}) print(f计算结果result_correct[2,2] {result_correct[2,2]})输出修正后的向量化计算结果 [[ 6 9 12 15 14] [23 30 35 40 37] [46 60 72 84 78] [69 90 108 126 117] [62 81 98 115 106]] 中心点grid[2,2] 12 上邻居grid[1,2] 7 下邻居grid[3,2] 17 左邻居grid[2,1] 11 右邻居grid[2,3] 13 邻居和 48 计算结果result_correct[2,2] 48完美这个向量化的版本没有任何显式循环完全通过数组的切片和算术运算完成。对于任意大小的数组其计算时间几乎只与数组大小成线性关系且由于全程在C层执行比Python循环快数百倍。5.5 更优雅的解决方案使用卷积其实计算邻居和这个问题在图像处理中非常常见它本质上是一个卷积操作。我们可以使用scipy.signal库的卷积函数或者Numpy的np.lib.stride_tricks.sliding_window_view较新版本来更概念化地解决。但为了保持纯Numpy且易于理解上面的向量化切片方法是最直接的。它揭示了一个重要模式许多涉及局部邻域的操作都可以通过构造偏移视图来实现向量化。6. 性能优化与内存管理进阶当你处理GB级别的大型数组时性能和内存就变得至关重要。这里分享几个关键经验。6.1 就地操作与避免临时数组Numpy的许多运算会创建新的数组。例如c a b会先创建一个临时数组存储ab的结果再赋值给c。对于大数组这会消耗双倍内存。a np.ones(int(1e7)) # 一个包含1千万个float64的数组约80MB b np.ones(int(1e7)) # 这种方式会创建临时数组 c a b # 瞬间内存增加80MB临时数组然后赋值给c # 使用就地操作可以节省内存 a b # 直接将结果写回a不创建临时数组。但注意这会修改a对于,*,-,/这类复合赋值运算符Numpy会尽可能执行就地操作。另外许多函数的out参数允许你指定输出数组避免创建新数组。result np.empty_like(a) np.add(a, b, outresult) # 将结果直接写入预分配的result数组6.2 选择合适的数据类型dtype不仅影响精度更影响内存和速度。float64默认是双精度占8字节float32是单精度占4字节内存减半计算也可能更快尤其GPU计算。如果数据范围在0-255使用uint8只需1字节。arr_f64 np.ones(1000000, dtypenp.float64) # 约8MB arr_f32 np.ones(1000000, dtypenp.float32) # 约4MB arr_u8 np.ones(1000000, dtypenp.uint8) # 约1MB在创建数组时如果确定数据范围尽量指定更紧凑的dtype。可以使用astype方法转换但注意这会创建新数组。6.3 利用NumPy的高级函数替代复杂循环对于更复杂的操作如按分组聚合、滑动窗口计算等虽然可以用循环和切片实现但Numpy或SciPy往往有更优的内置函数或策略。np.einsum用于简洁而高效地表达多维数组的求和、乘积、转置等操作是爱因斯坦求和约定的实现。对于复杂的张量运算它可以避免中间数组的创建。np.lib.stride_tricks.sliding_window_view用于创建数组的滑动窗口视图非常适合实现卷积、移动平均等操作无需复制数据。np.vectorize谨慎使用它可以将一个接受标量输入、输出标量的Python函数“向量化”但其底层仍然是Python循环性能提升有限。仅适用于无法用纯Numpy操作表达、且函数调用开销不大的简单函数。7. 调试与常见错误排查即使经验丰富也难免遇到错误。热搜词里的AttributeError: module ‘numpy’ has no attribute ‘product’就是一个典型例子。7.1 版本变迁与API更新Numpy的API并非一成不变。np.product在较新的版本中已被弃用推荐使用np.prod。这类错误通常发生在你参考的旧教程或旧代码使用了已弃用的函数。你安装的Numpy版本与代码期望的版本不一致。解决方案查看官方文档遇到不认识的函数第一时间去查 Numpy官方API文档 。使用help()函数在交互环境里help(np.prod)。升级/降级版本如果项目依赖旧API可能需要安装特定版本的Numpypip install numpy1.19.5。但这只是临时方案长期应迁移到新API。7.2 形状不匹配与广播错误这是最常见的运行时错误之一。a np.ones((3, 4)) b np.ones((4,)) c a b # 可以广播(3,4) (4,) - (3,4) (1,4) - (3,4) d np.ones((3,)) e a d # 错误(3,4) (3,) - 从右对齐4和3不匹配。排查方法遇到ValueError: operands could not be broadcast together...立即打印出操作数的shape属性然后手动应用广播规则检查。7.3 整数溢出与类型提升Numpy不会像Python整数那样自动升级到高精度。arr np.array([100, 200, 300], dtypenp.int8) print(arr.sum()) # 可能得到错误的结果溢出int8范围是-128~127600超过了int8的范围结果会发生环绕。解决方案在进行可能产生大数值的运算前先将数组转换为足够大的数据类型如int32,int64或float64。safe_sum arr.astype(np.int64).sum()7.4 视图与拷贝引发的玄学Bug如前所述切片返回视图修改视图会影响原数组。这个特性在链式操作中尤其危险。a np.array([1, 2, 3, 4, 5]) b a[1:4] b[:] 0 # 通过视图b修改 print(a) # [1 0 0 0 5] a被意外修改了黄金法则如果你不确定是否需要共享数据或者后续要修改切片安全起见使用.copy()。在函数中如果不想修改输入参数也应该先进行拷贝。掌握Numpy就像是掌握了数据分析世界的“内功”。它可能不像Pandas那样直接产出漂亮的表格也不像Matplotlib那样直接画出绚丽的图表但它是这一切的基石。当你面对海量数据一个复杂的转换或者一个自定义的统计指标时能够熟练运用Numpy进行向量化思考和高性能计算会让你从“数据搬运工”真正蜕变为“数据操盘手”。从理解ndarray的内存模型开始到熟练运用广播和聚合再到避免常见的视图陷阱每一步都需要在实战中反复练习。下次当你用Pandas的apply函数感觉慢时不妨想想能不能用Numpy的向量化操作来重写它。你会发现一片新的天地就此打开。
返回列表