xarray | 序列化及输入输出
xarray 支持多种文件格式(从 pickle文件到 netCDF格式文件)的序列化和输入输出。
Pickle
序列化 xarray 数组最简单的方法就是利用 python 内置的 pickle 模块。
>> import pickle
>> import xarray as xr
>> ds = xr.Dataset({'foo': (('x', 'y'), np.random.rand(4, 5))}, coords={'x': [10, 20, 30, 40],
'y': pd.date_range('2000-01-01', periods=5),
'z': ('x', list('abcd'))})
# 设置 protocol = -1 比默认的基于文本的 pickle 格式要快
>> pkl = pickle.dumps(ds, protocol=-1)
>> pickle.loads(pkl)
<xarray.Dataset>
Dimensions: (x: 4, y: 5)
Coordinates:
* y (y) datetime64[ns] 2000-01-01 2000-01-02 2000-01-03 2000-01-04 ...
* x (x) int32 10 20 30 40
z (x) <U1 'a' 'b' 'c' 'd'
Data variables:
foo (x, y) float64 0.7336 0.8575 0.3533 0.2408 0.06464 0.1491 ...
支持 Pcikle 是非常重要的,因为这可以无需安装额外的库就能让你用其他python 模块(比如 multiprocessing) 使用 xarray 对象。但有两点要注意:
- 为了简化序列化操作, xarray 在 dumping 对象之前会将数组中的所有值加载到内存中。因此这种方式不适用于 大数据 集。比如 netCDF 或 OPeNDAP
- 只要 xarray 对象的内部数据结构不变, Pickle 就能工作。因为 xarray 的内部设计是重新定义的,所以无法保证能够适用于所有版本。
字典
使用 to_dict 方法可以将 Dataset (DataArray) 转换为 字典:
>> d = ds.to_dict()
{'attrs': {},
'coords': {'x': {'attrs': {}, 'data': [10, 20, 30, 40], 'dims': ('x',)},
'y': {'attrs': {},
'data': [datetime.datetime(2000, 1, 1, 0, 0),
datetime.datetime(2000, 1, 5, 0, 0)],
'dims': ('y',)},
'z': {'attrs': {}, 'data': ['a', 'b', 'c', 'd'], 'dims': ('x',)}},
'data_vars': {'foo': {'attrs': {},
'data': [[0.7336403118672791,
0.06463679057178773],
[0.14912269066889416,
0.6386273347534372],
[0.2930828836692492,
0.5314383607667001],
[0.7881755926175252,
0.8322477167318335]],
'dims': ('x', 'y')}},
'dims': {'x': 4, 'y': 5}}
也可以使用 from_dict 方法创建 xarray 对象:
>> ds_dict = xr.Dataset.from_dict(d)
字典支持非常灵活的使用 xarray 对象。无需外部的库即可很容易的转换为 pickle,json 或 geojson。所有的值都会转换为列表,因此字典可以很大。
netCDF
推荐使用 netCDF 存储 xarray 数据结构。netCDF是源于地理科学的自描述二进制数据格式。 xarray 基于 netCDF 数据模式,因此磁盘中的 netCDF文件和 Dataset 对象是对应的。
netCDF在大多数平台上都支持,因此科学程序语言几乎都支持解析 netCDF 文件。最近的 netCDF 版本基于更广泛使用的 HDF-5 文件格式。了解更多netCDF文件格式 [注1] 。
为了读取或写入 netCDF 文件,需要安装 scipy 或 netcdf4-python。
使用 to_netcdf 方法可以存储 Dataset 到磁盘中:
>> ds.to_netcdf('save.nc')
默认存储为 netCDF4 格式。通过 format 和 engine 参数控制文件写入。
使用 open_dataset 方法可以从 netCDF 文件加载数据,并创建 Dataset:
>> ds_disk = xr.open_dataset('save.nc')
DataArray 对象也可以使用相同的方式存储和读取 netCDF文件。但是在操作之前都会先将 DataArray 转换为 Dataset,从而保证数据的准确性。
一个数据集可以加载或写入netCDF 文件的特定组中。传入 group 关键词参数给 open_dateset 函数可以从特定组加载数据。也可以通过类路径方式指定组。比如:获取 foo 组中的 bar 组,可以传递 '/foo/bar/' 给 group 参数。当要在一个文件中写入多个组时,传入 mode = 'a' 给 to_netcdf ,从而确保每一次调用都不会删除文件。
除非执行一系列计算操作,否则 netCDF 文件中的值是不会加载到内存中的。更为重要的一点是:当你改变数据集的值时,如果只是改变了内存中 xarray,那么源文件是不会被改变的。
技巧:
xarray 对 服务器 或本地磁盘文件的延迟加载并不总是有利的。当你要执行高强度计算之前,应先执行 load 方法将数据加载到内存中。
虽然 Dataset 有 close 方法可以关闭 netCDF 文件,但是通常还是利用 with 来处理,因为这会自动关闭文件。
with xr.open_dataset('saved_on_disk.nc') as ds:
print(ds.keys())
尽管 xarray 提供了递增文件读取,但是并不支持这种形式的写入操作。对于文件太大而无法适应内存的数据集来说,这是非常有效的策略。xarray 整合了 dask.array 来提供完整的流计算。
读取编码数据
NetCDF 文件遵循一些编码 datetime 数组 (作为具有 'units' 属性的数字) 以及打包和解包数据约定。如果 open_dataset 方法设置了 decode_cf = True (默认值),xarray 会根据CF规则(一般只需要知道此解码过程即可)试图自动解码 netCDF 文件中的数值。如果变量有一个无效的 'units' 或 'calendar' 属性的话,此转换过程会失败。此时,可以手动关闭解码过程。
DataArray.encoding 属性可以查看解码信息:
ds_disk['y'].encoding
注意 :除了索引外,管理变量的所有操作都会移除编码信息。
写入编码数据
你也可以自定义 xarray 如何为 netCDF 文件中的每个数据集变量提供编码信息。encoding 参数接收包含编码信息的键值对字典。这些信息会保存为 netCDF 变量的编码信息,从而使得 xarray 能够更准确的读取编码数据。
注意:
是否使用编码选项是可选的。如果不指定编码信息的话,xarray 会使用默认的编码属性信息;如果指定的话,这会更有利于额外的处理操作,尤其是压缩操作。
当存储文件时,这些属性信息会保存为每一个变量的属性。从而允许xarray 以及其它工具能够正确的读取 netCDF 文件。
缩放系数及类型转换
以下选项对于任何 netCDF 版本均适用:
- dtype:任何有效的 numpy 类型或字符串都可转换为 dtype。控制写入文件的数据类型。
- _FillValue:当保存 xarray 对象到文件时,xarray 变量中的 Nan 会映射为此属性包含的值。这在转换具有缺省值的浮点数为整数时就显得非常重要了。因为 Nan 对于整数来说不是有效值。默认情况下,对于包含浮点值的变量在存储时 _FillValue 为 Nan。
- scale_factor 和 add_offset:使用公式: decode = scale_factor * encoded + add_offset 将编码数据转换为解码数据。
数据块压缩
zlib,complevel,fletcher32,continguous 和 chunksizes 均可用于 netCDF/HDF5 数据块压缩。这只对 netCDF4 文件有效,即 format = 'netCDF4',engine = 'netcdf4' 或 'h5netcdf'。
基于 gzip 的数据块压缩可以有效的节省空间,尤其是稀疏数据。当然这会产生很大的性能开销。HDF5 可以完全将块读入内存,其解码速度是 50-100 MB/s。但是HDF5压缩和解压缩操作目前不能并行处理。
时间单位
'units' 和 ‘calendar’ 属性控制 xarray 如何将 datetime64 和 timedelta64 数组序列化为数值数组。'units' 编码是类似 datetime64 数据的 '
days
since
1900-01-01
' 字符串或 timedelta64 的 'day' 字符串。'calendar' 应该是 netcdf4-python 支持的日历形式:'standard','gregorian',‘proleptic_gregorian’ ‘noleap’, ‘365_day’, ‘360_day’, ‘julian’, ‘all_leap’, ‘366_day’。
默认情况下,xarray 使用 'proleptic_gregorian' 作为日历,两个值之间的最小时间差作为单位。第一个时间值作为标准时间。
OPeNDAP
xarray 对 OPeNDAP 的支持可以让我们通过 HTTP 获取大数据集。
例如,可以 PRISM 项目的 GB 级天气数据产品:
>> remote_data = xr.open_dataset('http://iridl.ldeo.columbia.edu/SOURCES/.OSU/.PRISM/.monthly/dods', decode_times=False)
>> remote_data
<xarray.Dataset>
Dimensions: (T: 1420, X: 1405, Y: 621)
Coordinates:
* X (X) float32 -125.0 -124.958 -124.917 -124.875 -124.833 -124.792 ...
* T (T) float32 -779.5 -778.5 -777.5 -776.5 -775.5 -774.5 -773.5 ...
* Y (Y) float32 49.9167 49.875 49.8333 49.7917 49.75 49.7083 ...
Data variables:
ppt (T, Y, X) float64 ...
tdmean (T, Y, X) float64 ...
tmax (T, Y, X) float64 ...
tmin (T, Y, X) float64 ...
Attributes:
Conventions: IRIDL
expires: 1370044800
注意:
很多数据集可能不会遵循CF规则,当遇到这种情况时,设置 open_dataset 方法的 decode_cf 参数为 False。
我们可以选择任意时间的数据,并对数据进行切片操作。除非查看特定的值,否则不会加载。
>> tmax = remote_data['tmax'][:500, ::3, ::3]
<xarray.DataArray 'tmax' (T: 500, Y: 207, X: 469)>
[48541500 values with dtype=float64]
Coordinates:
* X (X) float32 -125.0 -124.875 -124.75 -124.625 -124.5 -124.375 ...
* T (T) float32 -779.5 -778.5 -777.5 -776.5 -775.5 -774.5 -773.5 ...
* Y (Y) float32 49.9167 49.7917 49.6667 49.5417 49.4167 49.2917 ...
Attributes:
pointwidth: 120
standard_name: air_temperature
units: Celsius_scale
expires: 1370044800
>> tmax[0].plot
Rasterio
如果安装了 rasterio,可以使用 rasterio 打开GeoTiff以及其它栅格数据集。比如:
>> rio = xr.open_rasterio('RGB.byte.tif')
<xarray.DataArray (band: 3, y: 718, x: 791)>
[1703814 values with dtype=uint8]
Coordinates:
* band (band) int64 1 2 3
* y (y) float64 2.827e+06 2.827e+06 2.826e+06 2.826e+06 2.826e+06 ...
* x (x) float64 1.02e+05 1.023e+05 1.026e+05 1.029e+05 1.032e+05 ...
Attributes:
crs: +init=epsg:32618
目前这是个实验性功能,0.9.6以后的版本才支持。
使用 PyNIO 处理
xarray 可以处理 PyNIO 支持的所有格式文件,只需要在使用 open_dateset 方法时指定 engine 参数为 'pynio' 即可。
使用 pandas 处理
目前 pandas 已经支持了很多文件格式的处理。比如:
Format Type |
Data Description |
Reader |
Writer |
---|---|---|---|
text |
CSV |
read_csv |
to_csv |
text |
JSON |
read_json |
to_json |
text |
HTML |
read_html |
to_html |
text |
Local clipboard |
read_clipboard |
to_clipboard |
binary |
MS Excel |
read_excel |
to_excel |
binary |
HDF5 Format |
read_hdf |
to_hdf |
binary |
Feather Format |
read_feather |
to_feather |
binary |
Msgpack |
read_msgpack |
to_msgpack |
binary |
Stata |
read_stata |
to_stata |
binary |
SAS |
read_sas |
|
binary |
Python Pickle Format |
read_pickle |
to_pickle |
SQL |
SQL |
read_sql |
to_sql |
SQL |
Google Big Query |
read_gbq |
to_gbq |
多文件合并
netCDF 文件通常是一个集合,比如,不同模式运行输出的不同文件。利用 concat 方法可以将多个文件合并为单个文件。
如果你安装了 dask 的话,可以使用 open_mfdataset 合并多个文件:
xr.open_mfdataset('../*.nc')
此函数会自动合并并连接多个文件为一个 xarray 数据集。
下面是 netCDF4-python 中 MFDataset 类似版:
from glob import glob
import xarray as xr
def read_netcdfs(files, dim):
# glob expands paths with * to a list of files, like the unix shell
paths = sorted(glob(files))
datasets = [xr.open_dataset(p) for p in paths]
combined = xr.concat(dataset, dim)
return combined
combined = read_netcdfs('/all/my/files/*.nc', dim='time')
上述示例虽然适用于大多数情况,但并不稳定。首先,没有关闭文件,当加载很多文件时就会失败;其次,假设读去文件中的所有数据,会填满内存。
下面是增强版:
def read_netcdfs(files, dim, transform_func=None):
def process_one_path(path):
# 使用上下文管理器,确保文件使用后被关闭
with xr.open_dataset(path) as ds:
# transform_func 可以执行一些选择操作
if transform_func is not None:
ds = transform_func(ds)
# 从转换数据集中加载所有数据
ds.load()
return ds
paths = sorted(glob(files))
datasets = [process_one_path(p) for p in paths]
combined = xr.concat(datasets, dim)