《利用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上减去均值,需要这么写

 

posted @ 2020-10-14 09:19  就是想学习  阅读(122)  评论(0编辑  收藏  举报