《利用Python进行数据分析》第4章 NumPy基础:数组与向量化计算 笔记
4.1 NumPy ndarray:多维数组对象
In [121]: data = np.random.randn(2,3) In [122]: data Out[122]: array([[ 0.54913133, 1.22840566, -0.2471307 ], [ 1.13488389, -0.94895987, 1.06972625]]) In [123]: data * 10 Out[123]: array([[ 5.49131332, 12.28405661, -2.47130702], [11.34883889, -9.48959874, 10.69726254]]) In [124]: data + data Out[124]: array([[ 1.09826266, 2.45681132, -0.4942614 ], [ 2.26976778, -1.89791975, 2.13945251]]) In [125]: data.shape Out[125]: (2, 3) In [126]: data.dtype Out[126]: dtype('float64')
4.1.1 生成ndarray
最简单的方法使用array函数,输入一个序列即可,比如list:
除非主动声明,否则np.array会自动给data搭配适合的类型,并保存在dtype里:
4.1.2 ndarray的数据类型
可以通过.astype的方式修改数组的类型
arr = np.array([1, 2, 3, 4, 5]) arr.dtype
float_arr = arr.astype(np.float64) float_arr.dtype float_arr
array([1., 2., 3., 4., 5.])
记住,astype总是会返回一个新的数组
4.1.3 NumPy数组算术
数组之所以重要,是因为不用写for循环就能表达很多操作,这种特性叫做vectorization(向量化)。任何两个大小相等的数组之间的运算,都是element-wise(点对点
4.1.4基础索引于切片
一维数组的切片跟列表切片操作一样,记住列表切片是浅拷贝,数组切片是视图。
在一个多维数组中,你可以省略后续索引值,返回的对象将是降低一个维度的数组。
In [138]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]) In [139]: arr3d[0] Out[139]: array([[1, 2, 3], [4, 5, 6]]) In [140]: arr3d Out[140]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]]) In [141]: arr3d[0] = 99 In [142]: arr3d Out[142]: array([[[99, 99, 99], [99, 99, 99]], [[ 7, 8, 9], [10, 11, 12]]]) In [143]:
可以选出一个维度的数组通过数值给维度内所有的元素赋值。
4.1.4.1 数组的切片索引
可以结合索引于切片混合使用。
切片通过:使用,单:就是包含所有
4.1.5布尔索引
In [149]: names Out[149]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4') In [150]: data Out[150]: array([[-1.00710439, -1.69541524, -1.18684578, 0.09359326], [-1.28548156, -1.23123463, 2.15290595, -1.4515048 ], [-0.61391704, 0.65959513, 0.28808878, 0.58197308], [-0.73955125, 0.89556729, 0.99848676, -1.44457724], [ 0.44648024, 2.0481886 , 0.01026874, 1.24072392], [-0.84346087, -1.41325766, -0.29533334, 0.89035935], [ 0.69944641, -0.74938503, -0.22261616, 1.42224533]]) In [151]:
data的每一行对应每个人的信息
In [152]: names == 'Bob' Out[152]: array([ True, False, False, True, False, False, False]) In [153]: data[names == 'Bob'] Out[153]: array([[-1.00710439, -1.69541524, -1.18684578, 0.09359326], [-0.73955125, 0.89556729, 0.99848676, -1.44457724]])
选取了'Bob'的数据之后,我们还可以通过索引于切片选择需要的信息
In [157]: data[names == 'Bob', 2:] Out[157]: array([[-1.18684578, 0.09359326], [ 0.99848676, -1.44457724]]) In [158]: data[names == 'Bob', 3] Out[158]: array([ 0.09359326, -1.44457724])
同!=于& | ~等操作符也可以进行一些多条件操作。
使用布尔值索引选择数据时,总是生成数据的拷贝,即使返回的数据并没有发生任何变化。
4.1.6神奇索引【魔术索引】
就是用一个数组进行数据的索引。
In [159]: arr = np.empty((8, 4)) ...: for i in range(8): ...: arr[i] = i ...: arr Out[159]: array([[0., 0., 0., 0.], [1., 1., 1., 1.], [2., 2., 2., 2.], [3., 3., 3., 3.], [4., 4., 4., 4.], [5., 5., 5., 5.], [6., 6., 6., 6.], [7., 7., 7., 7.]]) In [160]: arr[[4,3,0,6]] Out[160]: array([[4., 4., 4., 4.], [3., 3., 3., 3.], [0., 0., 0., 0.], [6., 6., 6., 6.]]) In [161]: arr[[-3,-5,-7]] Out[161]: array([[5., 5., 5., 5.], [3., 3., 3., 3.], [1., 1., 1., 1.]]) In [162]:
传递多个索引数组情况有些许不同,这样会根据每个索引元素对应的元素选出一个一个数组:
In [162]: arr = np.arange(32).reshape((8, 4)) In [163]: arr Out[163]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 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]]) In [164]: arr[[1, 5, 7, 2], [0, 3, 1, 2]] Out[164]: array([ 4, 23, 29, 10])
In [165]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]] Out[165]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
上面可以分成两步理解,第一步通过魔法索引取出需要的行,第二步,通过魔法索引调换列的位置。
请牢记魔法索引于切片不同,它总是将数据复制到一个新的数组中。
4.1.7数组转置和换轴
可以通过T对二维数组进行转置
In [184]: arr = np.arange(15).reshape((3, 5)) In [185]: arr Out[185]: array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) In [186]: arr.T Out[186]: array([[ 0, 5, 10], [ 1, 6, 11], [ 2, 7, 12], [ 3, 8, 13], [ 4, 9, 14]])
np.dot会计算矩阵内积
In [187]: arr = np.random.randn(6, 3) In [188]: arr Out[188]: array([[ 0.24976157, 0.31071076, -0.47578096], [-1.26412403, -0.43652115, 0.60267153], [-0.21745991, 1.73535988, -0.11671441], [ 0.86726475, 0.76131387, 0.80603824], [-1.59655193, 0.05412926, 0.04500314], [-0.55093819, -0.14713175, 1.3890123 ]]) In [189]: np.dot(arr.T,arr) Out[189]: array([[ 5.31233831, 0.9069503 , -0.99336388], [ 0.9069503 , 3.90274234, -0.20173437], [-0.99336388, -0.20173437, 3.18428085]]) In [190]:
对于更高维度的数组,transpone方法可以接收包含轴编号的元祖,由于置换轴
In [190]: arr = np.arange(16).reshape((2, 2, 4)) In [191]: arr Out[191]: array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]]) In [192]: arr.transpose((1,0,2)) Out[192]: array([[[ 0, 1, 2, 3], [ 8, 9, 10, 11]], [[ 4, 5, 6, 7], [12, 13, 14, 15]]]) In [193]:
看的有点晕,使用.T是进行转置换轴的一个特殊案例。ndarray有一个swapaxes方法,该方法接收一对轴编号作为参数,并对轴进行调整用于数据重组。
In [199]: arr = np.arange(16).reshape((2, 2, 4)) In [200]: arr Out[200]: array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]]) In [201]: arr.swapaxes(1,2) Out[201]: array([[[ 0, 4], [ 1, 5], [ 2, 6], [ 3, 7]], [[ 8, 12], [ 9, 13], [10, 14], [11, 15]]])
swapaxes返回的是数据的视图,二分没有数据进行复制。
4.2通用函数:快速的逐元素数组函数
通用函数,也可以称为ufunc,是一种是ndarray数据中进行逐元素操作的函数。某些简单函数接收一个或者多个标量数值,并产生一个或多个标量结果,而通用函数是对这些简单函数的向量化封装。
简单的逐元素转换,也就是一元通用函数。
In [202]: arr = np.arange(10) In [203]: arr Out[203]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [204]: np.sqrt(arr) Out[204]: array([0. , 1. , 1.41421356, 1.73205081, 2. , 2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ]) In [205]: np.exp(arr) Out[205]: 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]) In [206]:
比如add或maximum则会接收两个数组并返回一个数组作为结果,称为二元通用函数:
In [206]: x = np.random.randn(8) In [207]: y = np.random.randn(8) In [208]: np.maximum(x,y) Out[208]: array([-0.38501671, 1.38583418, 1.58075387, 0.74666747, -0.61495016, -1.70572677, 0.55603417, 0.2539314 ]) In [209]: x Out[209]: array([-0.40859617, -2.57965322, 1.58075387, 0.74666747, -1.97264923, -1.70572677, -0.48024945, 0.2539314 ]) In [210]: y Out[210]: array([-0.38501671, 1.38583418, -2.23990722, 0.28165291, -0.61495016, -2.15174086, 0.55603417, -0.30538897])
也有一些通用函数返回多个数组。比如modf,是Python内建函数divmod的向量化版本。它返回一个浮点值数组的小数部分和整数部分
In [213]: arr = np.random.randn(7) * 5 In [214]: arr Out[214]: array([ 0.77025919, -2.31969754, 3.19981579, -0.48684179, -1.1123567 , 5.08604258, 1.5711621 ]) In [215]: remainder, whole_part = np.modf(arr) In [216]: remainder Out[216]: array([ 0.77025919, -0.31969754, 0.19981579, -0.48684179, -0.1123567 , 0.08604258, 0.5711621 ]) In [217]: whole_part Out[217]: array([ 0., -2., 3., -0., -1., 5., 1.])
通用函数独有一个可选参数out,进行输出
In [245]: arr Out[245]: array([ 0.77025919, -2.31969754, 3.19981579, -0.48684179, -1.1123567 , 5.08604258, 1.5711621 ]) In [246]: np.sqrt(arr) /usr/local/bin/ipython:1: RuntimeWarning: invalid value encountered in sqrt #!/Library/Developer/CommandLineTools/usr/bin/python3 Out[246]: array([0.87764411, nan, 1.78880289, nan, nan, 2.25522562, 1.25346005]) In [247]: In [247]: np.sqrt(arr, arr) /usr/local/bin/ipython:1: RuntimeWarning: invalid value encountered in sqrt #!/Library/Developer/CommandLineTools/usr/bin/python3 Out[247]: array([0.87764411, nan, 1.78880289, nan, nan, 2.25522562, 1.25346005]) In [248]: arr Out[248]: array([0.87764411, nan, 1.78880289, nan, nan, 2.25522562, 1.25346005])
4.3使用数组进行面向数组编程
4.3.1 将条件逻辑作为数组操作
numpy.where函数是三元逼到事x if condition else y的向量化版本
In [15]: xarr = 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]) In [16]: result = [(x if c else y) for x, y, c in zip(xarr,yarr,cond)] In [17]: result Out[17]: [1.1, 2.2, 1.3, 1.4, 2.5]
其实这个已经写的很不错了。
但用numpy.where更快加
In [15]: xarr = 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]) In [16]: result = [(x if c else y) for x, y, c in zip(xarr,yarr,cond)] In [17]: result Out[17]: [1.1, 2.2, 1.3, 1.4, 2.5] In [18]: result = np.where(cond, xarr, yarr) ...: result Out[18]: array([1.1, 2.2, 1.3, 1.4, 2.5]) In [19]: %timeit [(x if c else y) for x, y, c in zip(xarr,yarr,cond)] 1.83 µs ± 8.83 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) In [21]: %timeit np.where(cond, xarr, yarr) 1.26 µs ± 4.16 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
np.where的第二个和第三个参数并不需要是数组,它们可以是标量。where在数据分析的一个典型用法是根据一个数组来生成一个新的数据.
In [22]: arr = np.random.randn(4,4,) In [23]: arr Out[23]: array([[-0.97648403, 1.02798534, -0.93480427, 1.374078 ], [-1.84431231, 1.93422431, 0.00659856, 1.86583238], [ 1.08911163, -1.73644384, -0.45355162, 0.39349842], [ 0.10746774, 0.59731332, 0.72120852, 0.95930772]]) In [24]: arr > 0 Out[24]: array([[False, True, False, True], [False, True, True, True], [ True, False, False, True], [ True, True, True, True]]) In [25]: np.where(arr>0,2,-2) Out[25]: array([[-2, 2, -2, 2], [-2, 2, 2, 2], [ 2, -2, -2, 2], [ 2, 2, 2, 2]]) In [26]:
4.3.2数学和统计方法
聚合函数,比如sum,mean,std
In [40]: arr = np.random.randn(5, 4) In [41]: arr Out[41]: array([[ 0.56675129, 1.9773117 , -0.85703968, 1.52822419], [ 0.90498759, 0.54164539, 0.7010517 , -0.05463528], [ 0.08180353, -1.28173057, 0.15845745, 0.47208329], [-0.57725913, -0.70663686, -0.72830859, 1.22866412], [ 2.29306912, -0.75646038, -3.47690011, -0.58694743]]) In [42]: arr.mean() Out[42]: 0.07140656664970654 In [43]: np.mean(arr) Out[43]: 0.07140656664970654 In [44]: arr.sum() Out[44]: 1.428131332994131 In [45]: arr.mean(axis=1) Out[45]: array([ 0.80381187, 0.52326235, -0.14234658, -0.19588512, -0.6318097 ]) In [46]: arr.mean(1) Out[46]: array([ 0.80381187, 0.52326235, -0.14234658, -0.19588512, -0.6318097 ])
axis就是把该维度合并了,所以axis=1,表示把列维度合并了。
In [48]: arr Out[48]: array([0, 1, 2, 3, 4, 5, 6, 7]) In [49]: arr.cumsum() Out[49]: array([ 0, 1, 3, 6, 10, 15, 21, 28]) In [50]:
In [52]: arr = np.arange(9).reshape(3,3,) In [53]: arr Out[53]: array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) In [54]: arr.cumsum(axis=0) Out[54]: array([[ 0, 1, 2], [ 3, 5, 7], [ 9, 12, 15]]) In [55]: arr.cumprod(1) Out[55]: array([[ 0, 0, 0], [ 3, 12, 60], [ 6, 42, 336]]) In [56]:
4.3.3布尔值数组的方法
In [58]: arr = np.random.randn(10000) In [59]: (arr>0).sum() Out[59]: 4923 In [60]: np.count_nonzero(arr>0) Out[60]: 4923 In [61]:
In [62]: arr.all() Out[62]: True In [63]: np.all(arr) Out[63]: True In [64]: arr.any() Out[64]: True
4.3.4 排序
和Python的内建列表类似,NumPy数组可以使用sort方法按位置排序
In [72]: arr Out[72]: array([ 0.21161757, 0.14908061, -0.60250355, 0.21311908, -0.97577853, -0.80022333]) In [73]: arr.sort() In [74]: arr Out[74]: array([-0.97577853, -0.80022333, -0.60250355, 0.14908061, 0.21161757, 0.21311908]) In [75]: arr = np.random.randn(5, 3) In [76]: arr Out[76]: array([[ 1.0852479 , -0.1907266 , -1.31399142], [ 0.1200116 , -0.99438789, -0.52340313], [-0.72657845, 0.67799928, 0.0685691 ], [-1.20275342, 0.65808226, -0.09704002], [ 0.99616396, 0.96444138, -0.81331213]]) In [77]: arr.sort(1) In [78]: arr Out[78]: array([[-1.31399142, -0.1907266 , 1.0852479 ], [-0.99438789, -0.52340313, 0.1200116 ], [-0.72657845, 0.0685691 , 0.67799928], [-1.20275342, -0.09704002, 0.65808226], [-0.81331213, 0.96444138, 0.99616396]]) In [79]:
4.3.5 唯一值与其他集合逻辑
np.unique返回的是排好序的唯一值
In [94]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) In [95]: np.unique(names) Out[95]: array(['Bob', 'Joe', 'Will'], dtype='<U4') In [96]: ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4]) In [97]: np.unique(ints) Out[97]: array([1, 2, 3, 4])
返回一维数组的元素是否在另外一个元素内
In [98]: values = np.array([6, 0, 0, 3, 2, 5, 6]) In [99]: np.in1d(values,[2,3,4]) Out[99]: array([False, False, False, True, True, False, False])
4.4 使用数组进行文件输入和输出
NumPy可以在硬盘中将数据以文本或二进制文件的形式进行存入硬盘或者硬盘载入。【一般用pandas】
In [101]: arr = np.arange(10) In [102]: np.save('some_array', arr) In [103]: np.load('some_array.npy') Out[103]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [104]:
如果多个数据,可以通过np.savez的方式,后面通过关键字传参
n [101]: arr = np.arange(10) In [102]: np.save('some_array', arr) In [103]: np.load('some_array.npy') Out[103]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [104]: np.savez('array_archive.npz',a=arr,b=arr) In [105]: arch = np.load('array_archive.npz') In [107]: arch['a'] Out[107]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [108]: np.savez_compressed('arrays_compressed.npz',a=arr,b=arr) In [109]: arch = np.load('arrays_compressed.npz') In [110]: arch['b'] Out[110]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [111]: ll *.npz -rw-r--r-- 1 shijianzhong staff 650 10 12 22:14 array_archive.npz -rw-r--r-- 1 shijianzhong staff 424 10 12 22:16 arrays_compressed.npz
4.5 线性代数
x.dot(y)点乘积可以用x@y表示。
4.6伪随机数生成
In [123]: samples = np.random.normal(size=(4,4)) In [124]: samples Out[124]: array([[ 1.22048664, 0.32098404, -0.03541593, 1.25904642], [-1.24029633, 1.31176221, -0.46916461, -0.18168937], [ 1.55699043, 0.49925716, -0.71558119, 1.60975006], [ 0.84303413, -0.86610878, -0.92012729, -1.17054933]])
np.random.seed可以更改NumPy的随机数种子
random.RandomState创建一个随机数生成器,使数据独立于其他随机数状态。
4.7 示例:随机漫步
In [125]: import random ...: position = 0 ...: walk = [position] ...: steps = 1000 ...: for i in range(steps): ...: step = 1 if random.randint(0, 1) else -1 ...: position += step ...: walk.append(position) ...: In [126]: import matplotlib.pyplot as plt In [127]: plt.figure() Out[127]: <Figure size 640x480 with 0 Axes> In [128]: plt.plot(walk[:100]) Out[128]: [<matplotlib.lines.Line2D at 0x121389320>]
好无聊的试验。
4.7.1一次性模拟多次随机漫步
模拟多次5000次的情况,也就使多行数据
In [5]: nwalks = 5000 ...: nsteps = 1000 ...: draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1 ...: steps = np.where(draws > 0, 1, -1) ...: walks = steps.cumsum(1) ...: walks Out[5]: array([[ -1, -2, -1, ..., -58, -57, -58], [ 1, 2, 3, ..., 34, 33, 32], [ -1, -2, -3, ..., -16, -17, -16], ..., [ 1, 0, -1, ..., 44, 43, 44], [ 1, 2, 1, ..., 18, 17, 16], [ 1, 0, -1, ..., 40, 39, 40]])
计算这些随机满足的最大值于最小值
In [28]: walks.max() Out[28]: 102 In [29]: walks.min() Out[29]: -121
统计有达到累计30的
In [30]: hits30 = (np.abs(walks)>30).any(1) In [33]: hits30 Out[33]: array([ True, True, True, ..., True, False, True]) In [34]: hits30.sum() Out[34]: 3238
通过any生成生成结果,并通过sum对结果进行求和
开始求穿越花费的时间。
# 选出走到了30步以上的行,再次通过>=0转换成bool索引,通过argmax(1),选出首次出现索引。 In [35]: crossing_times = (np.abs(walks[hits30]) >=30).argmax(1) In [36]: crossing_times Out[36]: array([569, 923, 429, ..., 743, 523, 809]) In [37]: crossing_times.mean() Out[37]: 496.17850525015444 In [38]: