将pandas timezone-aware DateTimeIndex转换为天真时间戳,但在某个时区

jor*_*ris 71 python pandas

您可以使用该功能tz_localize来创建一个时间戳或DateTimeIndex时区知道,但你怎么能反其道而行之:你怎么能转换时区了解时间戳天真的一个,同时保持其时区?

一个例子:

In [82]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10, freq='s', tz="Europe/Brussels")

In [83]: t
Out[83]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels
Run Code Online (Sandbox Code Playgroud)

我可以通过将时区设置为None来删除时区,但结果将转换为UTC(12点钟变为10):

In [86]: t.tz = None

In [87]: t
Out[87]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 10:00:00, ..., 2013-05-18 10:00:09]
Length: 10, Freq: S, Timezone: None
Run Code Online (Sandbox Code Playgroud)

有没有另一种方法我可以将DateTimeIndex转换为时区naive,但保留时区设置为?


关于我问这个问题的一些背景:我想使用timezone天真的时间序列(为了避免时区的额外麻烦,我不需要它们用于我正在处理的情况).
但出于某种原因,我必须在当地时区(欧洲/布鲁塞尔)处理时区感知的时间序列.正如我的所有其他数据时区天真(但在我的本地时区代表),我想这个时间序列转化为天真的进一步工作与它,但它也有在我的本地时区来表示(所以刚取出时区信息,不将用户可见时间转换为UTC).

我知道时间实际上是内部存储为UTC,并且只有当你表示它时才转换为另一个时区,因此当我想要"离域化"时必须进行某种转换.例如,使用python datetime模块,您可以像这样"删除"时区:

In [119]: d = pd.Timestamp("2013-05-18 12:00:00", tz="Europe/Brussels")

In [120]: d
Out[120]: <Timestamp: 2013-05-18 12:00:00+0200 CEST, tz=Europe/Brussels>

In [121]: d.replace(tzinfo=None)
Out[121]: <Timestamp: 2013-05-18 12:00:00> 
Run Code Online (Sandbox Code Playgroud)

因此,基于此,我可以执行以下操作,但我认为在使用更大的时间序列时这不会非常有效:

In [124]: t
Out[124]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels

In [125]: pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Out[125]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: None, Timezone: None
Run Code Online (Sandbox Code Playgroud)

jor*_*ris 86

为了回答我自己的问题,此时此功能已添加到pandas中.从pandas 0.15.0开始,您可以使用tz_localize(None)删除时区导致当地时间.
请参阅whatsnew条目:http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

以上我的例子:

In [4]: t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H',
                          tz= "Europe/Brussels")

In [5]: t
Out[5]: DatetimeIndex(['2013-05-18 12:00:00+02:00', '2013-05-18 13:00:00+02:00'],
                       dtype='datetime64[ns, Europe/Brussels]', freq='H')
Run Code Online (Sandbox Code Playgroud)

使用tz_localize(None)删除时区信息导致当地时间过长:

In [6]: t.tz_localize(None)
Out[6]: DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], 
                      dtype='datetime64[ns]', freq='H')
Run Code Online (Sandbox Code Playgroud)

此外,您还可以使用tz_convert(None)删除时区信息但转换为UTC,从而产生朴素的UTC时间:

In [7]: t.tz_convert(None)
Out[7]: DatetimeIndex(['2013-05-18 10:00:00', '2013-05-18 11:00:00'], 
                      dtype='datetime64[ns]', freq='H')
Run Code Online (Sandbox Code Playgroud)

这比解决方案更高效datetime.replace:

In [31]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10000, freq='H',
                           tz="Europe/Brussels")

In [32]: %timeit t.tz_localize(None)
1000 loops, best of 3: 233 µs per loop

In [33]: %timeit pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
10 loops, best of 3: 99.7 ms per loop
Run Code Online (Sandbox Code Playgroud)

  • 如果你没有有用的索引,你可能需要 `t.dt.tz_localize(None)` 或 `t.dt.tz_convert(None)`。注意`.dt`。 (3认同)
  • 此解决方案仅在系列中存在一个唯一的 tz 时才有效。如果您在同一个系列中有多个不同的 tz,请在此处查看(并投票)解决方案:-):/sf/answers/4144332601/ (2认同)

Jua*_*rro 28

因为我总是很难记住,所以快速总结一下每个人的作用:

>>> pd.Timestamp.now()  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.utcnow()  # tz aware UTC
Timestamp('2019-10-07 08:30:19.428748+0000', tz='UTC')

>>> pd.Timestamp.now(tz='Europe/Brussels')  # tz aware local time
Timestamp('2019-10-07 10:30:19.428748+0200', tz='Europe/Brussels')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_localize(None)  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_localize(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')
Run Code Online (Sandbox Code Playgroud)


D. *_* A. 13

我认为你不能以比你提出的更有效的方式实现你想要的东西.

潜在的问题是时间戳(你似乎知道)由两部分组成.表示UTC时间的数据和时区tz_info.在将时区打印到屏幕时,时区信息仅用于显示目的.在显示时,数据被适当地偏移,+ 01:00(或类似)被添加到字符串.剥离tz_info值(使用tz_convert(tz = None))实际上并不会更改表示时间戳的天真部分的数据.

所以,做你想做的事情的唯一方法是修改底层数据(pandas不允许这样... DatetimeIndex是不可变的 - 请参阅DatetimeIndex上的帮助),或者创建一组新的时间戳对象并将它们包装起来在新的DatetimeIndex中.你的解决方案是后者:

pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Run Code Online (Sandbox Code Playgroud)

作为参考,这是(参见tslib.pyx)的replace方法Timestamp:

def replace(self, **kwds):
    return Timestamp(datetime.replace(self, **kwds),
                     offset=self.offset)
Run Code Online (Sandbox Code Playgroud)

您可以参考文档datetime.datetime以查看是否datetime.datetime.replace还创建了一个新对象.

如果可以的话,效率的最佳选择是修改数据源,以便(错误地)报告没有时区的时间戳.你提到过:

我想使用timezone天真的时间序列(为了避免时区的额外麻烦,我不需要它们用于我正在处理的情况)

我很好奇你指的是多么麻烦.我建议作为所有软件开发的一般规则,保留UTC中的时间戳'天真值'.没有比看两个不同的int64值更糟糕的了解它们属于哪个时区.如果您始终始终使用UTC作为内部存储,那么您将避免无数令人头疼的问题.我的口头禅是Timezones仅供人类I/O使用.

  • 感谢您的回答,以及迟到的回复:我的案例不是申请,只是对我自己工作的科学分析(例如,不与世界各地的合作者共享)。在这种情况下,使用简单的时间戳会更容易,但要使用本地时间。所以我不必担心时区,只需将时间戳解释为本地时间(额外的“麻烦”可以是例如所有内容都必须在时区中,否则您会得到诸如“无法比较偏移量-天真的和偏移感知日期时间”)。但是在处理更复杂的应用程序时,我完全同意您的看法。 (3认同)

fil*_*mor 6

tz显式设置索引的属性似乎有效:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None
Run Code Online (Sandbox Code Playgroud)

  • 迟到的评论,但我希望结果是在本地时区表示的时间,而不是UTC.正如我在问题中所示,将`tz`设置为None也会将其转换为UTC. (3认同)

toz*_*CSS 6

当系列中有多个不同的时区时,已接受的解决方案不起作用。它抛出ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

解决方法是使用apply方法。

请参阅以下示例:

# Let's have a series `a` with different multiple timezones. 
> a
0    2019-10-04 16:30:00+02:00
1    2019-10-07 16:00:00-04:00
2    2019-09-24 08:30:00-07:00
Name: localized, dtype: object

> a.iloc[0]
Timestamp('2019-10-04 16:30:00+0200', tz='Europe/Amsterdam')

# trying the accepted solution
> a.dt.tz_localize(None)
ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

# Make it tz-naive. This is the solution:
> a.apply(lambda x:x.tz_localize(None))
0   2019-10-04 16:30:00
1   2019-10-07 16:00:00
2   2019-09-24 08:30:00
Name: localized, dtype: datetime64[ns]

# a.tz_convert() also does not work with multiple timezones, but this works:
> a.apply(lambda x:x.tz_convert('America/Los_Angeles'))
0   2019-10-04 07:30:00-07:00
1   2019-10-07 13:00:00-07:00
2   2019-09-24 08:30:00-07:00
Name: localized, dtype: datetime64[ns, America/Los_Angeles]
Run Code Online (Sandbox Code Playgroud)


MrF*_*pes 6

迟来的贡献,但刚刚在 Python datetime 中遇到了类似的东西,pandas 为同一日期提供了不同的时间戳

如果您在 中具有时区感知日期时间pandas从技术上讲,tz_localize(None)会更改 POSIX 时间戳(内部使用),就好像时间戳中的本地时间是 UTC 一样。 在这种情况下,本地是指指定时区的本地。前任:

import pandas as pd

t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H', tz="US/Central")
# DatetimeIndex(['2013-05-18 12:00:00-05:00', '2013-05-18 13:00:00-05:00'], dtype='datetime64[ns, US/Central]', freq='H')

t_loc = t.tz_localize(None)
# DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], dtype='datetime64[ns]', freq='H')

# offset in seconds according to timezone:
(t_loc.values-t.values)//1e9
# array([-18000, -18000], dtype='timedelta64[ns]')
Run Code Online (Sandbox Code Playgroud)

请注意,这会让您在 DST 转换期间遇到奇怪的事情,例如

t = pd.date_range(start="2020-03-08 01:00:00", periods=2, freq='H', tz="US/Central")
(t.values[1]-t.values[0])//1e9
# numpy.timedelta64(3600,'ns')

t_loc = t.tz_localize(None)
(t_loc.values[1]-t_loc.values[0])//1e9
# numpy.timedelta64(7200,'ns')
Run Code Online (Sandbox Code Playgroud)

相反,tz_convert(None)不会修改内部时间戳,它只是删除tzinfo.

t_utc = t.tz_convert(None)
(t_utc.values-t.values)//1e9
# array([0, 0], dtype='timedelta64[ns]')
Run Code Online (Sandbox Code Playgroud)

我的底线是:如果可以的话,坚持使用时区感知的日期时间,或者只使用t.tz_convert(None)不会修改底层 POSIX 时间戳的日期时间。请记住,那时您实际上正在与 UTC 合作。

(Windows 10 上的 Python 3.8.2 x64,pandasv1.0.5。)