A (third) proposal for implementing some date/time types in NumPy¶
作者: | Francesc Alted i Abad |
---|---|
联系: | faltet @ pytables 。 com |
作者: | Ivan Vilata i Balaguer |
联系: | ivan @ selidor net |
日期: | 2008-07-30 |
Executive summary¶
在许多需要处理数据集的领域中,日期/时间标记是非常方便的。虽然Python有几个模块定义日期/时间类型(如集成的datetime
[1]或mx.DateTime
[2 ]),NumPy缺少它们。
在本文档中,我们建议添加一系列日期/时间类型以填补此空白。对所提出的类型的要求是双重的:1)它们必须能够快速操作,2)它们必须尽可能与现有的随着Python一起的datetime
模块兼容。
Types proposed¶
首先,几乎不可能提出一个单一的日期/时间类型,满足每种使用情况的需要。因此,在考虑了不同的可能性之后,我们停留了两种不同的类型,即datetime64
和timedelta64
(这些名称是初步的, ),其可以具有不同的时间单位以便覆盖不同的需要。
重要
时间单元在这里被设想为补充日期/时间dtype,而不改变基本类型的元数据。它提供关于存储数字的含义的信息,而不是它们的结构。
现在对所提出的类型进行详细描述。
datetime64
¶
它表示绝对时间(即不相对)。它在内部实现为int64
类型。内部时期是POSIX时期(参见[3])。像POSIX一样,日期的表示不考虑闰秒。
In time unit conversions and time representations (but not in other time computations), the value -2**63 (0x8000000000000000) is interpreted as an invalid or unknown date, Not a Time or NaT. 有关详细信息,请参阅时间单位转换部分。
Time units¶
它接受不同的时间单位,每个时间单位意味着不同的时间跨度。下表描述了相应时间跨度支持的时间单位。
时间单位 | 时间跨度(年) | |
---|---|---|
码 | 含义 | |
Y | 年 | [公元前9.2e18,公元9.2e18] |
M | 月 | [7.6e17 BC,7.6e17 AD] |
W | 周 | [1.7e17 BC,1.7e17 AD] |
B | 工作日 | [3.5e16 BC,3.5e16 AD] |
D | 天 | [2.5e16 BC,2.5e16 AD] |
H | 小时 | [1.0e15 BC,1.0e15 AD] |
m | 分钟 | [1.7e13 BC,1.7e13 AD] |
s | 第二 | [公元前2.9e9年,公元2.9e9] |
女士 | 毫秒 | [公元前2.9e6,公元2.9e6] |
我们 | 微秒 | [290301 BC,294241 AD] |
C# | 壁虱(100ns) | [公元2757年,公元31197年] |
ns | 纳秒 | [1678 AD,2262 AD] |
因此,绝对日期的值是自内部纪元以来传递的所选时间单位的整数个单位。当工作在工作日,星期六和星期日被忽略从计数(即工作日的第3天不是星期六1970-01-03,但星期一1970-01-05)。
Building a datetime64
dtype¶
在dtype构造函数中指定时间单位的建议方法是:
使用长字符串符号:
dtype('datetime64[us]')
使用短字符串符号:
dtype('M8[us]')
如果未指定时间单位,则默认值为微秒。因此,'M8'等价于'M8 [us]'
Setting and getting values¶
具有此dtype的对象可以通过一系列方法设置:
t = numpy.ones(3, dtype='M8[s]')
t[0] = 1199164176 # assign to July 30th, 2008 at 17:31:00
t[1] = datetime.datetime(2008, 7, 30, 17, 31, 01) # with datetime module
t[2] = '2008-07-30T17:31:02' # with ISO 8601
也可以得到不同的方式:
str(t[0]) --> 2008-07-30T17:31:00
repr(t[1]) --> datetime64(1199164177, 's')
str(t[0].item()) --> 2008-07-30 17:31:00 # datetime module object
repr(t[0].item()) --> datetime.datetime(2008, 7, 30, 17, 31) # idem
str(t) --> [2008-07-30T17:31:00 2008-07-30T17:31:01 2008-07-30T17:31:02]
repr(t) --> array([1199164176, 1199164177, 1199164178],
dtype='datetime64[s]')
Comparisons¶
也将支持比较:
numpy.array(['1980'], 'M8[Y]') == numpy.array(['1979'], 'M8[Y]')
--> [False]
或通过应用广播:
numpy.array(['1979', '1980'], 'M8[Y]') == numpy.datetime64('1980', 'Y')
--> [False, True]
下一个也应该工作:
numpy.array(['1979', '1980'], 'M8[Y]') == '1980-01-01'
--> [False, True]
因为右手表达式可以广播到dtype'M8 [Y]'的2个元素的数组中。
Compatibility issues¶
这将与Python的datetime
模块的datetime
类(仅当使用微秒的时间单位)完全兼容。对于其他时间单位,转换过程将失去精度或将根据需要溢出。从/到datetime
对象的转换不考虑闰秒。
timedelta64
¶
它表示相对的时间(即不是绝对的)。它在内部实现为int64
类型。
In time unit conversions and time representations (but not in other time computations), the value -2**63 (0x8000000000000000) is interpreted as an invalid or unknown time, Not a Time or NaT. 有关详细信息,请参阅时间单位转换部分。
Time units¶
它接受不同的时间单位,每个时间单位意味着不同的时间跨度。下表描述了相应时间跨度支持的时间单位。
时间单位 | 时间跨度 | |
---|---|---|
码 | 含义 | |
Y | 年 | + - 9.2e18年 |
M | 月 | + - 7.6e17年 |
W | 周 | + - 1.7e17年 |
B | 工作日 | + - 3.5e16年 |
D | 天 | + - 2.5e16年 |
H | 小时 | + - 1.0e15年 |
m | 分钟 | + - 1.7e13年 |
s | 第二 | + - 2.9e12年 |
女士 | 毫秒 | + - 2.9e9年 |
我们 | 微秒 | + - 2.9e6年 |
C# | 壁虱(100ns) | + - 2.9e4年 |
ns | 纳秒 | + - 292年 |
ps | 皮秒 | + - 106天 |
fs | 飞秒 | + - 2.6小时 |
如 | 阿特秒 | + - 9.2秒 |
The value of a time delta is thus an integer number of units of the chosen time unit.
Building a timedelta64
dtype¶
在dtype构造函数中指定时间单位的建议方法是:
使用长字符串符号:
dtype('timedelta64[us]')
使用短字符串符号:
dtype('m8[us]')
如果没有指定默认值,默认值为微秒:'m8'等价于'm8 [us]'
Setting and getting values¶
具有此dtype的对象可以通过一系列方法设置:
t = numpy.ones(3, dtype='m8[ms]')
t[0] = 12 # assign to 12 ms
t[1] = datetime.timedelta(0, 0, 13000) # 13 ms
t[2] = '0:00:00.014' # 14 ms
也可以得到不同的方式:
str(t[0]) --> 0:00:00.012
repr(t[1]) --> timedelta64(13, 'ms')
str(t[0].item()) --> 0:00:00.012000 # datetime module object
repr(t[0].item()) --> datetime.timedelta(0, 0, 12000) # idem
str(t) --> [0:00:00.012 0:00:00.014 0:00:00.014]
repr(t) --> array([12, 13, 14], dtype="timedelta64[ms]")
Comparisons¶
也将支持比较:
numpy.array([12, 13, 14], 'm8[ms]') == numpy.array([12, 13, 13], 'm8[ms]')
--> [True, True, False]
或通过应用广播:
numpy.array([12, 13, 14], 'm8[ms]') == numpy.timedelta64(13, 'ms')
--> [False, True, False]
下一个也应该工作:
numpy.array([12, 13, 14], 'm8[ms]') == '0:00:00.012'
--> [True, False, False]
因为右手表达式可以广播到dtype'm8 [ms]'的3个元素的数组中。
Compatibility issues¶
这将与Python的datetime
模块的timedelta
类(仅当使用微秒的时间单位)完全兼容。对于其他单位,转换过程将失去精度或将根据需要溢出。
Examples of use¶
这里是使用datetime64
的示例:
In [5]: numpy.datetime64(42, 'us')
Out[5]: datetime64(42, 'us')
In [6]: print numpy.datetime64(42, 'us')
1970-01-01T00:00:00.000042 # representation in ISO 8601 format
In [7]: print numpy.datetime64(367.7, 'D') # decimal part is lost
1971-01-02 # still ISO 8601 format
In [8]: numpy.datetime('2008-07-18T12:23:18', 'm') # from ISO 8601
Out[8]: datetime64(20273063, 'm')
In [9]: print numpy.datetime('2008-07-18T12:23:18', 'm')
Out[9]: 2008-07-18T12:23
In [10]: t = numpy.zeros(5, dtype="datetime64[ms]")
In [11]: t[0] = datetime.datetime.now() # setter in action
In [12]: print t
[2008-07-16T13:39:25.315 1970-01-01T00:00:00.000
1970-01-01T00:00:00.000 1970-01-01T00:00:00.000
1970-01-01T00:00:00.000]
In [13]: repr(t)
Out[13]: array([267859210457, 0, 0, 0, 0], dtype="datetime64[ms]")
In [14]: t[0].item() # getter in action
Out[14]: datetime.datetime(2008, 7, 16, 13, 39, 25, 315000)
In [15]: print t.dtype
dtype('datetime64[ms]')
在这里,它用于timedelta64
的示例:
In [5]: numpy.timedelta64(10, 'us')
Out[5]: timedelta64(10, 'us')
In [6]: print numpy.timedelta64(10, 'us')
0:00:00.000010
In [7]: print numpy.timedelta64(3600.2, 'm') # decimal part is lost
2 days, 12:00
In [8]: t1 = numpy.zeros(5, dtype="datetime64[ms]")
In [9]: t2 = numpy.ones(5, dtype="datetime64[ms]")
In [10]: t = t2 - t1
In [11]: t[0] = datetime.timedelta(0, 24) # setter in action
In [12]: print t
[0:00:24.000 0:00:01.000 0:00:01.000 0:00:01.000 0:00:01.000]
In [13]: print repr(t)
Out[13]: array([24000, 1, 1, 1, 1], dtype="timedelta64[ms]")
In [14]: t[0].item() # getter in action
Out[14]: datetime.timedelta(0, 24)
In [15]: print t.dtype
dtype('timedelta64[s]')
Operating with date/time arrays¶
datetime64
vs datetime64
¶
绝对日期之间允许的唯一算术运算是减法:
In [10]: numpy.ones(3, "M8[s]") - numpy.zeros(3, "M8[s]")
Out[10]: array([1, 1, 1], dtype=timedelta64[s])
但不是其他操作:
In [11]: numpy.ones(3, "M8[s]") + numpy.zeros(3, "M8[s]")
TypeError: unsupported operand type(s) for +: 'numpy.ndarray' and 'numpy.ndarray'
允许绝对日期之间的比较。
Casting rules¶
当操作(基本上,只允许减法)两个具有不同单位时间的绝对时间,结果将是引发异常。这是因为不同时间单位的范围和时间跨度可以非常不同,并且根本不清楚什么时间单位对于用户是优选的。例如,应允许:
>>> numpy.ones(3, dtype="M8[Y]") - numpy.zeros(3, dtype="M8[Y]")
array([1, 1, 1], dtype="timedelta64[Y]")
但下一个不应该:
>>> numpy.ones(3, dtype="M8[Y]") - numpy.zeros(3, dtype="M8[ns]")
raise numpy.IncompatibleUnitError # what unit to choose?
datetime64
vs timedelta64
¶
可以从绝对日期中添加和减去相对时间:
In [10]: numpy.zeros(5, "M8[Y]") + numpy.ones(5, "m8[Y]")
Out[10]: array([1971, 1971, 1971, 1971, 1971], dtype=datetime64[Y])
In [11]: numpy.ones(5, "M8[Y]") - 2 * numpy.ones(5, "m8[Y]")
Out[11]: array([1969, 1969, 1969, 1969, 1969], dtype=datetime64[Y])
但不是其他操作:
In [12]: numpy.ones(5, "M8[Y]") * numpy.ones(5, "m8[Y]")
TypeError: unsupported operand type(s) for *: 'numpy.ndarray' and 'numpy.ndarray'
Casting rules¶
在这种情况下,绝对时间应该具有确定结果的时间单位的优先级。这将代表人们在大多数时候想要做的事情。例如,这将允许:
>>> series = numpy.array(['1970-01-01', '1970-02-01', '1970-09-01'],
dtype='datetime64[D]')
>>> series2 = series + numpy.timedelta(1, 'Y') # Add 2 relative years
>>> series2
array(['1972-01-01', '1972-02-01', '1972-09-01'],
dtype='datetime64[D]') # the 'D'ay time unit has been chosen
timedelta64
vs timedelta64
¶
最后,只要该结果可以转换回timedelta64
,就可以以相对时间来操作,就像它们是常规的int64 dtypes :
In [10]: numpy.ones(3, 'm8[us]')
Out[10]: array([1, 1, 1], dtype="timedelta64[us]")
In [11]: (numpy.ones(3, 'm8[M]') + 2) ** 3
Out[11]: array([27, 27, 27], dtype="timedelta64[M]")
但:
In [12]: numpy.ones(5, 'm8') + 1j
TypeError: the result cannot be converted into a ``timedelta64``
Casting rules¶
当使用不同的时间单位组合两个timedelta64
类型时,结果将是两者中较短的一个(“保持精度”规则)。例如:
In [10]: numpy.ones(3, 'm8[s]') + numpy.ones(3, 'm8[m]')
Out[10]: array([61, 61, 61], dtype="timedelta64[s]")
然而,由于不可能知道相对年或相对月的确切持续时间,当这些时间单位出现在一个操作数中时,将不允许该操作:
In [11]: numpy.ones(3, 'm8[Y]') + numpy.ones(3, 'm8[D]')
raise numpy.IncompatibleUnitError # how to convert relative years to days?
为了能够执行上述操作,提出了称为change_timeunit
的新的NumPy函数。其签名将是:
change_timeunit(time_object, new_unit, reference)
其中'time_object'是其单位将被改变的时间对象,'new_unit'是期望的新时间单位,并且'reference'是绝对日期(NumPy datetime64标量),其将用于允许转换相对时间使用具有不确定数量的较小时间单位(相对年份或月份不能用天数表示)的时间单位的情况。
由此,上述操作可以如下进行:
In [10]: t_years = numpy.ones(3, 'm8[Y]')
In [11]: t_days = numpy.change_timeunit(t_years, 'D', '2001-01-01')
In [12]: t_days + numpy.ones(3, 'm8[D]')
Out[12]: array([366, 366, 366], dtype="timedelta64[D]")
dtype vs time units conversions¶
为了更改现有数组的日期/时间dtype,我们建议使用.astype()
方法。这将主要用于改变时间单位。
例如,对于绝对日期:
In[10]: t1 = numpy.zeros(5, dtype="datetime64[s]")
In[11]: print t1
[1970-01-01T00:00:00 1970-01-01T00:00:00 1970-01-01T00:00:00
1970-01-01T00:00:00 1970-01-01T00:00:00]
In[12]: print t1.astype('datetime64[D]')
[1970-01-01 1970-01-01 1970-01-01 1970-01-01 1970-01-01]
相对时间:
In[10]: t1 = numpy.ones(5, dtype="timedelta64[s]")
In[11]: print t1
[1 1 1 1 1]
In[12]: print t1.astype('timedelta64[ms]')
[1000 1000 1000 1000 1000]
将不支持从/到相对于/从绝对dtypes直接更改:
In[13]: numpy.zeros(5, dtype="datetime64[s]").astype('timedelta64')
TypeError: data type cannot be converted to the desired type
工作日有一个特点,他们不包括一个连续的时间线(他们在周末有缺口)。因此,当从任何普通时间转换到工作日时,可能发生原始时间是不可表示的。在这种情况下,转换的结果是不是时间(NaT):
In[10]: t1 = numpy.arange(5, dtype="datetime64[D]")
In[11]: print t1
[1970-01-01 1970-01-02 1970-01-03 1970-01-04 1970-01-05]
In[12]: t2 = t1.astype("datetime64[B]")
In[13]: print t2 # 1970 begins in a Thursday
[1970-01-01 1970-01-02 NaT NaT 1970-01-05]
当转换回普通日子时,NaT值保持不变(这发生在所有时间单位转换中):
In[14]: t3 = t2.astype("datetime64[D]")
In[13]: print t3
[1970-01-01 1970-01-02 NaT NaT 1970-01-05]
Final considerations¶
Why the origin
metadata disappeared¶
在讨论NumPy列表中的日期/时间dtype时,最初发现具有补充绝对datetime64
的定义的origin
元数据的想法是有用的。
然而,在更多地考虑这一点之后,我们发现绝对datetime64
与相对timedelta64
的组合确实提供了相同的功能,而不需要额外的origin
元数据。这就是为什么我们从这个提案中删除它。
Operations with mixed time units¶
每当接受具有相同单元的相同类型的两个时间值之间的操作时,具有不同单位的时间值的相同操作应是可能的(例如,以秒为单位增加时间增量,以微秒为单位),从而产生足够的时间单位。这种操作的确切语义在“使用日期/时间数组”部分的“铸造规则”子部分中定义。
由于营业日的特殊性,最有可能不允许将营业日与其他时间单位混合的操作。
Why there is not a quarter
time unit?¶
这个建议试图集中在最常用的一组时间单位来操作,quarter
可以被认为是一个导出的单位。此外,使用quarter
通常要求它可以在一年中的任何一个月开始,并且由于我们不包括对origin
元数据的支持,因此这不是这里是一个可行的地方。Finally, if we were to add the quarter
then people should expect to find a biweekly
, semester
or biyearly
just to put some examples of other derived units, and we find this a bit too overwhelming for this proposal purposes.
[1] | http://docs.python.org/lib/module-datetime.html |
[2] | http://www.egenix.com/products/python/mxBase/mxDateTime |
[3] | http://en.wikipedia.org/wiki/Unix_time |