【问题标题】:Convert pandas timezone-aware DateTimeIndex to naive timestamp, but in certain timezone将熊猫时区感知 DateTimeIndex 转换为天真的时间戳,但在特定时区
【发布时间】:2013-05-13 18:56:25
【问题描述】:

您可以使用函数 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

我可以通过将时区设置为无来删除时区,但结果会转换为 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 转换为天真的时区,但同时保留它设置的时区?


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

【问题讨论】:

  • Timezone=None 表示 UTC...我不确定我是否理解您在此处询问的内容。
  • 我添加了一些解释。我想保留您作为用户“看到”的时间。我希望这能澄清一点。
  • 啊哈,确实如此,我不知道你可以用replace做到这一点。
  • @AndyHayden 所以实际上我想要的是 tz_localize 的精确倒数,这是 replace(tzinfo=None) 对日期时间所做的,但它确实不是一个非常明显的方法。

标签: python pandas datetime timezone


【解决方案1】:

我如何在欧洲使用 15 分钟的频率日期时间索引来处理这个问题。

如果您处于时区感知(在我的情况下为Europe/Amsterdam)索引并希望通过转换将其转换为 timezone naive 索引的情况一切都到当地时间,你会有dst问题,即

  • 3 月的最后一个星期日(欧洲切换到夏令时)将少 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 月份,我会删除重复项。

【讨论】:

    【解决方案2】:

    迟来的贡献,但在Python datetime and pandas give different timestamps for the same date 中遇到了类似的东西。

    如果您在 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]')
    

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        因为我总是难以记住,所以快速总结一下它们各自的作用:

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

        【讨论】:

          【解决方案5】:

          最重要的是在定义日期时间对象时添加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())
          

          【讨论】:

            【解决方案6】:

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

            【讨论】:

            • 如果您正在使用已经是 UTC 并且需要将其转换为本地时间并然后删除时区:from tzlocal import get_localzonetz_here = get_localzone()、@987654333 @
            • 如果您没有有用的索引,您可能需要t.dt.tz_localize(None)t.dt.tz_convert(None)。注意.dt
            • 此解决方案仅在系列中有一个唯一 tz 时才有效。如果您在同一个系列中有多个不同的 tz,请在此处查看(并投票)解决方案 :-):stackoverflow.com/a/59204751/1054154
            【解决方案7】:

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

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

            【讨论】:

            • 迟到的评论,但我希望结果是本地时区的时间,而不是 UTC 时间。正如我在问题中所展示的,将 tz 设置为 None 也会将其转换为 UTC。
            • 此外,时间序列已经是时区感知的,因此在其上调用 tz_convert 会引发错误。
            【解决方案8】:

            基于 D.A. 的建议,即“做你想做的事的唯一方法是修改基础数据”并使用 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 啊,好接!我没有考虑过!我将修改我的解决方案以尽快处理这种情况。
            • 我相信这仍然是错误的,因为您只是在计算第一次的偏移量,而不是随着时间的推移而计算。这将导致您错过夏令时,并且不会在给定日期及以后进行相应调整。
            【解决方案9】:

            我认为你无法以比你建议的更有效的方式实现你想要的。

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

            供参考,这里是Timestampreplace方法(见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

            【讨论】:

            • 感谢您的回答,以及迟到的回复:我的案例不是申请,只是对我自己工作的科学分析(例如,不与世界各地的合作者分享)。在这种情况下,使用简单的时间戳会更容易,但在您的当地时间。所以我不必担心时区,只需将时间戳解释为当地时间(额外的“麻烦”可能是所有内容都必须在时区,否则你会得到类似“无法比较偏移量-天真和偏移感知日期时间”)。但在处理更复杂的应用程序时,我完全同意你的看法。
            猜你喜欢
            • 2014-05-19
            • 2016-11-26
            • 2014-10-28
            • 2013-02-18
            • 2022-07-21
            • 2021-12-18
            • 2015-03-03
            • 2012-04-17
            相关资源
            最近更新 更多