簡體   English   中英

將熊貓時區感知 DateTimeIndex 轉換為朴素的時間戳,但在某些時區

[英]Convert pandas timezone-aware DateTimeIndex to naive timestamp, but in certain timezone

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

為了回答我自己的問題,此功能已同時添加到 Pandas 中。 從 pandas tz_localize(None)開始,您可以使用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

因為我總是很難記住,所以快速總結一下每個人的作用:

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

我認為您無法以比您提出的更有效的方式實現您想要的。

潛在的問題是時間戳(如您所知)由兩部分組成。 表示 UTC 時間和時區的數據 tz_info。 時區信息僅用於將時區打印到屏幕時的顯示目的。 在顯示時,數據會適當偏移,並將 +01:00(或類似的)添加到字符串中。 剝離 tz_info 值(使用 tz_convert(tz=None))實際上並沒有改變代表時間戳朴素部分的數據。

所以,做你想做的唯一方法是修改底層數據(熊貓不允許這樣做...... 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 進行內部存儲,那么您將避免無數頭痛。 我的口頭禪是Timezones are for human I/O only

當系列中有多個不同的時區時,已接受的解決方案不起作用。 它拋出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]

顯式設置索引的tz屬性似乎有效:

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

建立在 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)

遲到的貢獻,但只是在Python datetime 中遇到了類似的東西,pandas 為同一個 date 提供了不同的時間戳

如果您在pandas tz_localize(None)區感知日期時間,從技術上講, 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]')

我的底線是:如果您可以或僅使用不修改底層 POSIX 時間戳的t.tz_convert(None) ,請堅持使用時區感知日期時間。 請記住,那時您實際上是在使用 UTC。

(Windows 10 上的 Python 3.8.2 x64, pandas v1.0.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())

我是如何在歐洲用 15 分鍾頻率的日期時間索引處理這個問題的。

如果您處於具有時區感知(在我的情況下為Europe/Amsterdam )索引並希望通過將所有內容轉換為本地時間將其轉換為時區原始索引的情況,您將遇到 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 月,我刪除了重復項。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM