将可识别熊猫时区的DateTimeIndex转换为朴素的时间戳,但在特定的时区


99

您可以使用该函数tz_localize来识别Timestamp或DateTimeIndex时区,但是如何相反:如何在保留时区的情况下将时区识别的Timestamp转换为朴素的时间戳?

一个例子:

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

我可以通过将其设置为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

还有另一种方法可以将DateTimeIndex转换为朴素的时区,但同时保留设置时区的时区吗?


关于我问这个问题的原因的一些上下文:我想使用时区朴素的时间序列(以避免时区的额外麻烦,在我正在研究的情况下不需要它们)。
但是由于某些原因,我必须处理本地时区(欧洲/布鲁塞尔)中的时区感知时间序列。由于我所有其他数据都是时区纯朴的(但以本地时区表示),因此我想将此时间序列转换为朴素才能进一步使用,但它也必须以我的本地时区表示(因此,只需删除时区信息,而不将用户可见的时间转换为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> 

因此,基于此,我可以执行以下操作,但是我认为当使用较大的时间序列时,这将不是很有效:

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

时区=无表示UTC。。。我不确定我是否理解您在这里的要求。
安迪·海登

我添加了一些解释。我想保留您作为用户“看到”的时间。我希望这可以澄清一下。
13年

啊哈,确实如此,但我没有意识到您可以使用来做到这一点replace
安迪·海登

@AndyHayden所以其实我要的是完全相反的的tz_localize这是什么replace(tzinfo=None)做的日期时间,但它的确是一个不很明显的方式。
乔里斯

Answers:


122

为了回答我自己的问题,此功能已同时添加到了熊猫中。从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')

使用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')

此外,您还可以使用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')

这比解决方案性能更高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

1
如果你正在使用的东西,是已经UTC,并需要将其转换为本地时间和工作,然后:删除时区from tzlocal import get_localzonetz_here = get_localzone()<datetime object>.tz_convert(tz_here).tz_localize(None)
内森·劳埃德

3
如果没有有用的索引,则可能需要t.dt.tz_localize(None)t.dt.tz_convert(None)。注意.dt
Acumenus

2
仅当系列中有一个唯一的tz时,此解决方案才有效。如果您在同一系列中有多个不同的tz,请在此处查看(并赞成)解决方案:-):stackoverflow.com/a/59204751/1054154
tozCSS

14

我认为您无法以比您提议的更有效的方式来实现所需的目标。

潜在的问题是时间戳(如您所知)由两部分组成。代表UTC时间和时区tz_info的数据。当在屏幕上打印时区时,时区信息仅用于显示目的。在显示时,数据会适当偏移,并且+01:00(或类似值)会添加到字符串中。剥离tz_info值(使用tz_convert(tz = None))实际上并不会改变表示时间戳幼稚部分的数据。

因此,执行所需操作的唯一方法是修改基础数据(熊猫不允许这样做……DatetimeIndex是不可变的–请参见DatetimeIndex的帮助),或创建一组新的时间戳对象并包装它们在新的DatetimeIndex中。您的解决方案将执行后者:

pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])

作为参考,以下是replace方法Timestamp(请参阅tslib.pyx):

def replace(self, **kwds):
    return Timestamp(datetime.replace(self, **kwds),
                     offset=self.offset)

您可以参考文档上的内容datetime.datetime,它datetime.datetime.replace还会创建一个新对象。

如果可以的话,提高效率的最佳选择是修改数据源,以使它(错误地)报告没有时区的时间戳。您提到:

我想使用时区朴素的时间序列(以避免额外的时区麻烦,在我正在处理的情况下,我不需要它们)

我很好奇您指的是什么额外的麻烦。作为所有软件开发的一般规则,我建议您将时间戳记“天真值”保持在UTC中。没有什么比查看两个不同的int64值(要知道它们属于哪个时区)更糟糕的了。如果您始终始终使用UTC作为内部存储,那么将避免无数的麻烦。我的口头禅是时区是人类I / O只


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

12

因为我总是想不起来,所以快速总结一下这些功能:

>>> 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')

7

tz显式设置索引的属性似乎可行:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None

3
意见较晚,但我希望结果是本地时区而不是UTC表示的时间。正如我在问题中所示,将设置tz为None也会将其转换为UTC。
joris

此外,时间序列已经知道时区,因此调用tz_convert它会引发错误。
joris

4

当系列中有多个不同时区时,可接受的解决方案将不起作用。它抛出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]

3

在DA的建议的基础上,“唯一的方法就是修改基础数据”,然后使用numpy修改基础数据...

这对我有用,并且非常快:

def tz_to_naive(datetime_index):
    """Converts a tz-aware DatetimeIndex into a tz-naive DatetimeIndex,
    effectively baking the timezone into the internal representation.

    Parameters
    ----------
    datetime_index : pandas.DatetimeIndex, tz-aware

    Returns
    -------
    pandas.DatetimeIndex, tz-naive
    """
    # Calculate timezone offset relative to UTC
    timestamp = datetime_index[0]
    tz_offset = (timestamp.replace(tzinfo=None) - 
                 timestamp.tz_convert('UTC').replace(tzinfo=None))
    tz_offset_td64 = np.timedelta64(tz_offset)

    # Now convert to naive DatetimeIndex
    return pd.DatetimeIndex(datetime_index.values + tz_offset_td64)

感谢您的回答!但是,我认为这仅在数据集周期内没有夏/冬过渡时才有效。
joris 2013年

@joris啊,很好!我没有考虑过!我将修改解决方案以尽快处理这种情况。
杰克·凯利

我认为这仍然是错误的,因为您只是在计算第一次的偏移量,而不是整个时间都在计算偏移量。这将导致您错过夏令时,并且在给定的日期及以后没有进行相应的调整。
Pierre-Luc Bertrand

2

贡献较晚,但在Python日期时间中遇到了类似情况,而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]')

请注意,这会在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')

相反,tz_convert(None)不修改内部时间戳记,而是删除tzinfo

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

我的底线是:如果可以使用或仅使用时区识别日期时间 t.tz_convert(None)不会修改底层POSIX时间戳记的时间戳记,请。请记住,那时您实际上正在使用UTC。

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


0

最重要的是tzinfo定义日期时间对象时添加。

from datetime import datetime, timezone
from tzinfo_examples import HOUR, Eastern
u0 = datetime(2016, 3, 13, 5, tzinfo=timezone.utc)
for i in range(4):
     u = u0 + i*HOUR
     t = u.astimezone(Eastern)
     print(u.time(), 'UTC =', t.time(), t.tzname())
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.