利用python进行数据分析 第四章 4.4 用于数组的文件输入输出

4.4 用于数组的文件输入输出

NumPy能够读写磁盘上的文本数据或二进制数据。这一小节只讨论NumPy的内置二进制格式,因
为更多的用户会使用pandas或其它工具加载文本或表格数据(见第6章)。
np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进
制格式保存在扩展名为.npy的文件中的:
In [213]: arr = np.arange(10)
In [214]: np.save('some_array', arr)

如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以通过np.load读取磁盘
上的数组:
In [215]: np.load('some_array.npy')
Out[215]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

通过np.savez可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可:
In [216]: np.savez('array_archive.npz', a=arr, b=arr)

加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载:
In [217]: arch = np.load('array_archive.npz')
In [218]: arch['b']
Out[218]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

如果要将数据压缩,可以使用numpy.savez_compressed:
In [219]: np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)

4.5 线性代数

线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。
不像Ḁ 些语言(如MATLAB),通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个
矩阵点积。因此,NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是numpy命名
空间中的一个函数):
In [223]: x = np.array([[1., 2., 3.], [4., 5., 6.]])
In [224]: y = np.array([[6., 23.], [-1, 7], [8, 9]])
In [225]: x
Out[225]:
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
In [226]: y
Out[226]:
array([[ 6., 23.],
[ -1., 7.],
[ 8., 9.]])
In [227]: x.dot(y)
Out[227]:
array([[ 28., 64.],
[ 67., 181.]])
x.dot(y)等价于np.dot(x, y):
In [228]: np.dot(x, y)
Out[228]:
array([[ 28., 64.],
[ 67., 181.]])

一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后将会得到一个一维数组:
In [229]: np.dot(x, np.ones(3))
Out[229]: array([ 6., 15.])

@符(类似Python 3.5)也可以用作中缀运算符,进行矩阵乘法:
In [230]: x @ np.ones(3)
Out[230]: array([ 6., 15.])

numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB和
R等语言所使用的是相同的行业标准线性代数库,如BLAS、LAPACK、Intel MKL(Math Kernel
Library,可能有,取决于你的NumPy版本)等:
In [231]: from numpy.linalg import inv, qr
In [232]: X = np.random.randn(5, 5)
In [233]: mat = X.T.dot(X)
In [234]: inv(mat)
Out[234]:
array([[ 933.1189, 871.8258, -1417.6902, -1460.4005, 1782.1391],
[ 871.8258, 815.3929, -1325.9965, -1365.9242, 1666.9347],
[-1417.6902, -1325.9965, 2158.4424, 2222.0191, -2711.6822],
[-1460.4005, -1365.9242, 2222.0191, 2289.0575, -2793.422 ],
[ 1782.1391, 1666.9347, -2711.6822, -2793.422 , 3409.5128]])
In [235]: mat.dot(inv(mat))
Out[235]:
array([[ 1., 0., -0., -0., -0.],
[-0., 1., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[-0., 0., 0., 1., -0.],
[-0., 0., 0., 0., 1.]])
In [236]: q, r = qr(mat)
In [237]: r
Out[237]:
array([[-1.6914, 4.38 , 0.1757, 0.4075, -0.7838],
[ 0. , -2.6436, 0.1939, -3.072 , -1.0702],
[ 0. , 0. , -0.8138, 1.5414, 0.6155],
[ 0. , 0. , 0. , -2.6445, -2.1669],
[ 0. , 0. , 0. , 0. , 0.0002]])

表达式X.T.dot(X)计算X和它的转置X.T的点积。
表4-7中列出了一些最常用的线性代数函数。

4.6 伪随机数生成

numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布
的样本值的函数。例如,你可以用normal来得到一个标准正态分布的4×4样本数组:
In [238]: samples = np.random.normal(size=(4, 4))
In [239]: samples
Out[239]:
array([[ 0.5732, 0.1933, 0.4429, 1.2796],
[ 0.575 , 0.4339, -0.7658, -1.237 ],
[-0.5367, 1.8545, -0.92 , -0.1082],
[ 0.1525, 0.9435, -1.0953, -0.144 ]])

而Python内置的random模块则只能一次生成一个样本值。从下面的测试结果中可以看出,如果需
要产生大量样本值,numpy.random快了不止一个数量级:
In [240]: from random import normalvariate
In [241]: N = 1000000
In [242]: %timeit samples = [normalvariate(0, 1) for _ in range(N)]
1.77 s +- 126 ms per loop (mean +- std. dev. of 7 runs, 1 loop each)
In [243]: %timeit np.random.normal(size=N)
61.7 ms +- 1.32 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)

我们说这些都是伪随机数,是因为它们都是通过算法基于随机数生成器种子,在确定性的条件下生
成的。你可以用NumPy的np.random.seed更改随机数生成种子:
In [244]: np.random.seed(1234)
numpy.random的数据生成函数使用了全局的随机种子。要避免全局状态,你可以使用
numpy.random.RandomState,创建一个与其它隔离的随机数生成器:
In [245]: rng = np.random.RandomState(1234)
In [246]: rng.randn(10)
Out[246]:
array([ 0.4714, -1.191 , 1.4327, -0.3127, -0.7206, 0.8872, 0.8596,
-0.6365, 0.0157, -2.2427])

表4-8列出了numpy.random中的部分函数。在下一节中,我将给出一些利用这些函数一次性生成
大量样本值的范例。

4.7 示例:随机漫步

我们通过模拟随机漫步来说明如何运用数组运算。先来看一个简单的随机漫步的例子:从0开始,
步长1和-1出现的概率相等。
下面是一个通过内置的random模块以纯Python的方式实现1000步的随机漫步:
In [247]: 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)
.....:

图4-4是根据前100个随机漫步值生成的折线图:
In [249]: plt.plot(walk[:100])

不难看出,这其实就是随机漫步中各步的累计和,可以用一个数组运算来实现。因此,我用
np.random模块一次性随机产生1000个“掷硬币”结果(即两个数中任选一个),将其分别设置为1
或-1,然后计算累计和:
In [251]: nsteps = 1000
In [252]: draws = np.random.randint(0, 2, size=nsteps)
In [253]: steps = np.where(draws > 0, 1, -1)
In [254]: walk = steps.cumsum()
#有了这些数据之后,我们就可以沿着漫步路径做一些统计工作了,比如求取最大值和最小值:
In [255]: walk.min()
Out[255]: -3
In [256]: walk.max()
Out[256]: 31

pycharm代码如下:

import numpy as np
import matplotlib.pyplot as plt
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)

plt.plot(walk[:100])
plt.show()

nsteps = 1000
draws = np.random.randint(0, 2, size=nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()
print(walk.min())
print(walk.max())
现在来看一个复杂点的统计任务——首次穿越时间,即随机漫步过程中第一次到达Ḁ 个特定值的时
间。假设我们想要知道本次随机漫步需要多久才能距离初始0点至少10步远(任一方向均可)。
np.abs(walk)>=10可以得到一个布尔型数组,它表示的是距离是否达到或超过10,而我们想要知
道的是第一个10或-10的索引。可以用argmax来解决这个问题,它返回的是该布尔型数组第一个
最大值的索引(True就是最大值):
In [257]: (np.abs(walk) >= 10).argmax()
Out[257]: 37

注意,这里使用argmax并不是很高效,因为它无论如何都会对数组进行完全扫᧿ 。在本例中,只
要发现了一个True,那我们就知道它是个最大值了。
一次模拟多个随机漫步
如果你希望模拟多个随机漫步过程(比如5000个),只需对上面的代码做一点点修改即可生成所
有的随机漫步过程。只要给numpy.random的函数传入一个二元元组就可以产生一个二维数组,然
后我们就可以一次性计算5000个随机漫步过程(一行一个)的累计和了:
In [258]: nwalks = 5000
In [259]: nsteps = 1000
In [260]: draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1
In [261]: steps = np.where(draws > 0, 1, -1)
In [262]: walks = steps.cumsum(1)
In [263]: walks
Out[263]:
array([[ 1, 0, 1, ..., 8, 7, 8],
[ 1, 0, -1, ..., 34, 33, 32],
[ 1, 0, -1, ..., 4, 5, 4],
...,
[ 1, 2, 1, ..., 24, 25, 26],
[ 1, 2, 3, ..., 14, 13, 14],
[ -1, -2, -3, ..., -24, -23, -22]])

现在,我们来计算所有随机漫步过程的最大值和最小值:
In [264]: walks.max()
Out[264]: 138
In [265]: walks.min()
Out[265]: -133

得到这些数据之后,我们来计算30或-30的最小穿越时间。这里稍微复杂些,因为不是5000个过
程都到达了30。我们可以用any方法来对此进行检查:
In [266]: hits30 = (np.abs(walks) >= 30).any(1)
In [267]: hits30
Out[267]: array([False, True, False, ..., False, True, False], dtype=bool)
In [268]: hits30.sum() # Number that hit 30 or -30
Out[268]: 3410

然后我们利用这个布尔型数组选出那些穿越了30(绝对值)的随机漫步(行),并调用argmax在
轴1上获取穿越时间:
In [269]: crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
In [270]: crossing_times.mean()
Out[270]: 498.88973607038122

pycharm代码如下:

import numpy as np
import matplotlib.pyplot as plt
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)
print(walks.max())
print(walks.min())
plt.plot(walks)
plt.show()
hits30 = (np.abs(walks) >= 30).any(1)
print(hits30.sum()) # Number that hit 30 or -30
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
print(crossing_times.mean())

请尝试用其他分布方式得到漫步数据。只需使用不同的随机数生成函数即可,如normal用于生成
指定均值和标准差的正态分布数据:
In [271]: steps = np.random.normal(loc=0, scale=0.25,
.....: size=(nwalks, nsteps))

pycharm:代码如下

import numpy as np
import matplotlib.pyplot as plt
nwalks = 5000
nsteps = 1000
steps = np.random.normal(loc=0, scale=0.25,size=(nwalks, nsteps))
plt.plot(steps)
plt.show()

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值