将 pandas 时区感知的 DateTimeIndex 转换为简单时间戳,但在特定时区

2025-01-06 08:32:00
admin
原创
119
摘要:问题描述:您可以使用该函数tz_localize使 Timestamp 或 DateTimeIndex 具有时区感知能力,但如何做相反的事情:如何将具有时区感知能力的 Timestamp 转换为简单的 Timestamp,同时保留其时区?举个例子:In [82]: t = pd.date_range(star...

问题描述:

您可以使用该函数tz_localize使 Timestamp 或 DateTimeIndex 具有时区感知能力,但如何做相反的事情:如何将具有时区感知能力的 Timestamp 转换为简单的 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

解决方案 1:

回答我自己的问题,此功能已添加到 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')

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

解决方案 2:

因为我总是很难记住,下面简要总结一下这些功能的作用:

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

解决方案 3:

我认为你无法以比你提出的更有效的方式实现你的目标。

根本问题是时间戳(您似乎知道)由两部分组成。表示 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])

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

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

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

如果可以的话,为了提高效率,最好的办法是修改数据源,使其(错误地)报告不带时区的时间戳。您提到:

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

我很好奇你指的是什么额外的麻烦。我建议作为所有软件开发的一般规则,将时间戳的“简单值”保留在 UTC 中。没有什么比查看两个不同的 int64 值并想知道它们属于哪个时区更糟糕的了。如果你总是、总是、总是使用 UTC 作为内部存储,那么你将避免无数的麻烦。我的口头禅是时区仅适用于人类 I/O

解决方案 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]

解决方案 5:

明确设置tz索引的属性似乎有效:

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

解决方案 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]')

请注意,这会在夏令时转换期间给你带来奇怪的事情,例如

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 上的 Python 3.8.2 x64,pandasv1.0.5。)

解决方案 7:

基于 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)

解决方案 8:

我如何使用欧洲 15 分钟频率 datetimeindex 来处理这个问题。

如果你有一个时区感知索引(Europe/Amsterdam在我的情况下),并且想要通过将所有内容转换为本地时间将其转换为时区简单索引,则会遇到 dst 问题,即

  • 三月的最后一个星期日(欧洲将改为夏令时)将缺 1 小时

  • 10 月的最后一个星期日(欧洲将改为夏令时)将有 1 小时的重复

您可以按照以下方式处理:

# make index tz naive
df.index = df.index.tz_localize(None)

# handle dst
if df.index[0].month == 3:
    # last sunday of march, one hour is lost
    df = df.resample("15min").pad()

if df.index[0].month == 10:
    # in october, one hour is added
    df = df[~df.index.duplicated(keep='last')]

df注意:在我的例子中,我在仅包含一个月份的计算机上运行上述代码,因此我df.index[0].month需要找出月份。如果您的计算机包含多个月份,则可能应该对其进行不同的索引,以便知道何时执行 DST。

它包括从 3 月份的最后一个有效值重新采样,以避免丢失 1 小时(在我的例子中,我的所有数据都是以 15 分钟为间隔,因此我像这样重新采样。根据您的间隔重新采样)。对于 10 月份,我会删除重复项。

解决方案 9:

最重要的是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())
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用