NumPy基本操作快速熟悉
NumPy 是 Python 数值计算非常重要的一个包。很多科学计算包都是以 NumPy 的数组对象为基础开发的。 本文用代码快速过了一遍 NumPy 的基本操作,对 NumPy 整体有一个把握。希望能对读者有帮助。 NumPy ndarray:多维数组对象ndarray 是 NumPy 很重要的特性,它是快速而灵活的数据集容器。ndarray 可以在整个数据集上进行类似于标量的运算,既快速又方便。 在 Jupyter Notebook 上,首先引入 NumPy 包: 1 import numpy as np 创建 ndarray,其中包含随机值(np.random.randn): 1 np.random.seed(666) # 设置随机种子 2 data = np.random.randn(2,3) 3 data array([[ 0.82418808,0.479966,1.17346801],[ 0.90904807,-0.57172145,-0.10949727]]) 进行一些运算(乘法和加法): 1 data * 10 2 data + data array([[ 8.24188083,4.79966003,11.73468012],[ 9.09048069,-5.71721452,-1.09497268]]) array([[ 1.64837617,0.95993201,2.34693602],[ 1.81809614,-1.1434429,-0.21899454]]) ndarray 所有的数据必须是相同类型,其属性 shape 表示形状大小,dtype 表示该数组的数据类型。 data.shape 2 data.dtype (2,3) dtype('float64') ndarray 的创建最简单的创建方法是使用 array 函数。该函数根据接收的序列对象,生成新的 NumPy 数组。 1 data1 = [6,66,666,6666,66666] 2 arr1 = np.array(data1) 3 arr1 arr2.shape 2 arr2.ndim ? array([ 6,66666]) 嵌套的序列(比如说嵌套列表),可以生成多维的数组。 1 data2 = [[6,666],[6666,66666,666666]] 2 arr2 = np.array(data2) 3 arr2 array([[ 6,[ 6666,666666]]) 前面介绍了 ndarray 的属性 shape 表示形状大小,它还有一个属性 ndim 表示维度大小,我们可以对比两者的区别。 2 arr2.ndim
(2,3) 2 除了 np.array,还有一些函数可以创建新数组:zeros、ones、empty 等等。(注意到 empty 不一定返回零值,某些情况它可能返回未初始化的不确定值) 1 np.zeros(62 np.ones([2,1)">]) 3 np.empty((2,3,2)) array([0.,0.,0.]) Out[26]: array([[1.,1.,1.],[1.,1.]]) Out[26]: array([[[0.00000000e+000,0.00000000e+000],[2.96439388e-323,[2.14321575e-312,1.58817677e-052]],[[6.50119216e-091,1.74483574e-076],[1.28392645e+160,1.35027265e+161],[3.99910963e+252,9.02421558e-309]]]) np.arange 类似于 Python 的 range 函数,只不过它生成的是 ndarray。 1 np.arange(62 np.array(range(6)) arange 等价于该效果 array([0,1,2,4,5]) array([0,5]) ndarray 的数据类型前面介绍的 dtype 可以在创建时指定: 1 arr1 = np.array([6,dtype=np.float64) 2 arr2 = np.array([6,1)">np.int32) 3 arr1.dtype 4 arr2.dtype dtype('float64') dtype('int32') 一些典型的数据类型有:uint8、int32、float64、float128、complex64、bool、object、string_、unicode_ 等。 使用 astype 可以对数据类型进行显式转换。 1 arr = np.array([6,6662 arr.dtype 3 float_arr = arr.astype(np.float64) 4 float_arr.dtype dtype('int64') dtype('float64') 浮点数数组转为整型数组: 1 arr = np.array([1.7,2.7,3.7arr 3 arr.astype(np.int32) array([1.7,3.7]) array([1,3],dtype=int32) 字符串数组转为浮点数数组(float 是 Python 类型,NumPy 自动解析识别): 1 numeric_strings = np.array([@H_479_301@'@H_479_301@1.7@H_479_301@',@H_479_301@-2.7@H_479_301@3.7@H_479_301@'],1)">np.string_) 2 float_arr = numeric_strings.astype(float) float_arr 4 float_arr.dtype array([ 1.7,-2.7,3.7]) dtype('float64') 使用其他数组的 dtype 属性来进行转换: 1 int_arr = np.arange(6int_arr 3 float_arr = np.array([6.,66.,666.],1)">4 int_arr.astype(float_arr.dtype) array([0,5]) array([0.,2.,3.,4.,5.]) ndarray 的算术运算ndarray 的算术运算可以避免写 for 循环,这既方便,性能也好。 数组与相同大小的数组之间的运算: 1 arr = np.array([[1.,3.],[4.,5.,6.]]) 3 arr * arr 4 arr - arr array([[1.,6.]]) array([[ 1.,9.],[16.,25.,36.]]) array([[0.,0.],[0.,0.]]) 数组与标量的运算,会按数组的元素一一进行计算。 1 1 /2 arr ** 0.5 array([[1.,0.5,0.33333333],[0.25,0.2,0.16666667]]) array([[1.,1.41421356,1.73205081],[2.,2.23606798,2.44948974]]) 相同大小的数组之间的比较运算,会返回布尔值的数组: 1 arr2 = np.array([[0.,[7.,12arr2 4 arr2 > arr array([[ 0.,[ 7.,12.]]) array([[1.,6.]]) array([[False,True,False],[ True,False,True]]) 索引和切片一维数组很简单,看起来类似于 Python 列表: 1 arr = np.arange(103 arr[54 arr[5:85 arr[5:8] = 12 6 arr array([0,5,6,7,8,9])- 5 array([5,7]) array([ 0,12,9]) 第 5 行用到了 ndarray 的 broadcast 机制,这是和原生 Python 列表的第一个区别,我们可以看到列表会报错: 1 li = [0,9li 3 li[54 li[5:85 li[5:8] = 12 6 li [0,9] 5 [5,7] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-43-b59603238145> in <module> 3 li[5] 4 li[5:8] ----> 5 li[5:8] = 12 6 li TypeError: can only assign an iterable ndarray 的切片(或索引)是一个指针(NumPy 称之为视图 view),不会复制底层数据,修改切片会直接影响到数组本身。 1 arr_slice = arr[5:8arr_slice 3 arr_slice[1] = 666 4 arr array([12,12]) array([ 0,9]) 切片 [:] 的赋值会影响到所有元素: 1 arr_slice[:] = 777 2 arr array([ 0,777,9]) 由于 NumPy 经常和大型数据集打交道,因此切片是指针并不奇怪。如果希望不影响原数组,应该使用 copy 方法。 ].copy() 2 arr_slice[:] = 666 3 arr array([ 0,9]) 在二维数组中,每个索引代表的元素不再是标量。要找到每个元素有两种方法。 1 arr2d = np.array([[1,[4,6],[7,1)">]]) 2 arr2d[2 法 1 4 arr2d[0][25 法 2 6 arr2d[0,2] array([7,9]) 3 3 在这样的二维数组中,axis 0 可以理解为行,axis 1 可以理解为列。 三维数组的使用示例: 1 arr3d = np.array([[[1,6]],[[7,9],[10,11,1)">]]]) arr3d 3 arr3d[0] array([[[ 1,[ 4,[[ 7,12]]]) array([[1,6]]) 标量和数组都可以赋值给 arr3d[0]: 1 old_values = arr3d[0].copy() 2 arr3d[0] = 666 4 arr3d[0] = old_values 5 arr3d array([[[666,[666,666]],[[ 7,[ 10,12]]]) array([[[ 1,12]]]) 用切片进行索引2 arr[1:6arr2d 4 arr2d[:25 arr2d[:2,1:] array([ 0,9]) array([ 1,777]) array([[1,9]]) array([[1,6]]) array([[2,[5,6]]) 注意,如果使用切片作为索引,得到的总是相同维度的视图,而如果使用索引,则必定得到的是低维的数组。 使用切片 3 arr2d[1:2,:24 使用了索引 5 arr2d[1,1)">6 arr2d[:2,2] array([[1,9]]) array([[4,5]]) array([4,5]) array([3,6]) 布尔索引首先生成一些数据: 1 names = np.array([@H_479_301@Bob@H_479_301@Joe@H_479_301@Will@H_479_301@'2 np.random.seed(6663 data = np.random.randn(7,4names 5 data array(['Bob','Joe','Will','Bob','Joe'],dtype='<U4') array([[ 0.82418808,1.17346801,0.90904807],[-0.57172145,-0.10949727,0.01902826,-0.94376106],[ 0.64057315,-0.78644317,0.60886999,-0.93101185],[ 0.97822225,-0.73691806,-0.29873262,-0.46058737],[-1.08879299,-0.57577075,-1.68290077,0.22918525],[-1.75662522,0.84463262,0.27721986,0.85290153],[ 0.1945996,1.31063772,1.5438436,-0.52904802]]) 假设每个 name 对应着 data 中的每一行,我们希望选择 Bob 有关的数据。 1 names == @H_479_301@' 2 data[names == @H_479_301@'] array([ True,False]) array([[ 0.82418808,-0.46058737]]) 可以看到,选择了第 0 行和第 3 行的数据。需要注意的是布尔数组的长度必须和该维度的长度相同,否则会报错。 1 np.array([ True,False])[:-12 data[np.array([ True,False])[:-1]] array([ True,False]) --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-72-07628751d568> in <module> 1 np.array([ True,False])[:-1] ----> 2 data[np.array([ True,False])[:-1]] IndexError: boolean index did not match indexed array along dimension 0; dimension is 7 but corresponding boolean dimension is 6 布尔索引结合切片或索引的情况: 1 data[names == :] 1 data [names != @H_479_301@2 data[~(names == @H_479_301@')] array([[-0.57172145,-0.52904802]]) array([[-0.57172145,-0.52904802]]) 如果要结合多个布尔条件,使用布尔运算符 &(且) 和 |(或): 2 mask = (names == @H_479_301@') | (names == @H_479_301@mask 4 data[mask] array(['Bob',dtype='<U4') array([ True,0.22918525]]) 注意:使用布尔进行索引后返回的数组总是是原数组的拷贝,而非指针,也没有 broadcast 机制。这是与索引或者切片所不同的地方。 data 2 data_copy = data[names == @H_479_301@data_copy 4 data_copy[:,:] = 666 6 data array([[ 0.82418808,-0.52904802]]) array([[ 0.82418808,-0.46058737]]) array([[666.,666.,[666.,666.]]) array([[ 0.82418808,-0.52904802]]) 但是注意是返回后的数组才是拷贝,可以用布尔索引来直接进行赋值,也可以使用 broadcast 机制。 '] = 666 2 data array([[ 6.66000000e+02,6.66000000e+02,6.66000000e+02],[-5.71721452e-01,-1.09497268e-01,1.90282649e-02,-9.43761065e-01],[ 6.40573153e-01,-7.86443172e-01,6.08869993e-01,-9.31011849e-01],[ 6.66000000e+02,[-1.08879299e+00,-5.75770746e-01,-1.68290077e+00,2.29185248e-01],[-1.75662522e+00,8.44632619e-01,2.77219863e-01,8.52901533e-01],[ 1.94599603e-01,1.31063772e+00,1.54384360e+00,-5.29048024e-01]]) fancy 索引fancy 索引在 NumPy 中指的是使用整型数组来进行索引。 假设有以下一个 ndarray: 1 arr = np.empty((8,1)">)) for i in range(8): 3 arr[i] = i 4 arr array([[0.,2.],[3.,4.],[5.,5.],[6.,6.,6.],7.,7.]]) 使用 fancy 索引可以选择行的顺序: 1 arr[[7,1,0]] 2 arr[[-1,0]] array([[7.,7.],0.]]) array([[7.,0.]]) 如果传入多个索引数组的话,会针对每个既定顺序的行,选择既定顺序的列,最终得到几个元素的数组。 1 arr = np.arange(32).reshape((8,1)">3 arr[[3,0],[3,0]]
array([[ 0,7],[ 8,9,10,11],[12,13,14,15],[16,17,18,19],[20,21,22,23],[24,25,26,27],[28,29,30,31]]) array([15,0]) 下面示例的效果是变换数组所选行和列的顺序,而不会降维。 1 arr[[3,0]][:,0]]
array([[15,12],[11,8],[ 7,4],[ 3,0]]) 注意:fancy 索引同样返回的是数组的拷贝,而非指针。 数组的转置和轴的交换转置(.T)返回的是底层数组的视图,而非数组的拷贝。 1 arr = np.arange(15). reshape((3,53 arr.transpose() 转置 4 arr.T 更简便的写法 array([[ 0,[ 5,14]]) array([[ 0,10],[ 1,[ 2,13],14]]) 矩阵的内积计算使用 np.dot: 1 arr1 = np.array([[3,-1],[1,1)">arr1 3 arr2 = np.array([[2,1)">5 np.dot(arr1,arr2.T) array([[ 3,2]]) array([[2,1]]) array([[5],[4]]) transpose 可以接收元组参数来交换轴: 1 arr = np.arange(6).reshape((2,1)"> 2 3 arr.transpose((0,1)"> 4 arr.transpose((1 5 arr = np.arange(16).reshape((2,1)"> 6 7 置换 axis=1 和 axis=0,axis=2 不变 8 由于 axis=2 不变,那么 9 设 a = [0,3] b = [4,7] c = [8,11] d = [12,15] 10 arr = [[a,b],[c,d]] 11 arr.transpose(1,0) = arr.T = [[a,c],[b,d]] = [[[0,[8,11]],[[4,15]]] 12 arr.transpose((1,2)) array([[0,2],5]]) array([[0,[2,5]]) array([[[ 0,7]],[[ 8,15]]]) array([[[ 0,[[ 4,15]]]) swapaxes 同样可以交换轴: 2 arr.swapaxes(1,1)">3 arr.transpose(0,1) 类似于 arr.swapaxes(1,2) array([[[ 0,5],[ 9,14],15]]]) 注意 swapaxes 同样返回的是数组的视图,而不是数组的拷贝。 通用函数通用函数执行的是按元素的运算。 sqrt 和 exp: np.sqrt(arr) 4 np.exp(arr) array([0,9]) array([0.,1.73205081,2.44948974,2.64575131,2.82842712,3. ]) array([1.00000000e+00,2.71828183e+00,7.38905610e+00,2.00855369e+01,5.45981500e+01,1.48413159e+02,4.03428793e+02,1.09663316e+03,2.98095799e+03,8.10308393e+03]) maximum 和 minimum: 1 np.random.seed(6662 x = np.random.randn(8x 4 y = np.random.randn(8y 6 np.maximum(x,y) 7 np.minimum(x,y) array([ 0.82418808,0.90904807,-0.94376106]) array([ 0.64057315,-0.93101185,0.97822225,-0.46058737]) array([ 0.82418808,-0.46058737]) array([ 0.64057315,-0.94376106]) 典型的一元通用函数有:
典型的二元通用函数有:
面向数组编程直接面向数组编程可以避免使用 for 循环,这又称为向量化(vectorization),一般来说性能会好上一两个量级以上。 1 points = np.arange(-5,0.012 xs,ys = np.meshgrid(points,points) xs 4 ys array([[-5.,-4.99,-4.98,...,4.97,4.98,4.99],[-5.,4.99]]) array([[-5.,-5.,-5. ],[-4.99,-4.99],[-4.98,-4.98],[ 4.97,4.97],[ 4.98,4.98],[ 4.99,4.99,4.99]]) 1 z = np.sqrt(xs ** 2 + ys ** 22 z array([[7.07106781,7.06400028,7.05693985,7.04988652,7.06400028],[7.06400028,7.05692568,7.04985815,7.04279774,7.05692568],[7.05693985,7.04278354,7.03571603,7.04985815],[7.04988652,7.0286414,7.04279774],7.05692568]]) 这里使用 matplotlib 建立这个二维数组的数据可视化。 matplotlib.pyplot as plt 2 3 plt.imshow(z,cmap=plt.cm.gray) plt.colorbar() 5 6 plt.title(@H_479_301@Image plot of $sqrt{x^2 + y^2}$ for a grid of values@H_479_301@') 带条件逻辑的数组运算numpy.where 函数是 x if condition else y 的向量化版本。? 1 x = np.array([6,1)">2 y = np.array([7,77,7777,777773 cond = np.array([True,False]) 4 result = np.where(cond,x,1)">5 result array([ 6,77777]) 典型的使用场景: 2 arr = np.random.randn(4,1)">4 arr > 0 5 np.where(arr > 0,-1) 对两种情况都赋值 6 np.where(arr > 0,arr,0) 只处理小于零的情况 array([[ 0.82418808,-0.46058737]]) array([[ True,True],[False,False]]) array([[ 1,1],[-1,-1,-1]]) array([[0.82418808,0. ],[0.64057315,[0.97822225,0. ]]) 数学和统计运算典型的聚合函数有:sum,mean,std。 2 arr = np.random.randn(6,1)">arr.mean() np.mean(arr) arr.sum() 7 arr.std() array([[ 0.82418808,-0.10949727],[ 0.01902826,-0.94376106,0.64057315,-0.46058737,-1.08879299,-0.57577075],[-1.68290077,0.22918525,-1.75662522,-0.52904802,-0.6564723,-0.2015057 ],[-0.70061583,0.68713795,-0.02607576,-0.82975832,0.29655378,-0.3126795 ]]) -0.03691228536732661 -0.03691228536732661 -1.328842273223758 0.81811319613762 mean 和 sum 有一个 axis 参数用于指定针对哪一行: 1 arr.mean(axis=12 arr.sum(axis=0) array([ 0.45090857,-0.23212411,-0.36376326,-0.20593112,0.27700915,-0.14757295]) array([-0.3674784,1.0262478,1.27645117,-0.8521562,-1.13434309,-1.27756353]) 一些典型的统计性方法:
布尔数组的方法?any 检查某数组是否有一个以上的 True,all 检查某数组是否所有值都为 True。 1 bools = np.array([False,1)">bools.any() 3 bools.all() True False 排序2 arr = np.random.randn(6arr.sort() 5 arr array([ 0.82418808,-0.10949727]) array([-0.57172145,0.82418808,1.17346801]) 针对某一维度进行排序: 2 arr = np.random.randn(5,1)">4 arr.sort(15 arr array([[ 0.82418808,0.64057315],[-0.78644317,-0.29873262]]) array([[ 0.479966,[-0.94376106,[-0.93101185,0.60886999],[-0.73691806,0.97822225]]) 查看四分位数: 2 arr = np.random.randn(10004 arr[int(0.25 * len(arr))] 5 arr[int(0.75 * len(arr))] -0.6786323778244538 0.6879940536373379 唯一值和集合逻辑NumPy 有一些一维数组的基本的集合运算。其中 np.unique 是非常常用的。 np.unique(names) 3 ints = np.array([3,1)">np.unique(ints) 纯 Python 实现 6 sorted(set(names)) array(['Bob','Will'],dtype='<U4') array([1,4]) ['Bob','Will'] np.in1d 检查两个数组值的关系。 1 values = np.array([6,1)">2 np.in1d(values,6])
array([ True,True]) 数组常用的集合运算有:
数组的文件输入和输出NumPy 可以用文本或者二进制的方式把数据存储到硬盘,我们一般都选用二进制的方式。 np.save 和 np.load 分别是以二进制存储和加载的函数。 持久化 3 np.save(@H_479_301@arr_data.npy 加载 5 np.load(@H_479_301@') array([0,9]) 还可以以非压缩或者压缩的格式保存多个 NumPy 数据: 非压缩 2 np.savez(@H_479_301@arr_archive.npzarr) 压缩 4 np.savez_compressed(@H_479_301@arr_compressed.npz@H_479_301@6 arch = np.load(@H_479_301@7 arch[@H_479_301@b@H_479_301@8 cmp = np.load(@H_479_301@9 cmp[@H_479_301@a@H_479_301@'] array([0,9]) array([0,9]) 线性代数矩阵运算使用的是 dot 函数。 ? 1 x = np.array([[1.,1)">2 y = np.array([[1.,[0,1)">x.dot(y) 6 np.dot(x,y) 等价于 x.dot(y) 7 x @ y Python3.5,等价于 x.dot(y) array([[1.,[-1.,[ 0.,1.]]) array([[-1.,14.]]) array([[-1.,14.]]) numpy.linalg 有很多关于矩阵分解(如求逆、行列式等)的函数。 from numpy.linalg inv,qr 3 np.random.seed(6664 X = np.random.randn(3,1)">5 mat = X.T.dot(X) inv(mat) 7 mat.dot(inv(mat)) 等于单位矩阵 8 q,r = qr(mat) 9 r array([[ 0.94360937,0.09940334,-0.46256198],[ 0.09940334,0.70123855,-0.05689685],[-0.46256198,-0.05689685,0.78260277]]) array([[ 1.00000000e+00,8.64663156e-18,-4.04784612e-17],[ 1.27360445e-17,1.00000000e+00,6.98841494e-18],[-7.26002573e-17,-9.92956304e-18,1.00000000e+00]]) array([[-1.7499547,0.22916446,-1.66008207],-1.43686954,-0.22584526],1.09786224]]) 常用的 numpy.linalg 函数:
生成伪随机数numpy.random 有各种生成随机数样本的函数,比原生 Python 随机数生成模块速度快很多。 生成的随机数称为伪随机数,因为这些随机数实际上依赖于 np.random.seed 的值。 2 samples = np.random.normal(size=(4,1)">3 samples
array([[ 0.82418808,-0.46058737]]) 可以使用全局随机种子: 1 rng = np.random.RandomState(6662 rng.randn(10) array([ 0.82418808,-0.78644317]) 常用的随机函数如下:
使用 NumPy 实现一个随机游走的例子2 nsteps = 1000 随机游走的步数 3 draws = np.random.randint(0,size=nsteps) 生成 0 或 1,一共 nsteps 个点 4 steps = np.where(draws == 0,1)"> 等于 0 则设为 1,不等于 0 则设为 -1 5 walk = steps.cumsum() 累积求和 walk.min() 7 walk.max() 8 (np.abs(walk) >= 10).argmax() 第一次绝对值超过 10 的索引 9 plt.plot(walk[:100]) 同时模拟多个随机游走2 nwalks = 5000 3 nsteps = 1000 4 draws = np.random.randint(0,size=(nwalks,nsteps)) 5 steps = np.where(draws > 0,-16 walks = steps.cumsum(1) 1 表示针对 axis=1 walks 8 walks.shape 行表示模拟的不同随机游走趋势,列表示不同的步数 array([[ -1,-2,-22,-21,-22],[ 1,20,19,20],-14,-13,-12],[ -1,24,26],16,15,16],-24,-23,-22]]) (5000,1000) 总结本文介绍了 NumPy 基本操作。 NumPy 最重要的数据结构是 ndarray。我们熟悉了:
围绕着 ndarray,NumPy 还提供了很多函数和方法:
最后是一个有关随机游走的简单示例。 通过本文我们主要知道了 NumPy 的核心数据结构 ndarray,以及有关它的各种函数出现的源头都是: 向量化的思想——数值计算尽量别用 for 循环,这样代码看起来简单,性能也好得多。 最后欢迎留言,有不足之处请指正。 ? 参考
? (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |