你是否在日常工作时, 总是喜欢用列表来处理数据并进行计算, 接着就被那种慢得让人怀疑人生的速度搞得十分崩溃, 举个例子来说, 当需要去循环累加一万个数字时, 电脑的CPU风扇就会呼呼地转个不停, 结果你苦苦等了半天之后, 最终才看到结果显示出来, 请你暂时先不要着急, 现在我将带你去重新认识一下被称为科学计算器具箱中的那把瑞士军刀一样的存在, 那就是NumPy, 这是一个神奇的库, 使用它能够让数据处理的速度快得如同飞起一般。
说直白了, 其实NumPy这一个核心技术就是关于一种高效的多维数组。而且它跟列表最大的那个区别是, 因为它是在内存里面进行连续存储的, 所以还可以一次性对于整个数组来执行数学方面的运算, 从此以后就可以彻底地去告别掉那些操作起来非常缓慢的for循环了。
拿一个具体的实例来说吧, 倘若你心中盘算的是要将一万个数值逐一进行乘以二的运算这一动作。要是采用传统的列表循环方式去执行的话, 大致上需要耗费三行代码的篇幅来进行书写, 而且其运行的时间成本会落在十几毫秒这样一个区间之内。
然而, 若是换用Num数组工具来处理此事, 仅仅只需要写下一行名为`arr * 2`的代码即可大功告成, 而其所消耗的时间将缩短至不足一毫秒的地步。这般对比鲜明的结果, 正是向量化操作所展现出来的强大威力所在之处。
怎么创建数组, 方法很简单, 就是直接用`np.array()`进行转换。如果想要快速生成等差数列, 可以试试使用像range但返回为数组的`np.`来操作, 比如传入`0, 10, 2`这样的参数;又或者是在0到1之间均匀取出5个数, 此时可以将参数设置成`0, 1, 5`的形式并调用相应的函数<。
更常用的做法是一下子就弄出那个全是零的、或者是那个全都是一的矩阵, 比如说像这个`np.zeros((3,4))`, 它生成了就是三行四列的那个全零的数组, 而`np.ones()`这个东西也是同样的道理,至于`np.eye(5)`, 那就是单位阵了。
另外, 还可以使用随机数功能, 例如调用 `np..rand(3,3)` 就能生成一个大小为 3x3 的、数据呈均匀分布的随机数组, 而执行 `np..randn(3,3)` 则是用来生成符合标准正态分布的数据。
如果需要保证每次代码运行出来的结果都一样, 也就是让结果具备可复现性, 那就需要配合使用 `np..seed(42)` 来固定随机种子。
在数组创建完成之后, 我们需要懂得如何去使用它的所谓身份证明, 通过调用`arr.shape`我们可以查看到行列的具体数量, 借助`arr.ndim`我们能够知晓维度究竟是多少, 同时依靠`arr.dtype`我们也可以看清它所占用的数据类型到底为何种样子, 至于索引和切片这一块, 它们相比普通的列表会显得更加的灵活多样, 如果我们想要查看一个二维数组的情况, 我们可以去参照`arr`这个方法进行观察, 就像我们去提取第2行并且是第3列的那个元素时, 也可以直接使用对应的`arr`方式来实现, 而且切片的操作也同样是可以按照这个思路, 直接采用类似的`arr`手段来进行处理的。
这里需要特别予以注意, 在NumPy里面, 进行切片操作的默认结果是取原数组的一个视图, 要是修改了这个切片的内容的话, 必然会导致原数组也跟着发生改变, 这一点情况和列表方面来说是完全不同的一个样子, 如果想要得到一个完全独立的副本内容, 就必须去执行arr.copy()这个操作才行, 对于花式索引和布尔索引这类方法而言, 它们确实是非常厉害的工具手段, 比如可以使用arr来获取第0行、第2行以及第4行的数据信息, 又或是利用arr来筛选出所有大于5的那些数值元素。
运算这部分是最让人愉快的。针对数组进行加减乘除的时候, 只要给一个标量, 就能让所有的元素都直接算一遍。要是两个数组之间要做计算, 只要它们的形状是兼容的, 就可以算得成。那个乘号加星号也就是*这个符号, 它用来表示对应位置的元素相乘。
如果是要做矩阵乘法的话, 一般是用at这个符号或者是np.dot()这行代码去处理。
统计方面的函数不需要你自己去写循环了, 想求平均值, 就用arr.mean(), 想要知道总和是多少, 就用arr.sum(), 还有像找最大值用arr.max()这种, 以及找最小值用arr.min()这些功能, 全部都给你准备好了。
当你把这个参数设置为0的时候, 它的意思就是按照列来进行计算, 而当设置为1的时候, 它就表示按照行进行处理。
在发现数组里存在缺失值的情况时, 千万不要着急, 可以先使用`np.isnan(arr)`这个函数来进行判断检测, 之后再配合着`arr。
~np.isnan(arr)
直接去掉。条件替换用np.where(arr > 3, 1, 0), 大于3的变成1, 否则变0。
数组还能“变形”:`np.((a,b))`垂直堆叠,`np.((a,b))`水平拼接,`np.sort(arr)`排序,`np.(arr)`去重。想交换两行?`arr = arr`一行搞定。
线性代数这个领域里面已经内置了相关的功能: 如果你想要去求一个逆矩阵, 可以通过调用`np..inv(a)`这个方式来实现;如果你在需要进行计算行列式的时候, 能够直接使用`np..det(a)`来进行计算;而当你在面对解方程组这个任务的时候, `np..solve(A, b)`这个方法可以直接给你输出答案。
举个例子来说明一下, 针对鸡兔同笼这个非常经典的问题, 当存在两个方程并且对应着两个未知数的情况时, 仅仅通过一行代码就能够把它全部搞定。
数据持久化:用`np.save('data.npy', arr)`存成二进制,体积小加载快;想给别人看用`np.('data.csv', arr, =',')`存成文本。`np.load`和`np.`对应读取。
在最后再反复叮嘱几件重要的事情, 你核心要做的就是必须时时刻刻都记得使用向量化的操作去彻底替代掉那些循环的逻辑, 因为内存的占用会更少速度还能够快上几十倍之多。你要多多地进行练习以掌握花式索引和广播机制的具体用法, 这样你在编写代码的时候整个过程就会变得清爽很多。
如果你曾经也碰上过那个列表循环一直转不停、卡得你没办法的讨厌情况, 那就请在留言区和大家说说看, 究竟什么样的数据处理局面是让你最为头疼的。如果觉得这篇内容对你有所帮助, 不妨转发给那位还在靠手写循环代码的同事, 好让他也能从这个痛苦里解放出来。