在PyTables中存储和提取numpy日期时间数据

8
我希望能够将numpy的datetime64数据存储在PyTables的Table中,而且不使用Pandas。

目前为止我尝试过的

设置

In [1]: import tables as tb
In [2]: import numpy as np
In [3]: from datetime import datetime

创建数据

In [4]: data = [(1, datetime(2000, 1, 1, 1, 1, 1)), (2, datetime(2001, 2, 2, 2, 2, 2))]
In [5]: rec = np.array(data, dtype=[('a', 'i4'), ('b', 'M8[us]')])
In [6]: rec  # a numpy array with my data
Out[6]: 
array([(1, datetime.datetime(2000, 1, 1, 1, 1, 1)),
       (2, datetime.datetime(2001, 2, 2, 2, 2, 2))], 
      dtype=[('a', '<i4'), ('b', '<M8[us]')])

使用Time64Col描述符打开PyTables数据集

In [7]: f = tb.open_file('foo.h5', 'w')  # New PyTables file
In [8]: d = f.create_table('/', 'bar', description={'a': tb.Int32Col(pos=0), 
                                                    'b': tb.Time64Col(pos=1)})
In [9]: d
Out[9]: 
/bar (Table(0,)) ''
  description := {
  "a": Int32Col(shape=(), dflt=0, pos=0),
  "b": Time64Col(shape=(), dflt=0.0, pos=1)}
  byteorder := 'little'
  chunkshape := (5461,)

将NumPy数据追加到PyTables数据集
In [10]: d.append(rec)
In [11]: d
Out[11]: 
/bar (Table(2,)) ''
  description := {
  "a": Int32Col(shape=(), dflt=0, pos=0),
  "b": Time64Col(shape=(), dflt=0.0, pos=1)}
  byteorder := 'little'
  chunkshape := (5461,)

我的日期时间发生了什么?
In [12]: d[:]
Out[12]: 
array([(1, 0.0), (2, 0.0)], 
      dtype=[('a', '<i4'), ('b', '<f8')])

我了解HDF5不提供对日期时间的本地支持。但我期望PyTables覆盖的额外元数据可以处理这个问题。
我的问题是如何在PyTables中存储包含日期时间的numpy记录数组?如何高效地从PyTables表中提取数据并保留日期时间到NumPy数组中?
通常的答案是使用Pandas,但我不想使用Pandas因为我没有索引,也不希望在数据集中存储索引。而且Pandas不允许您没有/存储索引。 (参见this question)
1个回答

5

首先,当将值放入 Time64Col 中时,它们需要是 float64 类型。您可以使用 astype 方法进行转换,如下所示:

new_rec = rec.astype([('a', 'i4'), ('b', 'f8')])

然后你需要将列b转换为自纪元以来的秒数,这意味着你需要除以1,000,000,因为我们现在使用的是微秒:

new_rec['b'] = new_rec['b'] / 1e6

接着调用d.append(new_rec)

当你把数组读取回内存时,要做反向操作并乘以1,000,000。在将任何东西放入内存之前,必须确保时间单位为微秒,这在numpy>= 1.7.x中通过astype('datetime64[us]')自动处理。

我使用了这个问题的解决方案:How to get unix timestamp from numpy.datetime64

以下是你示例的可行版本:

In [4]: data = [(1, datetime(2000, 1, 1, 1, 1, 1)), (2, datetime(2001, 2, 2, 2, 2, 2))]

In [5]: rec = np.array(data, dtype=[('a', 'i4'), ('b', 'M8[us]')])

In [6]: new_rec = rec.astype([('a', 'i4'), ('b', 'f8')])

In [7]: new_rec
Out[7]:
array([(1, 946688461000000.0), (2, 981079322000000.0)],
      dtype=[('a', '<i4'), ('b', '<f8')])

In [8]: new_rec['b'] /= 1e6

In [9]: new_rec
Out[9]:
array([(1, 946688461.0), (2, 981079322.0)],
      dtype=[('a', '<i4'), ('b', '<f8')])

In [10]: f = tb.open_file('foo.h5', 'w')  # New PyTables file

In [11]: d = f.create_table('/', 'bar', description={'a': tb.Int32Col(pos=0),
   ....:                                             'b': tb.Time64Col(pos=1)})

In [12]: d.append(new_rec)

In [13]: d[:]
Out[13]:
array([(1, 946688461.0), (2, 981079322.0)],
      dtype=[('a', '<i4'), ('b', '<f8')])

In [14]: r = d[:]

In [15]: r['b'] *= 1e6

In [16]: r.astype([('a', 'i4'), ('b', 'datetime64[us]')])
Out[16]:
array([(1, datetime.datetime(2000, 1, 1, 1, 1, 1)),
       (2, datetime.datetime(2001, 2, 2, 2, 2, 2))],
      dtype=[('a', '<i4'), ('b', '<M8[us]')])

1
现在我记得为什么pandas不使用TimeCol64,因为它是由float64支持的,无法支持纳秒精度。 - Jeff
1
是的,往返处理datetime64所需的工作量要大得多,这相比于为PyTables提供Time64Col元数据所提供的任何好处都微不足道。 - Phillip Cloud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接