一 NumPy ndarray: 多维数组对象上一次总结了该书第三章的内容,这次带来第四章,也就是NumPy 的基础部分,有关Numpy的历史发展等相关知识点,读者可直接参考该书的87、88页。从这两页,我们知道Numpy主要是在数组部分,其算法库是采用C编写,数据存储在连续内存块上,这一特性,使得Numpy数组方法在性能上要远优于传统的Python方法。
环境依旧是 Pycharm 2020.2.4 社区版, Anconda3
作为NumPy的核心特征之一,ndarray即N维数组对象,是Python中一个快速、灵活的大型数据集容器。
用下面的随机NumPy数组代码,来感受一下
import numpy as np# 生成随机数组data = np.random.randn(2, 3)print(data)# 对numpy 数组进行数组操作print(data * 10)print(data + data)# 描述数组每一维度的数量print(data.shape)# 描述数组的数据类型————ndarray 所有元素的数据类型相同print(data.dtype)
1、生成 ndarray几个示例代码可以参考一下
import numpy as npdata1 = [6, 7.5, 8, 0, 1]# 将任意序列类型转换成 ndarray对象, 数据类型自动转换(低变高)arr1 = np.array(data1)print(arr1, arr1.dtype)# 嵌套序列自动转化成 高维数组data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]arr2 = np.array(data2)print(arr2, arr2.dtype)# 查看数组维度print(arr2.ndim, arr1.ndim)print(arr2.shape)# 其他生成数组函数# 生成全0print(np.zeros((3, 6)))# 生成未初始化数组print(np.empty((2, 3, 2)))# range 的数组版print(np.arange(12))
2、ndarray 的数据类型更多的数组生成函数可以参考该书的 表4-1
数据类型,即dtype,是一个特殊对象,它包含了ndarray 需要为某一种类型数据申明的内存块信息(也称为元数据,即表示数据的数据)
import numpy as nparr1 = np.array([1, 2, 3], dtype=np.float64)arr2 = np.array([1, 2, 3], dtype=np.int32)print(arr1.dtype)print(arr2.dtype)
更多数组类型请参考该书的 表4-2
import numpy as nparr1 = np.array([1, 2, 3], dtype=np.float64)# 使用astype方法显示转换数据类型arr1 = arr1.astype(np.int32)print(arr1.dtype)numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)numeric_strings = numeric_strings.astype(np.float64)print(numeric_strings)# 可以使用类型代码来传入数据类型empty_unit32 = np.empty(8, dtype='u4')print(empty_unit32, empty_unit32.dtype)
3、NumPy 数组算术使用 astype时 会生成一个新的数组,即使你传入的dtype 与之前一样。
数组之所以重要,是因为它允许进行批量操作而无须进行任何 for 循环。这一特性 也称为 向量化。
import numpy as nparr = np.array([[1., 2., 3.], [4., 5., 6.]])print(arr * arr)print(arr - arr)# 带有标量的算术操作也适用print(1 / arr)print(arr ** 0.5)# 同尺寸数组之间的比较,会产生一个布尔值数组arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])print(arr2 > arr)
4、基础索引与切片NumPy 数组索引 方式有很多种
import numpy as nparr = np.arange(10)print(arr)# 单个索引print(arr[5])# 数组切片print(arr[5:8])# 对切片段修改arr[5:8] = 12# 不会生成新数组,而是对原数组进行修改print(arr)arr_slice = arr[5:8]# 当arr_slice改变时,变化也会体现在原数组, 这一特性类似于cpp的引用arr_slice[1] = 12345print(arr)# 不写 切片值 的 [:] 将会引用数组的所有值arr_slice[:] = 13print(arr)# 如果需要复制,则需要显示调用复制copy = arr[5:8].copy()copy[:] = 0print(copy)print(arr)# 对于 二维数组 每个索引值对应的元素是一个一维数组arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])print(arr2d[0])# 单个元素可以通过递归方式获得,也可以传递索引的逗号分隔列表去选择单个元素print(arr2d[0][2], arr2d[0, 2])# 需要注意的是上诉通过索引返回的元素都是视图,即引用arr2d[0][2] = 5print(arr2d)
4.1 切片索引其实这种索引方式在上面有所提及,对于高维数组,我们可以使用切片索引获取低维度的切片,并重新赋值
import numpy as np# 对于 二维数组 每个索引值对应的元素是一个一维数组arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])print(arr2d[:2, 2])arr2d[:2, :1] = 0print(arr2d)
5、布尔索引其实就是bool值去索引, 需注意的是,布尔值数组长度应该和数组轴索引长度一致, 且可能是因为用C编写的原因,and和or关键字对布尔值数组没有用,需要使用 & 和 | 替代。
import numpy as npnames = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])data = np.random.randn(7, 4)print(names == 'Bob')print(data[names == 'Bob'])print(data[~(names == 'Bob')])mask = (names == 'Bob') | (names == 'Will')print(data[mask])data[data < 0] = 0print(data)
6、神奇索引其实就是用整数数组进行索引
import numpy as nparr = np.empty((8, 4))print(arr[[4, 3, 0, -6]])arr = np.arange(32).reshape((8, 4))print(arr)# 得到对应的元素print(arr[[1, 5, 7, 2], [0, 3, 1, 2]])# 得到相应的矩形区域print(arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]])
7、数组转置和换轴import numpy as nparr = np.arange(15).reshape((3, 5))print(arr)print(arr.T)# 计算矩阵内积print(np.dot(arr, arr.T))arr1 = np.arange(16).reshape((2, 2, 4))# 使用 transpose 方法对数组的轴重新排序print(arr1.transpose((1, 0, 2)))print(arr1)# 转置其实就是换轴的一种特殊案例, 使用swapaxes方法对轴进行调整用于重组数据print(arr1.swapaxes(1, 2))
二 通用函数以上方法,都是返回数据的底层视图,而不生成新的数组,也不对数据进行复制
通用函数,ufunc,是一种在 ndarray 数据中 进行逐元素操作的函数,某些简单函数接收一个或多个标量差值, 并产生 一个或多个 标量结果, 而通用函数就是对这些函数是对这些简单函数的向量化封装
import numpy as nparr = np.arange(10)print(np.sqrt(arr))print(np.exp(arr))x = np.random.randn(8)y = np.random.randn(8)print(x, y)# 比较x,y对应元素,返回最大值print(np.maximum(x, y))# modf 返回一个浮点数组的小数部分和整数部分reminder, whole_part = np.modf(x)print(reminder)print(whole_part)arr = np.random.randn(7) * 5print(arr)
三 使用数组进行面向数组编程更多的通用函数请参考该书的表 4-3,4-4
使用Numpy数组,可以使用简单的数组表达式代替显示循环的方法, 这种方法称为向量化,向量化的数组操作会比纯Python的等价实现在速度上更快
import numpy as nppoints = np.arange(-5, 5, 0.01)# meshgrid 方法 传入两个一维数组, 并根据两个数组的所有(x,y)对生成一个二维矩阵xs, ys = np.meshgrid(points, points)print(ys)z = np.sqrt(xs ** 2 + ys ** 2)print(z)import matplotlib.pyplot as pltplt.imshow(z, cmap=plt.cm.gray)plt.colorbar()plt.title("Image plot of $sqrt{x^2 + y^2}$ for a grid of values")plt.show()
1、将条件逻辑作为数组操作这里主要讲的是where函数, 其实就是三元表达式 x if condition else y 的向量化版本。如果直接使用三元表达式,其需要解释器解释python完成, 速度会很慢,且对高维数组需要修改代码,无法轻易复用。而使用哪np.where就可以非常简单的完成
import numpy as npxarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])cond = np.array([True, False, True, True, False])result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]print(result)result = np.where(cond, xarr, yarr)print(result)arr = np.random.randn(4, 4)print(arr)print(arr > 0)# 标量也可以作为结果,不一定非要使用数组# 将正值设为2,负值设为-2print(np.where(arr > 0, 2, -2))# 只将正值设为2print(np.where(arr > 0, 2, arr))
2、数学和统计方法传递给 np.where 的数组既可以是同等大小的数组,也可以是标量
import numpy as nparr = np.random.randn(5, 4)print(arr)print(arr.mean())# mean 表示平均数print(np.mean(arr))# 像mean,sum等函数,可以接受一个可选参数axis,可用于计算给定轴向上的统计值# mean(1) 计算每一行的平均值print(arr.mean(1))# sum(0) 计算每一行的和print(arr.sum(axis=0))arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])print(arr.cumsum())# 对于高维数组,可以在指定轴向根据较低维度的切片进行部分聚合arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])print(arr.cumsum(axis=0))print(arr.cumprod(axis=1))
3、布尔值数组的方法更多地请参考该书的表4-5,然后提一句,该书在该页有一个问题,axis=0 是表示每一行,axis=1 表示每一列
布尔值会被强制为 1(True)和 0(False)
import numpy as nparr = np.random.randn(100)print((arr > 0).sum())bools = np.array([False, False, True, True])# any 检查数组中是否至少有一个Trueprint(bools.any())# all 检查是否每个值都是 Trueprint(bools.all())
4、排序这些方法也适用非布尔值数组,所有非0的元素都按 True 处理
import numpy as nparr = np.random.randn(6)print(arr)arr.sort()print(arr)# 对于高维数组可以使用参数指定轴进行排序arr = np.random.randn(5, 3)print(arr)arr.sort(1)print(arr)
5、唯一值与其他集合逻辑顶层的 np.sort 方法 返回的是已经排序好的数组拷贝,而不是对原数组继续拷贝
NumPy 包含一些针对一维nadarray 的基础集合操作,一个常用的方法是 np.unique,返回的是数组中唯一值排序后形成的数组
import numpy as npnames = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])print(np.unique(names))ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])print(np.unique(ints))
另一个函数, np.in1d, 可以检查一个数组中的值是否存在另外一个数组中,并返回一个布尔值数组
import numpy as npvalues = np.array([6, 0, 0, 3, 2, 5, 6])print(np.in1d(values, [2, 3, 6]))
四 使用数组进行文件输入和输出更多的ndarray数组的集合操作,请参考表 4-6
import numpy as nparr = np.arange(10)np.save('som_array', arr)print(np.load('som_array.npy'))# 数据未压缩存储np.savez('array_archive.npz', a=arr, b=arr)arch = np.load('array_archive.npz')print(arch['b'])# 数据已经压缩存储np.savez_compressed('array_compressed.npz', a=arr, b=arr)print(np.load('array_compressed.npz'))
五 线性代数这部分函数大多集合在了numpy的linalg中
import numpy as npx = np.array([[1., 2., 3.], [4., 5., 6.]])y = np.array([[6., 23.], [-1, 7], [8, 9]])print(x.dot(y))# x.dot(y) 等价于 np.dot(x, y)print(np.dot(x, y))# @ 也作为 中缀操作符, 用于点乘操作print(x @ y)# 求逆和qr分解from numpy.linalg import inv, qrX = np.random.randn(5, 5)mat = X.T.dot(X)print(inv(mat))print(mat.dot(inv(mat)))q, r = qr(mat)print(r)
六 伪随机数生成更多的线性代数操作方法,请参考表4-7
import numpy as npsamples = np.random.normal(size=(4, 4))print(samples)# 改变随机种子np.random.seed(1234)# 创建一个随机数生成器rng = np.random.RandomState(1234)print(rng.randn(10))
其他numpy.random 中的部分函数列表参考表 4-8
七 示例:随机漫步一个简单的随机漫步
import matplotlib.pyplot as pltimport numpy as npimport random# 偏python基础语法的实现形式position = 0walk = [position]steps = 1000for i in range(steps): step = 1 if random.randint(0, 1) else -1 position += step walk.append(position)# 偏NumPy形式nsteps = 1000draws = np.random.randint(0, 2, size =nsteps)steps = np.where(draws > 0, 1, -1)walk = steps.cumsum()# 在漫步轨道提取 最大值和最小值print(walk.min(), walk.max())# 返回第一次走了 10 步或 -10 步的位置print((np.abs(walk) >= 10).argmax())# 随机漫步的前100步plt.plot(walk[:100])plt.show()
1、一次性模拟多次随机漫步import matplotlib.pyplot as pltimport numpy as npimport randomnwalks = 5000nsteps = 1000draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 或 1steps = np.where(draws > 0, 1, -1)walks = steps.cumsum(1)print(walks)# 这些随机步的最大值和最小值print(walks.max(), walks.min())# 采用 any 方法检查 最小穿越时间hits30 = (np.abs(walks) >= 30).any(1)print(hits30)print(hits30.sum()) # 达到30 或 -30 的数字# 使用布尔值数组来 选出绝对步数超过 30步所在的行crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)print(crossing_times)print(crossing_times.mean())
小结本书接下来的部分将会集中在打造 pandas 数据处理技能上,继续在基于数组的风格下处理数据
终于写完了,最近的效率真的低下
_