Python数据分析 | (13)数组文件输入输出及结构化和记录式数组

本篇博客所有示例使用Jupyter NoteBook演示。

Python数据分析系列笔记基于:利用Python进行数据分析(第2版)  

目录

1.用于数组的文件输入输出

2.高级数组输入输出

3.结构化和记录式数组

4.性能建议


1.用于数组的文件输入输出

NumPy能够读写磁盘上的文本数据或二进制数据。本小节只讨论NumPy的内置二进制格式,因为更多用户会使用pandas或其他工具加载文本或表格数据(之后会学习).

np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的:

import numpy as np

arr = np.arange(10)
np.save('some_array',arr)

save的第一个参数是文件路径(绝对/相对路径)末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以使用np.load读取磁盘上的数组:

np.load('some_array.npy')

通过np.savez可以将多个数组保存到一个未压缩文件中,数组以关键字参数的形式传入:

print(arr)
np.savez('array.npz',a=arr,b=arr)
#加载.npz时会得到一个类似字典对象
arch = np.load('array.npz')
print(arch['a'])   #会对各个数组进行延迟加载
print(arch['b'])

如果要将数据压缩,可以使用np.savez_compressed:

print(arr)
np.savez_compressed('array_compressed.npz',a=arr,b=arr)
#加载.npz时会得到一个类似字典对象
arch = np.load('array_compressed.npz')
print(arch['a'])   #会对各个数组进行延迟加载
print(arch['b'])

2.高级数组输入输出

之前的np.save和np.load用于读写磁盘上以二进制格式存储的数组。还有一些工具可用于更为复杂的场景。尤其是内存映像(memory map),它使你能处理在内存中放不下的数据集。

  • 内存映像文件

内存映像文件是一种将磁盘上的非常大的二进制数据文件当作内存中的数组进行处理的方式。NumPy实现了一个类似于ndarray的memmap对象,它允许将大文件分成小段进行读写,而不是一次性将整个数组读入内存。另外,memmap也拥有跟普通数组一样的方法,因此,基本上只要是能用于ndarray的算法就也能用于memmap。

要创建一个内存映像,可以使用np.memmap函数并传入一个文件路径、数据类型、形状以及文件模式:

mmap = np.memmap('mymap',dtype='float64',mode='w+',shape=(10000,10000))
print(mmap)

当前路径下生成了一个内存映像:

对memmap切片会返回磁盘上数据的视图:

section = mmap[:5] #取前5行
print(section)

如果将数据赋值给这些视图(切片):数据会先被缓存到内存中(就像Python的文件对象),调用flush即可将其写入磁盘:

section[:] = np.random.randn(5,10000)
mmap.flush()
print(mmap)

del mmap #删除内存映像

只要某个内存映像超出了作用域,他就会被垃圾回收器回收,之前对其所做的任何修改都会被写入磁盘。当打开一个已经存在的内存映像时,仍然需要指明数据类型和形状,因为磁盘上的那个文件只是一块二进制数据而已,没有任何元数据:

mmap = np.memmap('mymap',dtype='float64',shape=(10000,10000),mode='r')
print(mmap)

内存映像可以使用结构化或嵌套dtype(下一部分会讲).

  • HDF5及其他数组存储方式

PyTables和h5py这两个Python项目可以将NumPy的数组数据存储为高效且可压缩的HDF5格式(HDF:层次化数据格式)。可以安全地将几百G甚至TB的数据存储为HDF5格式。

要学习Python使用HDF5,可以参考pandas线上文档。

3.结构化和记录式数组

到目前为止,我们所讨论的ndarray都是一种同质数据容器,也就是说,数组中的元素都是同一类型,在它所表示的内存块中,各元素占用的字节数相同(具体由dtype决定).表面上看,他似乎不能用于表示异质或表格型数据。结构化数组是一种特殊的ndarray,其中各个元素可以被看作C语言中的结构体(struct,这也是结构化的由来)或SQL表中带有多个命名字段的行:

dtype = [('x',np.float64),('y',np.int32)] #元组列表
sarr = np.array([(1.5,2),(np.pi,-3)],dtype=dtype)
sarr

定义结构化数组dtype的方式很多(可以查看NumPy在线文档).最典型的是使用元组列表,各元组的形式为(field_name,field_data_type).这样数组的元素就成了元组式对象,对象中的各个元素可以像字典那样进行访问:

print(sarr[0])
print(sarr[1])
print(sarr[0]['y'])
print(sarr['x'])

字段名保存在dtype.names属性中。在访问结构化数组的某个字段时,返回的是该数据的视图,不会发生数据复制。

  • 嵌套dtype和多维字段

在定义结构化dtype时,还可以设置一个形状(可以是一个整数,也可以是一个元组):

dtype = [('x',np.int64,3),('y',np.int32)]
arr = np.zeros(4,dtype=dtype)
print(arr)
print(arr[0])   #可以把arr看作一个表格  arr[0]访问第一行
print(arr[0]['x'])  #arr[0]['x']访问第一行的x字段/列 长度为3的一维数组
print(arr['x'])  #访问arr的x字段。 2维数组

这就使你能用单个数组的内存块存放复杂的嵌套结构。还可以嵌套dtype作出更复杂的结构,如:

dtype = [('x',[('a','f8'),('b','f4')]),('y',np.int32)] #定义各字段/列的类型 x字段又分了两个小字段
arr = np.array([((1.,2.5),12),((2.3,3.4),56)],dtype=dtype)
print(arr)
print(arr['x'])  #访问x字段
print(arr['y']) #访问y字段
print(arr['x']['a'])  #访问x字段下的a字段
print(arr[0])  #访问第一行  /第一个记录

pandas的DataFrame并不直接支持该功能,但它的分层索引机制和这个差不多。

  • 为什么使用结构化数组

和pandas的DataFrame相比,NumPy结构化数组是一种相对较低级的工具。它可以将单个内存块解释为带有任意复杂嵌套列的表格型结构。由于数组中的每个元素在内存中都被表示为固定的字节数,所以结构化数组能够提供非常快速高效的磁盘数据读写(包括内存映像)、网络传输等功能。

结构化数组的另一个常见用法是将数据文件写成定长记录字节流,这是C和C++代码中常见的数据序列化手段(业界许多历史系统中都能找得到).只要知道文件格式(记录的大小、元素顺序、字节数以及数据类型等),就可以使用np.fromfile将数据读入内存(这种用法目前超过了Python数据分析系列博客的范围,了解一下就好)。

4.性能建议

使用NumPy的代码性能一般都很不错,因为数组运算一般都比纯Python循环快得多,下面是一些注意事项:

  • 将Python循环和条件逻辑转换为数组运算和布尔数组运算
  • 尽量使用广播
  • 避免复制数据,尽量使用数组视图(如切片)
  • 利用ufunc及其各种方法

如果单用NumPy无论如何都达不到所需的性能指标,就可以考虑一下用C、Fortran或Cython来编写代码。工作中会比较常用到Cython,因为它不用花费太多精力就能得到C语言那样的性能。

 

  • 连续内存的重要性

这个话题稍微了解一下就好了,超出了Python数据分析系列博客的范围。在某些应用场景中,数组的内存布局可以对计算速度造成极大的影响。这是因为性能差别在一定程度上跟CPU的高速缓存(cache)体系有关。运算过程中访问连续内存块(如,对以C顺序存储的数组的行求和)一般是最快的,因为内存子系统会将适当的内存块缓存到超高速的L1或L2CPU Cache中。此外,NumPy的C语言基础代码(某些)对连续存储的情况进行了优化处理,这样就能避免一些跨越式的内存访问。

一个数组的内存布局是连续的,就是说元素是以他们在数组中出现的顺序(即Fortran型(列优先)或C型(行优先))存储在内存中的。默认情况下,NumPy数组是以C型连续的方式创建的。列优先数组(比如C型连续数组的转置)也被称为Fortran型连续。通过ndarray的flags属性即可查看这些信息:

arr_c = np.ones((1000,1000),order='C') #默认C 可以不指定
arr_f = np.ones((1000,1000),order='F')
print(arr_c.flags)
print("----------------")
print(arr_f.flags)
print(arr_f.flags.f_contiguous)

在上例中对两个数组的行进行求和计算(axis=1 沿1轴 各个列的方向/水平),理论上arr_c比arr_f要快,因为arr_c的行在内存中是连续的:

%timeit arr_c.sum(1)
%timeit arr_f.sum(1)

如果想从NumPy中提升性能,这是一个可以下手的地方。如果数组的内存顺序不符合你的要求,使用copy并传入'C'或'F'即可解决该问题:

print(arr_f.copy('C').flags)  #arr_f的C顺序的副本

注意在构造数组视图时,其结果不一定是连续的:

print(arr_c[:50].flags.contiguous)
print(arr_c[:,:50].flags)

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/sdu_hao/article/details/86508215
今日推荐