一、numpy将数据保存到磁盘和从磁盘加载数据
NumPy 能够以某些文本或二进制格式将数据保存到磁盘和从磁盘加载数据。这里,先学习NumPy 的内置二进制格式,在后面学习pandas的时候再学习用pandas来加载文本或表格数据。
在磁盘上有效地保存和加载数组数据numpy常用以下这两个主要函数:
numpy.save和numpy.load
默认情况下,数组以文件扩展名为 .npy 的未压缩原始二进制格式保存。用法其实也很简单,直接写代码:
import numpy as np
arr = np.arange(10)
np.save("some_array", arr)
np.save("some_array", arr) 第一个参数是保存到磁盘的文件名,第二个参数是要保存的数据。执行完之后,会在python文件相同目录下生成一个some_array.npy二进制文件。
也可以用绝对路径指定保存的文件路径,我们改下上面的代码试试:
import numpy as np
arr = np.arange(10)
np.save("d:\some_array", arr)
这样就保存在d盘根目录下了
接下来我们用numpy.load函数将这个数组从磁盘中加载,并打印的vs code的控制台。
import numpy as np
a = np.load("some_array.npy")
print(a)
load("some_array.npy") 函数传入参数为 要加载的文件名,输出结果如下:
[0 1 2 3 4 5 6 7 8 9]
另外numpy还提供了另外一个函数savez,可以将多个数组保存在未压缩的存档中,并将数组作为关键字参数传递,例如:
import numpy as np
arr = np.arange(10)
#存储到磁盘上
np.savez("array_archive.npz", a=arr, b=arr)
#从磁盘加载
arch = np.load("array_archive.npz")
print(arch["a"])
print(arch["b"])
执行上面的代码,会在该python文件相同目录生成一个rray_archive.npz二进制文件,然后我又从该文件中加载了数组并分别打印出了数组a和数组b(两个数组内容是一样的)
vs code控制台输出:
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
以上生成的都是未压缩的二进制文件,如果数据能被很好的压缩,还可以使用 numpy.savez_compressed函数,例如:
np.savez_compressed("arrays_compressed.npz", a=arr, b=arr)
同样加载回来用load函数即可。
NumPy还提供了其他格式文件的存取方式,比如txt、csv文件的存取方式,用savetxt和loadtxt函数。
numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ', encoding=None)
fname:文件路径和名称,数组将被保存到此文件。
X:需要保存的数组数据。
fmt:存储格式,默认是浮点数格式。
delimiter:分隔符,默认是空格。
newline:行分隔符,默认是换行符\n。
header、footer、comments:分别用于在文件开头、结尾添加内容和注释。
encoding:文件编码,默认无编码。
import numpy as np
data = np.array([[1, 2, 3], [4, 5, 6]])
#将数组data保存为txt文件
np.savetxt('data.txt', data, fmt='%d')
#将数据data保存为csv文件,分割符用‘,’
np.savatxt('data.csv', data, delimiter=',')
numpy.loadtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0, encoding='bytes')
fname:待读取的文件路径和名称。
dtype:读取结果的数据类型,默认是浮点数。
comments:注释字符,默认是#。
delimiter:分隔符,默认是空格。
skiprows:跳过的行数,默认是0行。
usecols:读取的列索引,可以是整数或序列。
unpack:是否将多列数据解包为单独的变量,默认是False。
encoding:文件编码,默认是字节编码。
import numpy as np
#读取刚刚存入的txt文件
data = np.loadtxt('data.txt', delimiter=',')
#读取刚刚存入的csv文件
data = np.loadtxt('data.csv', np.int, delimiter=',')
二、线性代数
线性代数运算(如矩阵乘法、分解、行列式和其他方阵数学)是许多数组库的都具备的重要功能。用符号 * 将两个二维数组相乘是元素乘积,而矩阵乘法在numpy中需要使用函数。因此,有一个函数 dot,用于矩阵乘法。
import numpy as np
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
# x矩阵乘以y矩阵
z = x.dot(y)
print(z)
输出结果:
[[ 28. 64.]
[ 67. 181.]]
也可以使用 np.dot(x, y) ,与x.dot(y) 等效。
二维数组和相同大小的一维数组之间的矩阵积会产生一维数组,例如
import numpy as np
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
z = x @ np.ones(3)
print(z)
这里用了符号@ 代替 dot函数,这是等效的,在之前的学习中也讲到过。这个计算二维数组与一维数组[1 1 1]的乘积 ,结果是一维数组,输出结果:[ 6. 15.]。
线性代数中常用的计算还有求矩阵的逆矩阵和将矩阵分解为QR矩阵的计算,NumPy库中linalg模块,提供了inv函数求逆矩阵,qr函数将矩阵分解为QR矩阵。下面我们用from import来导入这两个函数,可以这样写:from numpy.linalg import inv, qr
import numpy as np
from numpy.linalg import inv, qr
rng = np.random.default_rng(seed=36)
X = rng.standard_normal((5, 5))
mat = X.T @ X
res_inv = inv(mat)
print(res_inv)
输出结果:
[[221.27842497 -85.91261283 33.33624306 -52.66279348 -70.22280046]
[-85.91261283 33.8112001 -12.91414019 20.66064125 27.23849554]
[ 33.33624306 -12.91414019 5.59253004 -7.62654026 -10.46466005]
[-52.66279348 20.66064125 -7.62654026 13.03311817 16.78772175]
[-70.22280046 27.23849554 -10.46466005 16.78772175 22.65748987]]
以下列表是一些常用的线性代数函数
三、 写个示例来总结下上面包括之前学习的内容
写个随机游走模拟:学习利用数组操作的说明性应用。我们首先考虑一个从 0 开始的简单随机游走,步长为 1 和 –1 的概率相等。(这个纯python实现)
import random
import matplotlib.pyplot as plt
#! 代码块开始
position = 0
walk = [position]
nsteps = 1000
for _ in range(nsteps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)
#! 代码块结束
plt.plot(walk[:100])
plt.show()
运行后输出如下界面:
您可能会观察到 walk 是随机步骤的累积和,并且可以作为数组表达式进行计算。 因此,我使用 numpy.random 模块一次绘制 1,000 次抛硬币(效果跟随机游走一样,这里只是换一种说法),将抛的正面和反面分别设置为 1 和 – 1,并计算累积和。(用numpy实现)
import numpy as np
import matplotlib.pyplot as plt
nsteps = 1000
rng = np.random.default_rng(seed=12345)
draws = rng.integers(0, 2, size=nsteps)
steps = np.where(draws == 0, 1, -1)
walk = steps.cumsum()
plt.plot(walk) #这里绘制了整个数组即1000次的游走情况
plt.show()
输出结果:
这个效率比python输出1000个结果要高很多。
基于以上代码我们可以开始提取统计数据,例如沿步行轨迹的最小值和最大值。
import numpy as np
import matplotlib.pyplot as plt
nsteps = 1000
rng = np.random.default_rng(seed=12345)
draws = rng.integers(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 是最大值)
import numpy as np
import matplotlib.pyplot as plt
nsteps = 1000
rng = np.random.default_rng(seed=12345)
draws = rng.integers(0, 2, size=nsteps)
steps = np.where(draws == 0, 1, -1)
walk = steps.cumsum()
#由此,我们可以开始提取统计数据,例如沿游走轨迹的最小值和最大值:
print((np.abs(walk) >= 10).argmax())
输出155.
请注意,在此处使用 argmax 并不总是有效的,因为它总是对数组进行完全扫描。在这种特殊情况下,一旦观察到 True,我们就知道它是最大值。
改进下代码,一次模拟多个随机游走。
import numpy as np
import matplotlib.pyplot as plt
nwalks = 5000
nsteps = 1000
rng = np.random.default_rng(seed=12345)
draws = rng.integers(0, 2, size=(nwalks, nsteps)) # 0 or 1
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(axis=1)
print(walks)
print(walks.max())
print(walks.min())
hits30 = (np.abs(walks) >= 30).any(axis=1)
print(hits30)
print(hits30.sum()) # Number that hit 30 or -30
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(axis=1)
print(crossing_times)
print(crossing_times.mean())
注意,这种矢量化方法需要创建一个带有 nwalks * nsteps 个元素的数组,这可能会使用大量内存。如果内存更加受限,则需要不同的方法。
numpy库就先学到这,下次开始学习pandas库了