แปลง 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

ฉันสามารถลบเขตเวลาได้โดยตั้งค่าเป็นไม่มี แต่ผลลัพธ์จะถูกแปลงเป็น 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 และจะแปลงเป็นเขตเวลาอื่นเมื่อคุณเป็นตัวแทนเท่านั้นดังนั้นจึงต้องมีการแปลงบางประเภทเมื่อฉันต้องการ "delocalize" ตัวอย่างเช่นด้วยโมดูล 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 = ไม่มีหมายถึง UTC ... ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณถามที่นี่
Andy Hayden

ฉันเพิ่มคำอธิบายบางอย่าง ฉันต้องการรักษาเวลาที่คุณ 'เห็น' ในฐานะผู้ใช้ ฉันหวังว่านี่จะทำให้กระจ่างขึ้นสักหน่อย
joris

replaceอาฮ่ามันไม่ผมไม่ทราบว่าคุณสามารถทำกับ
Andy Hayden

@AndyHayden ดังนั้นสิ่งที่ฉันต้องการคือค่าผกผันที่แน่นอนtz_localizeซึ่งเป็นสิ่งที่ใช้replace(tzinfo=None)กับวันที่ แต่มันไม่ใช่วิธีที่ชัดเจน
joris

คำตอบ:


123

เพื่อตอบคำถามของตัวเองฟังก์ชันนี้ได้ถูกเพิ่มเข้าไปในแพนด้าในระหว่างนี้ เริ่มจากแพนด้า 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_localzone, tz_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 สำหรับที่เก็บข้อมูลภายในอยู่เสมอคุณจะหลีกเลี่ยงความปวดหัวนับไม่ถ้วน มนต์ของฉันคือเขตสำหรับผมมนุษย์ / O เท่านั้น


3
ขอบคุณสำหรับคำตอบและการตอบกลับล่าช้า: กรณีของฉันไม่ใช่แอปพลิเคชันเป็นเพียงการวิเคราะห์ทางวิทยาศาสตร์สำหรับงานของฉันเอง (เช่นไม่มีการแบ่งปันกับผู้ทำงานร่วมกันทั่วโลก) และในกรณีนี้อาจเป็นเรื่องง่ายกว่าที่จะทำงานกับการประทับเวลาที่ไร้เดียงสา แต่เป็นเวลาท้องถิ่นของคุณ ดังนั้นฉันไม่ต้องกังวลเกี่ยวกับเขตเวลาและสามารถตีความการประทับเวลาเป็นเวลาท้องถิ่นได้ ('ความยุ่งยาก' พิเศษอาจเป็นได้เช่นว่าทุกอย่างจะต้องอยู่ในเขตเวลามิฉะนั้นคุณจะได้รับสิ่งต่างๆเช่น "ไม่สามารถเปรียบเทียบการชดเชย - วันที่ที่ไร้เดียงสาและตรงข้ามกับข้อมูล ") แต่ฉันเห็นด้วยอย่างยิ่งกับคุณเมื่อต้องจัดการกับแอปพลิเคชันที่ซับซ้อนมากขึ้น
อริส

13

เพราะฉันพยายามจำอยู่เสมอสรุปสั้น ๆ ว่าสิ่งเหล่านี้ทำอะไร:

>>> 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ไม่มีจะแปลงเป็น 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

@joris อาจับดี! ฉันไม่ได้พิจารณาอย่างนั้น! ฉันจะแก้ไขโซลูชันของฉันเพื่อจัดการกับสถานการณ์นี้โดยเร็ว
Jack Kelly

ฉันเชื่อว่าสิ่งนี้ยังผิดอยู่เนื่องจากคุณคำนวณเฉพาะการชดเชยในครั้งแรกและไม่ได้ดำเนินไปตลอดเวลา ซึ่งจะทำให้คุณพลาดเวลาออมแสงและไม่ปรับตามวันที่กำหนดและเป็นต้นไป
Pierre-Luc Bertrand

2

ช่วงปลายมีส่วนร่วม แต่เพียงมาข้ามบางสิ่งบางอย่างที่คล้ายกันในหลาม datetime และหมีแพนด้าให้ประทับเวลาแตกต่างกันสำหรับวันเดียวกัน

ถ้าคุณมี datetime เขตตระหนักใน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

(Python 3.8.2 x64 บน Windows 10, pandasv1.0.5.)


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())
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.