从numpy.datetime64到pandas.tslib.Timestamp的转换错误?

3 投票
1 回答
4598 浏览
提问于 2025-04-17 14:45

我有一个Python模块,它直接把数据加载到一个字典中,这个字典的值是numpy.ndarray,之后用在pandas.DataFrame里。不过,我发现了一个关于'NA'值的问题。我的文件格式把NA值表示为-9223372036854775808(这是一个很大的负数)。我的非NA值正常加载到了pandas.DataFrame里,值也对。但我觉得我的模块把数据加载成了numpy.datetime64的ndarray,然后再转换成pandas.tslib.Timestamp的列表。这个转换似乎没有保留'const_min'这个整数。试试下面的代码:

>>> pandas.tslib.Timestamp(-9223372036854775808)
NaT
>>> pandas.tslib.Timestamp(numpy.datetime64(-9223372036854775808))
<Timestamp: 1969-12-31 15:58:10.448384>

这是pandas的一个bug吗?我想我可以让我的模块在这种情况下不使用numpy.ndarray,而是用一些pandas不会出问题的东西(也许可以先分配好tslib.Timestamp的列表)。

这里还有一个意外情况的例子:

>>> npa = numpy.ndarray(1, dtype=numpy.datetime64)
>>> npa[0] = -9223372036854775808
>>> pandas.Series(npa)
0   NaT
>>> pandas.Series(npa)[0]
<Timestamp: 1969-12-31 15:58:10.448384>

根据Jeff下面的评论,我对出错的原因有了更多了解。

>>> npa = numpy.ndarray(2, dtype=numpy.int64)
>>> npa[0] = -9223372036854775808
>>> npa[1] = 1326834000090451
>>> npa
array([-9223372036854775808,     1326834000090451])
>>> s_npa = pandas.Series(npa, dtype='M8[us]')
>>> s_npa
0                          NaT
1   2012-01-17 21:00:00.090451

太好了!这个序列保留了NA和我的时间戳。不过,如果我试图从这个序列创建一个DataFrame,NaT就消失了。

>>> pandas.DataFrame({'ts':s_npa})
                      ts
0 1969-12-31 15:58:10.448384
1 2012-01-17 21:00:00.090451

唉,随便试试,我尝试把我的整数当作从纪元开始的纳秒来解释。让我惊讶的是,DataFrame正常工作了:

s2_npa = pandas.Series(npa, dtype='M8[ns]')
>>> s2_npa
0                             NaT
1   1970-01-16 08:33:54.000090451
>>> pandas.DataFrame({"ts":s2_npa})
                             ts
0                           NaT
1 1970-01-16 08:33:54.000090451

当然,我的时间戳不对。我的意思是,pandas.DataFrame在这里表现得不一致。为什么在使用dtype='M8[ns]'时能保留NaT,而在使用'M8[us]'时却不能?

我目前正在使用这个变通方法来转换,这样虽然速度慢了很多,但能工作:

>>> s = pandas.Series([1000*ts if ts != -9223372036854775808 else ts for ts in npa], dtype='M8[ns]')
>>> pandas.DataFrame({'ts':s})
                          ts
0                        NaT
1 2012-01-17 21:00:00.090451

(几个小时后...)

好的,我有进展了。我深入研究了代码,发现Series的repr函数最终调用了'_format_datetime64',这个函数检查'isnull'并会打印出'NaT'。这解释了这两者之间的区别。

>>> pandas.Series(npa)
0   NaT
>>> pandas.Series(npa)[0]
<Timestamp: 1969-12-31 15:58:10.448384>

前者似乎尊重NA,但它只在打印时这样做。我想可能还有其他pandas函数会调用'isnull'并根据结果采取行动,这在这种情况下可能对NA时间戳部分有效。不过,我知道这个Series是错误的,因为第一个元素的类型不对。它是一个Timestamp,但应该是NaTType。我接下来的步骤是深入研究Series的构造函数,弄清楚pandas在构造时如何使用NaT值。可以推测,当我指定dtype='M8[us]'时,它可能缺少一个情况...(后续会有更多信息)。

根据Andy在评论中的建议,我尝试使用pandas的Timestamp来解决这个问题,但没有成功。以下是这些结果的例子:

>>> npa = numpy.ndarray(1, dtype='i8')
>>> npa[0] = -9223372036854775808
>>> npa
array([-9223372036854775808])
>>> pandas.tslib.Timestamp(npa.view('M8[ns]')[0]).value
-9223372036854775808
>>> pandas.tslib.Timestamp(npa.view('M8[us]')[0]).value
-28909551616000

1 个回答

2

回答:不可以

从技术上来说,我在github上报告了这个bug,并在这里得到了回复:

https://github.com/pydata/pandas/issues/2800#issuecomment-13161074

“目前在索引等操作中,不支持纳秒以外的单位。这一点应该严格执行。”

我用'ns'而不是'us'进行的所有测试都运行得很好。我期待着未来的版本。

对于感兴趣的人,我修改了我的C++ Python模块,让它遍历我从磁盘加载的int64_t数组,并把所有值都乘以1000,除了NA值(boost::integer_traits::const_min)。我担心性能问题,但对我来说,加载时间的差别非常小。(在Python中做同样的事情会非常非常慢。)

撰写回答