前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >netcdf4-python 模块详解

netcdf4-python 模块详解

作者头像
bugsuse
发布2020-04-20 13:56:48
13.3K1
发布2020-04-20 13:56:48
举报
文章被收录于专栏:气象杂货铺气象杂货铺

python中提供了多种方式来处理netcdf文件,这里主要讲一下常用的 netcdf4-python 模块。

netcdf4-python是 netCDF4 C库的python模块。新版本(V4)的 netcdf 中有很多以前版本中没有的优点,而且新版本是在 HDF5 上建立的。此模块可以读写 netCDF4 及 netCDF3 格式的文件,同时也可创建 HDF5 客户端只读的文件。

netCDF4 格式的许多特征都实现了,比如:多个无限维度(groups)及zlib数据压缩。除此之外,所有新的数据类型(64-bit 和 无符号整型)也已实现。同时也支持复数(struct),变长枚举等数据类型,但不支持 opaque 数据类型,同时也不支持复数,变长型和枚举型数据的组合,比如复数数据中包含枚举型数据,或是变长类型数据中包含复数数据。

创建,打开和关闭 netCDF 文件

通过调用 Dataset 构造器可以创建 netCDF 文件,同时也可以用来打开已存在的文件。当然也可以执行写入操作,可以写入包括维度,组,变量和属性的数据类型。

netCDF文件包含五种类型的数据,即 NETCDF3_CLASSIC, NETCDF3_64BIT_OFFSET, NETCDF3_64BIT_DATA, NETCDF4_CLASSIC,NETCDF4.

NETCDF3_CLASSIC 是原始的netcdf二进制文件,此格式文件大小不能超过 2 G;V3.6版本库引入了 NETCDF3_64BIT_OFFSET 格式,从而使其大小可以超过 2 G;NETCDF3_64BIT_DATA 是V4.4.0版本库引入的,其扩展了NETCDF3_64BIT_OFFSET 格式,从而可以支持无符号64位整型数据及64位维度大小;NETCDF3_64BITNETCDF3_64BIT_OFFSET 格式的别名;NETCDF4_CLASSIC 使用了V4磁盘格式(HDF5),但是忽略了V3 API 中没有的特征。只有当重新链接 netcdf 库时,才可以通过 netCDF3 客户端读取,同时也可以通过HDF5客户端读取。

netCDF4 模块可以读取和写入上述格式中的文件。当创建文件时,可以通过 Dataset 构造器的 format 关键词参数指定格式。默认的格式是 NETCDF4。通过查看 data_model 属性可以确定文件的格式。通过 Dataset 实例的 close 方法可以关闭已经打开的 netcdf 文件。

代码语言:javascript
复制
>>> from netCDF4 import Dataset
>>> rootgrp = Dataset("test.nc", "w", format="NETCDF4")
>>> print rootgrp.data_model
NETCDF4
>>> rootgrp.close()

如果使用 url 代替传给 Dataset 构造器的文件名,那么就可以获取远程数据集了。前提是支持获取远程数据功能。

netcdf 文件中的 Groups

版本4的 netcdf 支持按层级来划分数据,这类似文件系统中的目录。Groups 可以包含变量,维度和属性,同时也可以包含其他 groups。Dataset 回创建一个特殊的组,即 root group,类似unix文件系统中的根目录。通过 Dataset 或 Group 实例的 createGroup 方法可以创建 Group 实例。createGroup 方法接受一个包含组名的 python 字符串。通过Dataset 实例的 groups 目录属性可以获取包含在 root group 中的新 Group 实例的信息。注:只有 NETCDF4 格式文件支持 Groups,使用其他格式创建Group时会报错。

代码语言:javascript
复制
>>> rootgrp = Dataset("test.nc", "a")
>>> fcstgrp = rootgrp.createGroup("forecasts")
>>> analgrp = rootgrp.createGroup("analyses")
>>> print rootgrp.groups
OrderedDict([("forecasts", 
              <netCDF4._netCDF4.Group object at 0x1b4b7b0>),
             ("analyses", 
              <netCDF4._netCDF4.Group object at 0x1b4b970>)])

组是可以进行嵌套的,也就是说一个组可以包含其他组,这就像unix文件系统的目录下可以包含其他目录一样。每一个Group都包含一个 groups 属性字典,其中包含了该组中包含的所有组实例。同样,每一个组实例都包含了path属性,指明了group所处的 ”路径“(类似unix文件系统中路径)。为了简化创建嵌套组的方式,可以传递类似unix文件系统中路径的方式给 createGroup 方法:

代码语言:javascript
复制
>>> fcstgrp1 = rootgrp.createGroup("/forecasts/model1")
>>> fcstgrp2 = rootgrp.createGroup("/forecasts/model2")

如果路径中不存在其他中间元素的话,创建组时就像在unix系统中使用 mkdir -p 命令创建路径一样。如果你试着创建已存在的组的话,不会导致错误,只会返回已存在的组。

下面是一个浏览 Dataset 中所有组的例子。函数 walktree 是一个生成器函数,用来遍历目录树。注意所打印出的组信息。

代码语言:javascript
复制
>>> def walktree(top):
>>>     values = top.groups.values()
>>>     yield values
>>>     for value in top.groups.values():
>>>         for children in walktree(value):
>>>             yield children
>>> print rootgrp
>>> for children in walktree(rootgrp):
>>>      for child in children:
>>>          print child
<type "netCDF4._netCDF4.Dataset">
root group (NETCDF4 file format):
    dimensions:
    variables:
    groups: forecasts, analyses

netcdf 文件中的维度

netcdf根据维度信息创建所有变量的大小,所以在创建变量之前必须要创建维度信息。对于标量变量来说,不需要维度信息。Dateset 或 Group 实例的 createDimension 方法可以用以创建一个维度,传递给此方法的 python 字符串和整数用来表示维度的名称和大小。如果要创建无限维度(即可以随时添加数据),可以将大小设置为 None0

下例中, time 和 level 都是无限维变量。可以包含多个无限维变量是netcdf 的一个新特征,之前的netcdf仅支持包含一个无限维变量,而且必须要包含在最左边,即第一个维度。

代码语言:javascript
复制
>>> level = rootgrp.createDimension("level", None)
>>> time = rootgrp.createDimension("time", None)
>>> lat = rootgrp.createDimension("lat", 73)
>>> lon = rootgrp.createDimension("lon", 144)

所有的 Dimension 实例都被存储在一个 python 字典中。

代码语言:javascript
复制
>>> print rootgrp.dimensions
OrderedDict([("level", <netCDF4._netCDF4.Dimension object at 0x1b48030>),
             ("time", <netCDF4._netCDF4.Dimension object at 0x1b481c0>),
             ("lat", <netCDF4._netCDF4.Dimension object at 0x1b480f8>),
             ("lon", <netCDF4._netCDF4.Dimension object at 0x1b48a08>)])

对 Dimension 实例执行 len 函数可以获取此维度的大小,Dimension 实例的 isunlimited 方法可以确定维度是不是无限维。

代码语言:javascript
复制
>>> print len(lon)
144
>>> print lon.isunlimited()
False
>>> print time.isunlimited()
True

直接打印 Dimension 对象获取更多有用信息,比如维度的名称,大小以及是否是无限维等。

代码语言:javascript
复制
>>> for dimobj in rootgrp.dimensions.values():
>>>    print dimobj
<type "netCDF4._netCDF4.Dimension"> (unlimited): name = "level", size = 0
<type "netCDF4._netCDF4.Dimension"> (unlimited): name = "time", size = 0
<type "netCDF4._netCDF4.Dimension">: name = "lat", size = 73
<type "netCDF4._netCDF4.Dimension">: name = "lon", size = 144
<type "netCDF4._netCDF4.Dimension"> (unlimited): name = "time", size = 0

通过 Dataset 或 Group 实例的 netCDF4.Datatset.renameDimension 方法可以改变维度的名称。

netcdf 中的变量

netcdf 中的变量就像 numpy 模块中的 python 多维数组。然而,不像 numpy 数组,可以在一个或多个无限维添加netcdf 变量。

使用 Dataset 或 Group 实例的 createVariable 方法可以创建一个 netcdf 变量。createVariable 方法需要至少传递两个参数(变量名,变量数据类型)。通过包含变量名的元组确定变量的维度。如果要创建标量变量,只需要忽略维度关键词即可。

变量的数据类型和 numpy 数据的类型是一致的。你可以指定数据类型为 numpy dtype 对象,或是可以转换为numpy dtype 对象的任何东西。

有效的数据类型包括:

f4 : 32位浮点数

f8 : 64位浮点数

i/u4 : 32位有/无符号整型

i/u2 : 16位有/无符号整型

i/u8 : 64位有/无符号整型

i/u1 : 8位有/无符号整型

旧的单字符代码 (f, d, h, s, b, B, c, i, l) 分别表示 (f4, f8, i2, i2, i1, i1, S1, i4, i4),这种设置方式仍然有效。如果文件格式是 NETCDF4, 无符号整型和64位整型可以使用。

维度本身也可以被定义为变量,称为 坐标变量

createVariable 方法会返回一个 Variable 类实例,可以通过其方法获取和设置变量数据及属性。

代码语言:javascript
复制
>>> times = rootgrp.createVariable("time","f8",("time",))
>>> levels = rootgrp.createVariable("level","i4",("level",))
>>> latitudes = rootgrp.createVariable("lat","f4",("lat",))
>>> longitudes = rootgrp.createVariable("lon","f4",("lon",))
>>> # two dimensions unlimited
>>> temp = rootgrp.createVariable("temp","f4",("time","level","lat","lon",))

在交互时可以直接打印变量的所有信息:

代码语言:javascript
复制
>>> print temp
<type "netCDF4._netCDF4.Variable">
float32 temp(time, level, lat, lon)
    least_significant_digit: 3
    units: K
unlimited dimensions: time, level
current shape = (0, 0, 73, 144)

当然也可以直接在组的层级结构中通过路径的方式创建变量:

代码语言:javascript
复制
>>> ftemp = rootgrp.createVariable("/forecasts/model1/temp","f4",("time","level","lat","lon",))

如果中间某个组不存在的话,会直接创建,而不会引起错误。

你可以通过指定 Group 或 Variable 实例的路径来查询 Group 或 Dataset 实例。

代码语言:javascript
复制
>>> print rootgrp["/forecasts/model1"] # a Group instance
<type "netCDF4._netCDF4.Group">
group /forecasts/model1:
    dimensions(sizes):
    variables(dimensions): float32 temp(time,level,lat,lon)
    groups:
>>> print rootgrp["/forecasts/model1/temp"] # a Variable instance
<type "netCDF4._netCDF4.Variable">
float32 temp(time, level, lat, lon)
path = /forecasts/model1
unlimited dimensions: time, level
current shape = (0, 0, 73, 144)
filling on, default _FillValue of 9.96920996839e+36 used

Dataset 或 Group 都会存储在 python 字典中,存储方式和 维度 相同。

代码语言:javascript
复制
>>> print rootgrp.variables
OrderedDict([("time", <netCDF4.Variable object at 0x1b4ba70>),
             ("level", <netCDF4.Variable object at 0x1b4bab0>),
             ("lat", <netCDF4.Variable object at 0x1b4baf0>),
             ("lon", <netCDF4.Variable object at 0x1b4bb30>),
             ("temp", <netCDF4.Variable object at 0x1b4bb70>)])

变量名可以通过 Dataset 实例的 renameVariable 方法更改。

netcdf 文件中的属性

netcdf 文件中包含了两种类型的属性:全局属性变量属性。前者提供的是组或整个数据集的信息,后者提供的是组中变量的信息。

指定值给Dataset 或 Group 实例变量可以设置 全局属性,而位 Variable 实例变量赋值可以设置变量属性。属性可以是字符串,数字或序列。

代码语言:javascript
复制
>>> import time
>>> rootgrp.description = "bogus example script"
>>> rootgrp.history = "Created " + time.ctime(time.time())
>>> rootgrp.source = "netCDF4 python module tutorial"
>>> latitudes.units = "degrees north"
>>> longitudes.units = "degrees east"
>>> levels.units = "hPa"
>>> temp.units = "K"
>>> times.units = "hours since 0001-01-01 00:00:00.0"
>>> times.calendar = "gregorian"

Dateset,Group,Variable的 ncattrs 方法可以获取所有 netcdf 属性的名称。使用python 内置的 dir 函数可以返回一些列私有方法和属性(用户不能或不应该更改)。

代码语言:javascript
复制
>>> for name in rootgrp.ncattrs():
>>>     print "Global attr", name, "=", getattr(rootgrp,name)
Global attr description = bogus example script
Global attr history = Created Mon Nov  7 10.30:56 2005
Global attr source = netCDF4 python module tutorial

Dataset,Group,Variable 实例的 __dict__ 属性将所有的 netcdf 属性名/值对存储在python字典中。

代码语言:javascript
复制
>>> print rootgrp.__dict__
OrderedDict([(u"description", u"bogus example script"),
             (u"history", u"Created Thu Mar  3 19:30:33 2011"),
             (u"source", u"netCDF4 python module tutorial")])

使用 del 命令可以删除 Dataset,Group,Variable 的属性。

写或读取netcdf变量数据

现在创建了Variable 实例,那么如何写入数据呢?你可以将其视为一个数组,然后传递数据给一个切片即可。

代码语言:javascript
复制
>>> import numpy
>>> lats =  numpy.arange(-90,91,2.5)
>>> lons =  numpy.arange(-180,180,2.5)
>>> latitudes[:] = lats
>>> longitudes[:] = lons

与 numpy 数组对象不同的是,如果你在当前定义的索引范围外赋值,那么无限维变量将会沿着这些维度自动扩展

代码语言:javascript
复制
>>> # append along two unlimited dimensions by assigning to slice.
>>> nlats = len(rootgrp.dimensions["lat"])
>>> nlons = len(rootgrp.dimensions["lon"])
>>> print "temp shape before adding data = ",temp.shape
temp shape before adding data =  (0, 0, 73, 144)
>>>
>>> from numpy.random import uniform
>>> temp[0:5,0:10,:,:] = uniform(size=(5,10,nlats,nlons))
>>> print "temp shape after adding data = ",temp.shape
temp shape after adding data =  (6, 10, 73, 144)
>>>
>>> # levels have grown, but no values yet assigned.
>>> print "levels shape after adding pressure data = ",levels.shape
levels shape after adding pressure data =  (10,)

注意:当沿着 temp 变量的 level 维度增加数据时,level 变量的大小将会增加。

代码语言:javascript
复制
>>> # 给 level 维度变量赋值
>>> levels[:] =  [1000.,850.,700.,500.,300.,250.,200.,150.,100.,50.]

然而,numpy 和 netcdf 变量在切片规则上稍有不同。通常是使用 start:stop:step 的形式进行切片。对 netcdf 变量而言,布尔数组和整型序列索引的行为与 numpy 数组是不同的。这些索引在每一个维度是单独作用的(类似 fortran 中的向量下标法)。这意味着:

代码语言:javascript
复制
>>> temp[0, 0, [0,1,2,3], [0,1,2,3]]

切片 netcdf 变量时返回形状为 (4, 4) 的数组, 但是对 numpy 数组而言,将返回形状为 (4, )的数组。类似的,用[0, array([True, False, True]), array([False, True, True, True]), :]检索一个形为 (2, 3, 4, 5)的netcdf数组,将返回一个 (2, 3, 5) 数组,但对于 numpy 来说,这将引起错误,因为这相当于 [0, [0,1], [1,2,3], :]。当使用整数序列切片时,索引不需要排序而且其可能包含重复值。对于 numpy 的 'fancy indexing' 可能引起一些迷惑。通过使对维度数组执行逻辑操作来创建切片,可以提取多维 netcdf 变量数据。

比如:

代码语言:javascript
复制
>>> tempdat = temp[::2, [1,3,6], lats>0, lons>0]

将提取时间维的 0, 2, 4层,压力层的 1, 3, 6层,北半球和东半球交叉部分。返回数组的大小为 (3, 3, 36, 71)。

代码语言:javascript
复制
>>> print "shape of fancy temp slice = ",tempdat.shape
shape of fancy temp slice =  (3, 3, 36, 71)

注意:针对标量变量,为了提取标量变量的数据,可以使用 np.asarray(v) 或 v[...],结果是一个 numpy 标量数组。

处理时间坐标

大部分元数据标准(比如CF)指出:时间的测量应该是使用固定的日历并且相对于一个固定的日期来测量,其单位应该类似于 YY:MM:DD hh-mm-ss

如果没有专门的转换工具的话,这种单位通常比较难处理。此模块提供了 num2date,date2num函数来处理。

代码语言:javascript
复制
>>> # fill in times.
>>> from datetime import datetime, timedelta
>>> from netCDF4 import num2date, date2num
>>> dates = [datetime(2001,3,1)+n*timedelta(hours=12) for n in range(temp.shape[0])]
>>> times[:] = date2num(dates,units=times.units,calendar=times.calendar)
>>> print "time values (in units %s): " % times.units+"\n",times[:]
time values (in units hours since January 1, 0001):
[ 17533056.  17533068.  17533080.  17533092.  17533104.]
>>> dates = num2date(times[:],units=times.units,calendar=times.calendar)
>>> print "dates corresponding to time values:\n",dates
dates corresponding to time values:
[2001-03-01 00:00:00 2001-03-01 12:00:00 2001-03-02 00:00:00
 2001-03-02 12:00:00 2001-03-03 00:00:00]

num2date 转换指定了 unitscalendar 的数值为 datetime 对象,而date2num执行相反的操作。当前定义的calendars均是基于 CF元数据转换标准。date2index函数返回和一系列 datetime 实例对应的netcdf时间变量的索引。

从多个netcdf数据集中获取数据

如果你想从多个文件中获取一个变量的数据,可以使用 MFDataset 类进行数据获取。相比使用单个文件名创建一个 Dataset 实例,MFDataset 实例可以通过一系列文件名或含有通配符的字符串从多个文件中获取数据。当然前提是这些文件必须具有相同的无限维数据,然后才能执行切片操作。

目前只有 NETCDF3_64BIT_OFFSET, NETCDF3_64BIT_DATA, NETCDF3_CLASSIC orNETCDF4_CLASSIC 格式文件支持上述操作,NETCDF4格式暂不支持多文件获取。

代码语言:javascript
复制
>>> from netCDF4 import MFDataset
>>> f = MFDataset("mftest*nc")
>>> print f.variables["x"][:]

注意:只能用来读取数据,不能用于写入数据。

有效压缩 netcdf 变量

存储在 netcdf4 对象中的数据可以执行压缩和解压缩操作。通过createVariable 方法的 zlib,complevel,shuffle关键词参数可以确定执行压缩操作的参数。

要执行压缩操作,zlib = True;complevel 表示压缩速度和效率(1表示速度最快,但压缩比最小,9表示速度最慢,但压缩比最高);设置 shuffle = False,将关闭 HDF5 的 shuffle 过滤,在重新排序字节进行压缩之前会对数据块进行逐行扫描。shuffle 过滤器能显著改善压缩比,默认打开此选项。

设置 createVariable 方法的 fletcher32 关键词参数为 True(默认为 False),将利用 Fletcher32 校验算法进行错误检查,而关键词参数 chunksizes 和 endian 可以用来设置存储在 HDF5 文件中的 HDF 块(chunking)参数和二进制字节序(endian-ness)。这些关键词参数仅和 NETCDF4NETCDF4_CLASSIC 文件相关,其他格式文件(NETCDF3_CLASSIC, NETCDF3_64BIT_OFFSET, NETCDF3_64BIT_DATA)会自动忽略这些关键词参数。

如果你的数据有固定精度的话(比如,温度数据,通常其精度为 0.1 度),可以通过 creatVariable 函数的 least_significant_digit 关键词参数进行数据量化,从而有效改善zlib压缩。

比如:如果数据的精度为 0.1,设置 least_significant_digit = 1将会使数据被量化为 numpy.around(scale*data)/scale, scale = 2**bits,此例中 bits = 4,这会导致压缩有损而不是无损,当然这是为了磁盘空间而必须做出的牺牲。

此例中,可以分别执行以下语句看一下文件的大小变化:

代码语言:javascript
复制
>>> temp = rootgrp.createVariable("temp","f4",("time","level","lat","lon",))

>>> temp = dataset.createVariable("temp","f4",("time","level","lat","lon",),zlib=True)

>>> temp = dataset.createVariable("temp","f4",("time","level","lat","lon",),zlib=True,least_significant_digit=3)

如果想要了解更多关于之前提到的三种数据类型可查阅官方文档。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 气象杂货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档