《利用Python进行数据分析》附录A 高阶NumPy
A.1 ndarray对象内幕
ndarray如何灵活的部分原因使每个数组对象都是一个数据块的分步视图。例如,数组arr[::2,::-1]如何做到不复制任何数据。
原因是ndarray不仅仅是一块内存和一个type,它还具有"跨步"信息,使数组能够以不同的步长在内部中移动。
更准确的说,ndarray内部包含以下内容:
指向数据的指针-即RAM中或内存映射文件中的数据块
数据类型或dtype,描述数组中固定大小的值单元格
表示数组形状(shape)的元祖
步长元祖,表示要"步进"的字节数的整数以便沿维度推进一个元素
步幅这里介绍比较好
计算步幅:如果要在维度0方向上移动到下一个数组的相同位置,则需要移动三个元素。 每个元素的大小为2个字节。 因此,维度0中的步幅是2个字节x3个元素= 6个字节。
同样,如果要在维度1中移动一个单位,则需要移动1个元素。 因此,维度1中的步幅是2个字节x 1个元素= 2个字节。 最后一个维度的步幅始终等于元素大小。
我们可以使用.strides检查数组的步幅:
In [41]: n
Out[41]:
array([[3, 4, 5],
[6, 7, 8],
[0, 1, 2]])
In [42]: n.dtype
Out[42]: dtype('int64')
In [43]: n.strides
Out[43]: (24, 8)
A1.1NumPy dtype层次结构
In [44]: ints = np.ones(10,dtype=np.uint16) In [45]: floats = np.ones(10,dtype=np.float32) In [46]: np.issubdtype(ints.dtype, np.integer) Out[46]: True In [47]: np.issubdtype(floats.dtype, np.floating) Out[47]: True In [48]: np.float64.mro() Out[48]: [numpy.float64, numpy.floating, numpy.inexact, numpy.number, numpy.generic, float, object] In [49]:
重点记住一个是否使子类的方法issubdtype
A2 高阶数组操作
A.2.1重塑数组
In [50]: n = np.zeros(9).reshape((3,3))
In [51]: n
Out[51]:
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
In [52]: n = np.zeros(9).reshape((3,-1))
In [53]: n
Out[53]:
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
In [54]:
reshape内部参数可以是元祖,也可以直接去掉那个括号,第二个参数可以使用-1,将直接根据第一个参数自动计算。
还有这个np.ravel以及np.flatten
两者的功能是一致的,将多维数组降为一维,但是两者的区别是返回拷贝还是返回视图,np.flatten(0返回一份拷贝,对拷贝所做修改不会影响原始矩阵,而np.ravel()返回的是视图,修改时会影响原始矩阵
In [55]: n = np.arange(4).reshape(2,-1)
In [56]: n
Out[56]:
array([[0, 1],
[2, 3]])
In [57]: n_ravel = n.ravel()
In [58]: n_flatten = n.flatten()
In [59]: n_ravel
Out[59]: array([0, 1, 2, 3])
In [60]: n_ravel[:] =9
In [61]: n
Out[61]:
array([[9, 9],
[9, 9]])
In [62]: n_flatten[:]=10
In [63]: n
Out[63]:
array([[9, 9],
[9, 9]])
A2.2 C顺序和Fortran顺序
由于历史原因,行和列方向的顺序也分别称为C顺序和Fortran顺序。在FORTRAN77语言中,矩阵都使列方向的
In [64]: arr = np.arange(12).reshape(3,4)
In [65]: arr
Out[65]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [69]: arr.ravel('F')
Out[69]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
In [70]: arr.T.ravel()
Out[70]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
In [71]:
A2.3连接和分隔数据
连接前面已经学过了,再复习一下,用concatenate来完成。
In [80]: arr1
Out[80]:
array([[1, 2, 3],
[4, 5, 6]])
In [81]: arr2
Out[81]:
array([[ 7, 8, 9],
[10, 11, 12]])
In [82]: np.concatenate([arr1,arr2])
Out[82]:
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
In [83]: np.vstack((arr1,arr2))
Out[83]:
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
In [84]: np.concatenate([arr1,arr2],axis=1)
Out[84]:
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
In [85]: np.hstack((arr1,arr2))
Out[85]:
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
其实只要记住了concatenate就可以了
In [87]: arr = np.random.randn(5,2)
In [88]: arr
Out[88]:
array([[ 1.33138714, 2.07803625],
[-0.93913673, -0.73406683],
[-0.72445096, -0.76812243],
[-1.14585571, -1.65073635],
[ 0.7386325 , -0.22569135]])
In [89]: first,second,third = np.split(arr,[1,3])
In [90]: first
Out[90]: array([[1.33138714, 2.07803625]])
In [91]: second
Out[91]:
array([[-0.93913673, -0.73406683],
[-0.72445096, -0.76812243]])
In [92]: third
Out[92]:
array([[-1.14585571, -1.65073635],
[ 0.7386325 , -0.22569135]])
切的话是split,内部需要传入切割的点,n个点生成n+1条线段
A2.3.1堆叠助手:r_和c_
这个前面一本书也有介绍,今天仔细的看了一下,感觉功能还是很强大的
In [100]: arr = np.arange(6)
In [101]: arr1 = np.arange(6).reshape(3,2)
In [102]: arr2 = np.random.randn(3,2)
In [103]: np.r_[arr1,arr2]
Out[103]:
array([[ 0. , 1. ],
[ 2. , 3. ],
[ 4. , 5. ],
[-1.21541112, -2.00013006],
[-0.80744917, -1.539052 ],
[-1.39408604, 0.84356217]])
In [104]: arr.shape
Out[104]: (6,)
In [105]: np.c_[np.r_[arr1,arr2],arr]
Out[105]:
array([[ 0. , 1. , 0. ],
[ 2. , 3. , 1. ],
[ 4. , 5. , 2. ],
[-1.21541112, -2.00013006, 3. ],
[-0.80744917, -1.539052 , 4. ],
[-1.39408604, 0.84356217, 5. ]])
这个c_太牛了,arr本是一维的数组,1行6个元素,直接当扩展的column了。
这个函数还可以将切片转换未数组:
In [113]: np.c_[5:0:-1,0:5]
Out[113]:
array([[5, 0],
[4, 1],
[3, 2],
[2, 3],
[1, 4]])
In [114]:
A2.4重复元素:tile和repaet
repeat和tile函数是用于重复或复制数组的两个有用的工具。repeat函数按照给定次数对数组中的每个元素进行复制,生成一个更大的数组:
In [114]: arr = np.arange(3) In [115]: arr.repeat(3) Out[115]: array([0, 0, 0, 1, 1, 1, 2, 2, 2]) In [116]: arr.repeat([2,3]) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-116-a0c86ecd16ca> in <module> ----> 1 arr.repeat([2,3]) ValueError: operands could not be broadcast together with shape (3,) (2,) In [117]: arr.repeat([2,3,2]) Out[117]: array([0, 0, 1, 1, 1, 2, 2])
可以指定重复的次数,也可以指定单个元素的重复次数,但需要内部套一个数组,数量要对repert的元素数量一样
多维数组可以在指定的轴向上对它们的元素进行重复:
In [121]: arr
Out[121]:
array([[-1.40245973, -0.04140971],
[-1.37313291, 0.7491575 ]])
In [122]: arr.repeat(2)
Out[122]:
array([-1.40245973, -1.40245973, -0.04140971, -0.04140971, -1.37313291,
-1.37313291, 0.7491575 , 0.7491575 ])
In [123]: arr.repeat(2,axis=0)
Out[123]:
array([[-1.40245973, -0.04140971],
[-1.40245973, -0.04140971],
[-1.37313291, 0.7491575 ],
[-1.37313291, 0.7491575 ]])
In [124]: arr.repeat([2,3],axis=1)
Out[124]:
array([[-1.40245973, -1.40245973, -0.04140971, -0.04140971, -0.04140971],
[-1.37313291, -1.37313291, 0.7491575 , 0.7491575 , 0.7491575 ]])
In [125]:
另一方面,tile是一种快捷方法,它可以沿着轴向堆叠副本。在视觉上,你可以把它看做类似于"铺设瓷砖":
In [127]: np.tile(arr,2)
Out[127]:
array([[-1.40245973, -0.04140971, -1.40245973, -0.04140971],
[-1.37313291, 0.7491575 , -1.37313291, 0.7491575 ]])
In [128]: np.tile(arr,(2,1))
Out[128]:
array([[-1.40245973, -0.04140971],
[-1.37313291, 0.7491575 ],
[-1.40245973, -0.04140971],
[-1.37313291, 0.7491575 ]])
In [129]: np.tile(arr,(3,2))
Out[129]:
array([[-1.40245973, -0.04140971, -1.40245973, -0.04140971],
[-1.37313291, 0.7491575 , -1.37313291, 0.7491575 ],
[-1.40245973, -0.04140971, -1.40245973, -0.04140971],
[-1.37313291, 0.7491575 , -1.37313291, 0.7491575 ],
[-1.40245973, -0.04140971, -1.40245973, -0.04140971],
[-1.37313291, 0.7491575 , -1.37313291, 0.7491575 ]])
In [130]: np.tile(arr,(1,2))
Out[130]:
array([[-1.40245973, -0.04140971, -1.40245973, -0.04140971],
[-1.37313291, 0.7491575 , -1.37313291, 0.7491575 ]])
In [131]:
np.tile将内部的元素的打包成一个副本,可以在行于列的进行复制。默认一个参数是在column方向进行复制。
如果填写一个array,会对row和column进行复制。
A2.5神奇索引的等价方法:take和put
In [131]: arr = np.arange(10)*10 In [132]: arr = np.arange(10)*100 In [133]: inds = [7,1,2,6] In [134]: arr[inds] Out[134]: array([700, 100, 200, 600]) In [135]:
上面的神奇索引有介绍过.
put拿来赋值,take拿来取值,切put不知道axis参数,而是将数组索引到扁平版本(一维,C顺序)。因此,当您需要使用其他轴上的索引数组设置元素是,通常最容易使用神奇索引。
In [143]: arr = np.arange(10) *100 In [144]: arr.take(inds) Out[144]: array([700, 100, 200, 600]) In [145]: arr.put(inds,42) In [146]: arr Out[146]: array([ 0, 42, 42, 300, 400, 500, 42, 42, 800, 900]) In [147]: arr[inds] = 42 In [148]: arr Out[148]: array([ 0, 42, 42, 300, 400, 500, 42, 42, 800, 900]) In [149]: arr.put(inds,[40,41,42,43]) In [150]: arr Out[150]: array([ 0, 41, 42, 300, 400, 500, 43, 40, 800, 900]) In [151]: arr[inds] = np.arange(40,44) In [152]: arr Out[152]: array([ 0, 41, 42, 300, 400, 500, 43, 40, 800, 900])
所有的操作都可以用魔法索引赋值进行操作。
take可以传递axis值
In [154]: arr = np.random.rand(2,4)
In [155]: arr
Out[155]:
array([[0.12023118, 0.4302048 , 0.28467179, 0.88792358],
[0.2420648 , 0.21527482, 0.10031776, 0.57313978]])
In [156]: arr.take(inds,axis=1)
Out[156]:
array([[0.28467179, 0.12023118, 0.28467179, 0.4302048 ],
[0.10031776, 0.2420648 , 0.10031776, 0.21527482]])
In [158]: arr[:,inds]
Out[158]:
array([[0.28467179, 0.12023118, 0.28467179, 0.4302048 ],
[0.10031776, 0.2420648 , 0.10031776, 0.21527482]])
A3 广播
广播秒速了算法如何在不同形状的数组之间进行运算。它是一个强大的功能,但可能会导致混淆,即使对于有经验的用户也是如此。最简单的广播示例发生在将标量值于数组组合的时候:
In [171]: arr = np.arange(5) In [172]: arr Out[172]: array([0, 1, 2, 3, 4]) In [173]: arr * 4 Out[173]: array([ 0, 4, 8, 12, 16])
这里我们说标量4已经被广播给乘法运算中的其他元素。
个人理解,一个标量,无论于几维的数组进行操作,都可以完美的进行广播,我把标量想象成0维,就好比杭州麻将的百搭,可以完美适配任何的维度【也就是所谓的广播】
例如,我们可以通过减去列均值来降低数组中的每一列的数值。
In [174]: arr = np.random.randn(4,3)
In [175]: arr
Out[175]:
array([[-0.21606821, -0.16318088, -0.19862113],
[ 0.35614129, -1.2219687 , -2.05856434],
[ 0.21446578, -0.6502341 , 1.33610149],
[ 0.0314505 , 0.24780944, 0.36204802]])
In [176]: arr.mean(0)
Out[176]: array([ 0.09649734, -0.44689356, -0.13975899])
In [177]: demeaned = arr - arr.mean(0)
In [178]: demeaned
Out[178]:
array([[-0.31256555, 0.28371268, -0.05886214],
[ 0.25964395, -0.77507514, -1.91880535],
[ 0.11796844, -0.20334054, 1.47586048],
[-0.06504684, 0.694703 , 0.50180701]])
In [179]: demeaned.mean(0)
Out[179]: array([6.93889390e-18, 5.55111512e-17, 5.55111512e-17])
In [180]: demeaned.mean(0).shape
Out[180]: (3,)
In [181]:
重点🏁重点 如果每个结尾的维度(即从尾部开始的),轴长度都匹配或者长度都是1,两个二维数组就是可以兼容广播的。之后,广播会在丢失的或长度为1的轴上进行。
这句话真的是金句,翻译的也很好,把另一本书上几句话说明的事情,抽象成了一句话,太棒了。
下面演示列广播,也就是行匹配了。
In [181]: arr
Out[181]:
array([[-0.21606821, -0.16318088, -0.19862113],
[ 0.35614129, -1.2219687 , -2.05856434],
[ 0.21446578, -0.6502341 , 1.33610149],
[ 0.0314505 , 0.24780944, 0.36204802]])
In [183]: row_meas = arr.mean(1)
In [184]: row_meas.shape
Out[184]: (4,)
In [185]: row_meas.reshape(4,1)
Out[185]:
array([[-0.19262341],
[-0.97479725],
[ 0.30011106],
[ 0.21376932]])
In [186]: arr.shape
Out[186]: (4, 3)
In [187]: demeaned = arr - row_meas.reshape(4,1)
In [188]: demeaned
Out[188]:
array([[-0.0234448 , 0.02944252, -0.00599772],
[ 1.33093854, -0.24717145, -1.08376709],
[-0.08564527, -0.95034516, 1.03599043],
[-0.18231882, 0.03404012, 0.1482787 ]])
In [189]: demeaned.mean(1)
Out[189]: array([-9.25185854e-18, -7.40148683e-17, 0.00000000e+00, 0.00000000e+00])
In [190]:
A3.1在其他轴上进行广播
在三维情况下,在三个维度中的任何一个维度上进行广播,只是将数据重塑为形状兼容的问题。书中的图简直太棒了。
因此,一个常见的问题是需要添加一个长度为1的新轴,专门用于广播目的。
使用reshpe当然可以,当需要构建一个元祖太麻烦了。因此,NumPy数组提供了一种通过索引插入新轴的特殊语法。
使用np.newaxis属性和"完整"切片来插入新轴:
In [193]: arr = np.zeros((4,4))
In [194]: arr
Out[194]:
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
In [195]: arr.shape
Out[195]: (4, 4)
In [196]: arr.ndim
Out[196]: 2
In [197]: arr3d = arr[:, np.newaxis,:]
In [198]: arr3d
Out[198]:
array([[[0., 0., 0., 0.]],
[[0., 0., 0., 0.]],
[[0., 0., 0., 0.]],
[[0., 0., 0., 0.]]])
In [199]: arr3d.ndim
Out[199]: 3
In [200]: arr3d.shape
Out[200]: (4, 1, 4)
这是二维数组添加了一个维度,变成了三维数组
In [201]: arr_1d = np.random.normal(size=3)
In [202]: arr_1d[:,np.newaxis]
Out[202]:
array([[-0.070474 ],
[ 0.80928887],
[ 1.04804804]])
In [203]: arr_1d[np.newaxis,:]
Out[203]: array([[-0.070474 , 0.80928887, 1.04804804]])
因此,如果我有一个三维数组并想在轴2上减去均值,需要这么写
浙公网安备 33010602011771号