Python数据分析NumPy和pandas(八、数组写入磁盘文件和从磁盘文件读取、线性代数计算和图形化)

时间:2024-10-23 08:07:48

一、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库了