第六讲 数据格式 DONE

Table of Contents

1 课前准备

安装 HDF5 和 CSV 的相关工具。

apt install vitables python3-h5py hdf5-tools csvtool

2 数据格式

到目前为止的输入和输出还比较简单和原始,形式上都是一个一个数字操作。如果这种数据达到几百TB,操作就太困难了。这就需要更高级的数据格式,作为中间结果的数据,最重要的是遵循透明原则。它默认由计算机处理,透明原则要求它易于被理解和检查。我们重点学习满足透明原则的三个范例 CSV、HDF5 和 JSON。 程序在运行时,数据在计算机内存中临时存储。要把数据输入和输出,是把内存和外部存储之间通信,本质上是数据格式转化的过程。浮点数在转化时,会有损失,在考虑数据格式的时候,尤其要注意。在精度可行的前提下,考虑转化是否方便。数据转化是工具的功能,它们承载了数据中的信息,设计目标是让人方便地写入和读出。在实验进行中,我们会很乐意看看,数据文件中都记录了什么,实验仪器的状态是什么样子的。在处理过程中,我们也有判断中间结果是否合理的需求。 因此数据格式方便与否,直接关系到研究中的日常体验。

2.1 CSV

CSV 是 comma separated values,本身就是文本文件,可以直接用编辑器打开。CSV 可以由 Excel 等 spreadsheet 工具打开,与图形界面工具兼容性良好。CSV 符合我们的书写习惯,每一行从左往右写,写完一行写下一行,在文本文件中把数据排成二维的表格。这种方法对整数和字符串尤其有效。现在浮点数要格外注意,用文本描述小数时,难免有精度损失,应当谨慎评估。 二维表格是 CSV 的特点,也是种限制:它无法直接表达高维的数据。但是这个限制不本质,在本书第四章“关系代数”部分,我们会看到一切数据都可以归结为表格。 NumPy 中自带了处理 CSV 文件的功能。这里 “comma separated values” 已经未必是“comma”(逗号)分隔了,而是广义的“分隔符”,可以是空格或制表符(TAB)甚至是句号——只要不引入歧义都可以。 我们生成一个 (10, 10) 形状的二维数组试一试

import numpy as np
s100 = np.arange(100).reshape((10, 10))
print(s100)
[[ 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 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]

把它存成 CSV,

np.savetxt("s100.txt", s100, fmt="%d")

看一下它的内容

cat s100.txt
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 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99

savetxt 默认的格式是 %.18e ,即18位的科学计数法。对整数来讲,使用 %d 可增加可读性。这个数字转字符串语法约定承袭于 C,叫做“printf format string”。 输出的 CSV 文件由空格分割。每行 10 个数字,一共 10 行。直觉上看,这就是个 \(10 \times \) 的表格。一个可能的麻烦是,带空格的字符串出现在 CSV 输出里,怎么办?它的解决方案没有一定之规,有加引号的,有把分隔符换成其它字符的。遗憾的是并没有形成完整的约定,各方案未必兼容,从而解读出错误的数据。

2.1.1 读入

np.loadtxt("s100.txt", dtype=int)
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, 32, 33, 34, 35, 36, 37, 38, 39],
       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
       [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
       [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
       [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
       [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])

dtype=int 保证以整数解读文件,否则数字读入时会被暗中转化成浮点型。

NumPy 还给出了更多种类型,用于平衡精度与空间占用,包括整形的 int16 int32 int64 ,浮点型的 =float16 float32 float64 float128 等。数字代表了所占的比特位数。

np.loadtxt("s100.txt", dtype=np.int16)
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, 32, 33, 34, 35, 36, 37, 38, 39],
       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
       [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
       [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
       [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
       [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]], dtype=int16)

NumPy 数组与列表的区别,在于它一定有数据类型,即使在没有类型时,也要有统一的 object 类型。

有如此多的数据类型,如何知道每个 CSV 文件适合用哪种?如果用错了,轻则会损失精度,重则会产生量级的差别。这样每次读数据之后,都要检查一遍会不会读错,检查分隔符是否用对了,“#”是否代表注释,等等。读入的正确与否没有标准,只有“与人的直觉一致”,或者在团队中有明确的约定。这是 CSV 直接用文本文体存储所带来的劣势。这一劣势伴随的优势是透明,只要能处理文本文件就可以处理 CSV。

2.2 HDF5

HDF5 意思是 Hierarchical Data Format 第 5 代。HDF 最初的设计目标是提供科学数据的“图形格式”标准,方便对数据研究绘图,揭示规律。科学数据的特点是规则、体量大,要求 HDF 数据格式具有高性能,并通过透明压缩减小资源占用。 HDF 由非盈利组织开发维护。从第4代开始在各学科尤其是天体物理领域流行起来。由 NASA 选定,很多海量望远镜数据都通过 HDF 格式存储。1998 年 HDF 到了第 5 代,很多物理实验和超算中心开始采用 HDF5 。从 1.8 版本的 HDF5 开始,netCDF4 (另一个在天文观测中广泛使用的格式) 与 HDF5 正式统一。 相比于 CSV, HDF5 的好处是带有数据类型,这样做的代价是不能按照文本文件读写了,需要专门的查看器来贯彻“透明”原则。通过制定开放的工业标准,让 HDF5 的格式良好定义,允许几乎所有程序语言的第三方程序对它进行读写,可以增强它的“透明”性。这使得从早期开始,HDF 基础之上就有大量数据分析工具涌现。 HDF5 具有数据的原始(raw)表示,即 HDF 中保存的是与内存同样标准的整数、浮点数,不会有类似 CSV 的精度损失。HDF5 的数据类型自我描述,在读入内存时不需要额外的信息源,因为 HDF5 文件中包含了数据类型和长度等辅助信息。 HDF5 的一个潜在缺点是无法处理中文,在它的标准在制定时只考虑了英文字符。为了保证它的兼容性,尽量不使用英文字母以外的字符。

2.2.1 HDF5 的结构

HDF5 文件结构分三种。

数据集 Dataset 与 NumPy 多维数组很像,数据类型多种多样可自定义。组织整理数据集要类, 可以用组 Group 。组的嵌套关系用“/”表达,语法与文件夹一致,例如 /calibration/water/waveform calibration 和 water 是组, waveform 是数据集。元数据 metadata 作为数据集或者组的标签,例如通过 metadata 标记 /calibration/water 组的温度为 25。记录实验条件信息,可以使用实验记录本。但是一次原则的指导下,最好相关的信息写在同一处, 元数据的设计正是为了提供此便利。

2.2.2 HDF5 的 Python 工具

Python 上流行的 HDF5 工具有两种,较底层极简的 h5py 和有高级功能自定义格式的 PyTables。由于 h5py 的 HDF5 原始格式与其它语言的兼容性更强,更符合标准,我们本着透明原则选用 h5py。其它工具能正常读写数据,远比 20% 的性能提升重要。 h5py 缩写的含义是 HDF5 Python。它的本质是 Python 调用 HDF5 C 语言库的接口,因此与使用 HDF5 标准格式无差别。

装载 h5py ,看一下它的 test。

import h5py
with h5py.File("s100.h5", "w") as opt:
    opt["s100"] = s100

在当前位置写入一个 s100.h5 的文件。从文件系统可访问到它。

file s100.h5
h5dump -A s100.h5
s100.h5: Hierarchical Data Format (version 5) data
HDF5 "s100.h5" {
GROUP "/" {
   DATASET "s100" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SIMPLE { ( 10, 10 ) / ( 10, 10 ) }
   }
}
}

file 识别出了它是 “Hierarchical Data Format (version 5) data”, HDF5 的工具 h5dump 给出它的内部信息,一个 H5T_STD_I64LE 数据类型,即 64 位整型的, (10, 10) 数组,与 NumPy 的原始类型一致。 不用 -A 参数时, h5dump 能查看全部的数据

h5dump s100.h5
HDF5 "s100.h5" {
GROUP "/" {
   DATASET "s100" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SIMPLE { ( 10, 10 ) / ( 10, 10 ) }
      DATA {
      (0,0): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
      (1,0): 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
      (2,0): 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
      (3,0): 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
      (4,0): 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
      (5,0): 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
      (6,0): 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
      (7,0): 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
      (8,0): 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
      (9,0): 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
      }
   }
}
}

H5T_STD_I64LE 存储 100 以内的数据太浪费,只要8位就够了。我们把 NumPy 的数组转成 8 位整型后,保存到 HDF5。

with h5py.File("s100-int8.h5", "w") as opt:
    opt["s100"] = s100.astype(np.int8)
h5dump s100-int8.h5
HDF5 "s100-int8.h5" {
GROUP "/" {
   DATASET "s100" {
      DATATYPE  H5T_STD_I8LE
      DATASPACE  SIMPLE { ( 10, 10 ) / ( 10, 10 ) }
      DATA {
      (0,0): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
      (1,0): 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
      (2,0): 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
      (3,0): 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
      (4,0): 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
      (5,0): 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
      (6,0): 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
      (7,0): 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
      (8,0): 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
      (9,0): 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
      }
   }
}
}

确认数据类型变成了 H5T_STD_I8LE ,但是内容不变。文件大小

ls -lh s100*.h5
-rw-r--r-- 1 xubd xubd 2.8K Jul 19 11:51 s100.h5
-rw-r--r-- 1 xubd xubd 2.1K Jul 19 12:07 s100-int8.h5

int8 存储的确实节省了空间,但是注意它能表示的范围只有 -128 至 127。

注意,在 h5py.File 是大写的 File ,在写入数据集时,例子中使用了 opt["s100"] = s100 ,当作字典来使用。写入的风格与 CSV 有所差异,它们相同的地方仅仅是都打开一个文件,但具体如何打开,打开后如何操作,不同的作者有不同的约定。多种形式难以记住,可随时查阅在线帮助。

2.2.3 读取 HDF5

with h5py.File("s100.h5", 'r') as ipt:
    h5_s100 = ipt["s100"][...]
print(h5_s100)
print(h5_s100.dtype)
[[ 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 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
int64

我们也用了 with h5py.File ,默认是读模式。和写时一致,文件读的操作器 handler 也能当成字典使用。在调用 ipt["s100"] 时,后面要加 [...] ,代表把所有数据读进内存。 操作器的类型是 h5py._hl.files.File

type(ipt)
h5py._hl.files.File

它并不是字典,但是模拟了字典的接口。这是工具接口的常见设计思想,模仿一个大家都熟悉的工具的接口。

with h5py.File("s100-int8.h5") as ipt:
    print(type(ipt.keys()))
    print(list(ipt.keys()))
    print(ipt["s100"])
<class 'h5py._hl.base.KeysViewHDF5'>
['s100']
<HDF5 dataset "s100": shape (10, 10), type "|i1">

i表示整数,1表示一个字节,即 int8。把 s100 取出时,HDF5 自我描述可自动把 NumPy 的类型设置好。

with h5py.File("s100-int8.h5") as ipt:
    h5_s100 = ipt["s100"][...]
print(h5_s100)
print(h5_s100.dtype)
[[ 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 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
int8

读取数据时后面的 [...] 或者 [()] ,用来把整个数据载入内存。但有时数据非常大,内存装不下,HDF5有方法把文件分块读入,逐块读入内存处理。此操作,叫做 “out of core computing”,又称 “external memory algorithm”。 类似于 HDF5 文件给出类字典的接口,它的数据集 dataset 提供的是类 NumPy 数组接口。后者是 Python 科学计算领域既有标准。下面展示 HDF5 数据集里,模拟 NumPy 数组的典型特征,包括数据类型和索引等。

with h5py.File("s100-int8.h5") as ipt:
    print(ipt['s100'].dtype)
    print(ipt['s100'][::2, ::3])
int8
[[ 0  3  6  9]
 [20 23 26 29]
 [40 43 46 49]
 [60 63 66 69]
 [80 83 86 89]]

不同的是 HDF5 dataset 支持 out of core computing。

2.2.4 HDF5 的组

HDF5 的组可与文件系统中的文件夹类比。创建组使用 creat_group 函数。

with h5py.File("hzg.h5", "w") as opt:
    opt.create_group("/home")
    opt["home"]["s100"] = s100

在命令行确认。

h5dump -A hzg.h5
HDF5 "hzg.h5" {
GROUP "/" {
   GROUP "home" {
      DATASET "s100" {
         DATATYPE  H5T_STD_I64LE
         DATASPACE  SIMPLE { ( 10, 10 ) / ( 10, 10 ) }
      }
   }
}
}

s100 的数组集被放在了 home 的组之下,注意 HDF5 文件都有一个 / 的默认的组。读取试试。

with h5py.File("hzg.h5", "r") as ipt:
    print(type(ipt["home"]))
    print(type(ipt["home"]["s100"]))
    print(type(ipt["home/s100"]))
<class 'h5py._hl.group.Group'>
<class 'h5py._hl.dataset.Dataset'>
<class 'h5py._hl.dataset.Dataset'>

2.2.5 移动数组集

HDF5 移动操作,可以用复制和删除组合实现。我们把 /home/s100 移动到 /s100

with h5py.File("hzg.h5", "a") as ipt:
    ipt["s100"] = ipt["home/s100"]
    del ipt["home/s100"]

这里打开文件的选项是“a”,意思为 append ,既读又写。

h5dump -A hzg.h5
HDF5 "hzg.h5" {
GROUP "/" {
   GROUP "home" {
   }
   DATASET "s100" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SIMPLE { ( 10, 10 ) / ( 10, 10 ) }
   }
}
}

文件修改后, s100 数据集与 home 组并列,都在同一个层次了。

HDF5 的组与数据集,与文件系统神似,有非常强的表现力,可以表征大多数的数据存储情形。它在大规模的数据处理中非常方便。例如 MATLAB 的 mat 文件,就是建立在 HDF5 标准之上。可见 HDF5 对工业界和学术界的影响深远。HDF5 的兼容性使得 Python 可以与其它语言,如 R、C++、MATLAB 进行数据交换,增加分工合作。

2.3 JSON

当数据没有整齐形态,可能伴随有分支、嵌套等时,使用JSON更方便。JSON 与 Javascript 有 很深的渊源,在网页前端开发中应用广泛。Javascript 是应该最广的程序语言,我们在浏览网页时,都会在浏览器运行 Javascript 程序。

JSON 是 JavaScript Object Notation 的缩写,读作 “Jason”。。JSON 的作者曾经给名叫 Jason 的人道过歉,给他们的生活造成了不便。他自己也没想到 JSON 可以变得如此流行。JSON 由网站的数据结构需求来。在网页中,要更新显示动态内容,数据的载体由 JSON 提供,替代了过去的冗长不易由人类阅读的 XML 格式。JSON 提供了与 XML 等价的逻辑结构,但更易阅读因此被迅速采用,成为了国际标准:透明原则。 JSON 借鉴了 Python 字典和列表的语法,与 Python 交互极其方便。但是 JSON 面向的纯文本数据,与 CSV 类似,对数字的表现力弱。在科学实验里,JSON 可用来表征有复杂层次关系的,不齐整的实验条件数据。这可与 HDF5 的元数据有效互补。 下载一个 JSON 数据样例,取自 LIGO 的公开数据集。

wget 'http://hep.tsinghua.edu.cn/~orv/pd/BBH_events_v3.json'
--2022-07-24 16:12:38--  http://hep.tsinghua.edu.cn/~orv/pd/BBH_events_v3.json
Resolving hep.tsinghua.edu.cn... 101.6.6.219, 2402:f000:1:416:101:6:6:219
Connecting to hep.tsinghua.edu.cn|101.6.6.219|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2202 (2.2K) [application/json]
Saving to: ‘BBH_events_v3.json’


BBH_events_v3.json    0%[                    ]       0  --.-KB/s               
BBH_events_v3.json  100%[===================>]   2.15K  --.-KB/s    in 0s      

2022-07-24 16:12:38 (215 MB/s) - ‘BBH_events_v3.json’ saved [2202/2202]

查看文件的内容。

cat BBH_events_v3.json
{
  "GW150914":{
      "name":"GW150914",
      "fn_H1"       : "H-H1_LOSC_4_V2-1126259446-32.hdf5",
      "fn_L1"       : "L-L1_LOSC_4_V2-1126259446-32.hdf5",
      "fn_template" : "GW150914_4_template.hdf5",
      "fs"          : 4096,
      "tevent"      : 1126259462.44,
      "utcevent"    : "2015-09-14T09:50:45.44",
      "m1"          : 41.743,
      "m2"          : 29.237,
      "a1"          : 0.355,
      "a2"          : -0.769,
      "approx"      : "lalsim.SEOBNRv2",
      "fband"       : [43.0,300.0],
      "f_min"       : 10.0
  },
  "LVT151012":{
      "name":"LVT151012",
      "fn_H1"       : "H-H1_LOSC_4_V2-1128678884-32.hdf5",
      "fn_L1"       : "L-L1_LOSC_4_V2-1128678884-32.hdf5",
      "fn_template" : "LVT151012_4_template.hdf5",
      "fs"          : 4096,
      "tevent"      : 1128678900.44,
      "utcevent"    : "2015-10-12T09:54:43.44",
      "m1"          : 44.111,
      "m2"          : 11.205,
      "a1"          : 0.447,
      "a2"          : -0.434,
      "approx"      : "lalsim.SEOBNRv2",
      "fband"       : [43.0,400.0],
      "f_min"       : 10.0
  },
  "GW151226":{
      "name":"GW151226",
      "fn_H1"       : "H-H1_LOSC_4_V2-1135136334-32.hdf5",
      "fn_L1"       : "L-L1_LOSC_4_V2-1135136334-32.hdf5",
      "fn_template" : "GW151226_4_template.hdf5",
      "fs"          : 4096,
      "tevent"      : 1135136350.65,
      "utcevent"    : "2015-12-26T03:38:53.65",
      "m1"          : 19.6427,
      "m2"          : 6.7054,
      "a1"          : 0.3998,
      "a2"          : -0.0396,
      "approx"      : "lalsim.SEOBNRv2",
      "fband"       : [43.0,800.0],
      "f_min"       : 10.0
  },
  "GW170104":{
      "name":"GW170104",
      "fn_H1"       : "H-H1_LOSC_4_V1-1167559920-32.hdf5",
      "fn_L1"       : "L-L1_LOSC_4_V1-1167559920-32.hdf5",
      "fn_template" : "GW170104_4_template.hdf5",
      "fs"          : 4096,
      "tevent"      : 1167559936.6,
      "utcevent"    : "2017-01-04T10:11:58.60",
      "m1"          : 33.64,
      "m2"          : 24.82,
      "a1"          : -0.236,
      "a2"          : 0.024,
      "approx"      : "lalsim.SEOBNRv2",
      "fband"       : [43.0,800.0],
      "f_min"       : 10.0
  }

简直就是 Python 的字典!

2.3.1 读取 JSON

import json

with open("BBH_events_v3.json", "r") as ipt:
    events = json.load(ipt)
print(type(events)) # 就是一个字典
print(events.keys())
<class 'dict'>
dict_keys(['GW150914', 'LVT151012', 'GW151226', 'GW170104'])

注意要先把文件按照文本形式打开,用 open ,再转成 JSON 的格式 json.load 。 events 完全就是 Python 字典,其中的词恰好对应文件中的 4 个部分。 把第一组值读出。

events["GW150914"]
{'name': 'GW150914',
 'fn_H1': 'H-H1_LOSC_4_V2-1126259446-32.hdf5',
 'fn_L1': 'L-L1_LOSC_4_V2-1126259446-32.hdf5',
 'fn_template': 'GW150914_4_template.hdf5',
 'fs': 4096,
 'tevent': 1126259462.44,
 'utcevent': '2015-09-14T09:50:45.44',
 'm1': 41.743,
 'm2': 29.237,
 'a1': 0.355,
 'a2': -0.769,
 'approx': 'lalsim.SEOBNRv2',
 'fband': [43.0, 300.0],
 'f_min': 10.0}

是一个嵌套的字典。

2.3.2 写 JSON

输出 JSON 时,使用 dump 函数。NumPy 用 loadtxt savetxt ,HDF5 用 File ,JSON 是 loaddump 。不同的命名风格来自不同的开发团队,注意其中的区别。

with open("BBH_events_rewrite.json", 'w') as opt:
    json.dump(events, opt)

新输出的文件对人不友好。

cat BBH_events_rewrite.json
{"GW150914": {"name": "GW150914", "fn_H1": "H-H1_LOSC_4_V2-1126259446-32.hdf5", "fn_L1": "L-L1_LOSC_4_V2-1126259446-32.hdf5", "fn_template": "GW150914_4_template.hdf5", "fs": 4096, "tevent": 1126259462.44, "utcevent": "2015-09-14T09:50:45.44", "m1": 41.743, "m2": 29.237, "a1": 0.355, "a2": -0.769, "approx": "lalsim.SEOBNRv2", "fband": [43.0, 300.0], "f_min": 10.0}, "LVT151012": {"name": "LVT151012", "fn_H1": "H-H1_LOSC_4_V2-1128678884-32.hdf5", "fn_L1": "L-L1_LOSC_4_V2-1128678884-32.hdf5", "fn_template": "LVT151012_4_template.hdf5", "fs": 4096, "tevent": 1128678900.44, "utcevent": "2015-10-12T09:54:43.44", "m1": 44.111, "m2": 11.205, "a1": 0.447, "a2": -0.434, "approx": "lalsim.SEOBNRv2", "fband": [43.0, 400.0], "f_min": 10.0}, "GW151226": {"name": "GW151226", "fn_H1": "H-H1_LOSC_4_V2-1135136334-32.hdf5", "fn_L1": "L-L1_LOSC_4_V2-1135136334-32.hdf5", "fn_template": "GW151226_4_template.hdf5", "fs": 4096, "tevent": 1135136350.65, "utcevent": "2015-12-26T03:38:53.65", "m1": 19.6427, "m2": 6.7054, "a1": 0.3998, "a2": -0.0396, "approx": "lalsim.SEOBNRv2", "fband": [43.0, 800.0], "f_min": 10.0}, "GW170104": {"name": "GW170104", "fn_H1": "H-H1_LOSC_4_V1-1167559920-32.hdf5", "fn_L1": "L-L1_LOSC_4_V1-1167559920-32.hdf5", "fn_template": "GW170104_4_template.hdf5", "fs": 4096, "tevent": 1167559936.6, "utcevent": "2017-01-04T10:11:58.60", "m1": 33.64, "m2": 24.82, "a1": -0.236, "a2": 0.024, "approx": "lalsim.SEOBNRv2", "fband": [43.0, 800.0], "f_min": 10.0}}

使用 jq 命令可以更好地阅读。

jq < BBH_events_rewrite.json
{
  "GW150914": {
    "name": "GW150914",
    "fn_H1": "H-H1_LOSC_4_V2-1126259446-32.hdf5",
    "fn_L1": "L-L1_LOSC_4_V2-1126259446-32.hdf5",
    "fn_template": "GW150914_4_template.hdf5",
    "fs": 4096,
    "tevent": 1126259462.44,
    "utcevent": "2015-09-14T09:50:45.44",
    "m1": 41.743,
    "m2": 29.237,
    "a1": 0.355,
    "a2": -0.769,
    "approx": "lalsim.SEOBNRv2",
    "fband": [
      43.0,
      300.0
    ],
    "f_min": 10.0
  },
  "LVT151012": {
    "name": "LVT151012",
    "fn_H1": "H-H1_LOSC_4_V2-1128678884-32.hdf5",
    "fn_L1": "L-L1_LOSC_4_V2-1128678884-32.hdf5",
    "fn_template": "LVT151012_4_template.hdf5",
    "fs": 4096,
    "tevent": 1128678900.44,
    "utcevent": "2015-10-12T09:54:43.44",
    "m1": 44.111,
    "m2": 11.205,
    "a1": 0.447,
    "a2": -0.434,
    "approx": "lalsim.SEOBNRv2",
    "fband": [
      43.0,
      400.0
    ],
    "f_min": 10.0
  },
  "GW151226": {
    "name": "GW151226",
    "fn_H1": "H-H1_LOSC_4_V2-1135136334-32.hdf5",
    "fn_L1": "L-L1_LOSC_4_V2-1135136334-32.hdf5",
    "fn_template": "GW151226_4_template.hdf5",
    "fs": 4096,
    "tevent": 1135136350.65,
    "utcevent": "2015-12-26T03:38:53.65",
    "m1": 19.6427,
    "m2": 6.7054,
    "a1": 0.3998,
    "a2": -0.0396,
    "approx": "lalsim.SEOBNRv2",
    "fband": [
      43.0,
      800.0
    ],
    "f_min": 10.0
  },
  "GW170104": {
    "name": "GW170104",
    "fn_H1": "H-H1_LOSC_4_V1-1167559920-32.hdf5",
    "fn_L1": "L-L1_LOSC_4_V1-1167559920-32.hdf5",

或者给 dump 函数加入 indent=2 的参数。

3 软件管理器

我们的 GNU 系统环境中,都带有软件管理器,例如 apt 。需要什么工具可以随手安装,只要网络足够快,就能快速安装和使用,非常方便。这个工具叫包管理器 package manager 。

Author: 续本达

Created: 2023-04-22 Sat 00:02

Validate