Pandaların saat dilimine duyarlı DateTimeIndex öğelerini saf zaman damgasına, ancak belirli bir saat dilimine dönüştür


99

tz_localizeBir Zaman Damgası veya DateTimeIndex zaman diliminin farkında olmasını sağlamak için işlevi kullanabilirsiniz , ancak bunun tersini nasıl yapabilirsiniz: Zaman dilimi farkında olan bir Zaman Damgasını zaman dilimini korurken nasıl saf bir zamana dönüştürebilirsiniz?

Bir örnek:

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

Saat dilimini Yok olarak ayarlayarak kaldırabilirim, ancak sonuç UTC'ye dönüştürülür (saat 12, 10 oldu):

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'i saf saat dilimine dönüştürebilmemin başka bir yolu var mı, ancak ayarlandığı saat dilimini korurken?


Bazı bağlam nedenine bu soruyorum: Ben zaman dilimi naif timeseries ile çalışmak istiyoruz (saat dilimine sahip ekstra güçlük önlemek için, ben üzerinde çalışıyorum vaka için onlara ihtiyacımız yok).
Ancak bazı nedenlerden dolayı, yerel zaman dilimimde (Avrupa / Brüksel) saat dilimine duyarlı bir zaman serisiyle uğraşmak zorundayım. Diğer tüm verilerim zaman diliminde saf olduğundan (ancak yerel saat dilimimde temsil edildiğinden), bu zaman serisini onunla daha fazla çalışmak için naif hale getirmek istiyorum, ancak aynı zamanda yerel saat dilimimde de temsil edilmesi gerekiyor (bu nedenle sadece saat dilimi bilgilerini kaldırın, kullanıcının gördüğü zamanı UTC'ye dönüştürmeden ).

Zamanın aslında UTC olarak dahili olarak saklandığını ve sadece siz onu temsil ettiğinizde başka bir zaman dilimine dönüştürüldüğünü biliyorum, bu yüzden onu "yerelleştirmek" istediğimde bir tür dönüşüm olmalı. Örneğin, python datetime modülü ile saat dilimini şu şekilde "kaldırabilirsiniz":

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> 

Dolayısıyla, buna dayanarak aşağıdakileri yapabilirim, ancak bunun daha büyük zaman serileriyle çalışırken çok verimli olmayacağını düşünüyorum:

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

Zaman dilimi = Yok demek UTC ... Burada ne sorduğunuzu anladığımdan emin değilim.
Andy Hayden 18:13

Bazı açıklamalar ekledim. Kullanıcı olarak 'gördüğünüz' zamanı saklamak istiyorum. Umarım bu biraz açıklığa kavuşturur.
joris

Ah ha, öyle, bunu yapabileceğini bilmiyordum replace.
Andy Hayden

@AndyHayden Yani aslında istediğim tam tersi tz_localizeveri zamanları için replace(tzinfo=None)yaptığı şey, ama aslında bu çok açık bir yol değil.
joris

Yanıtlar:


123

Kendi sorumu cevaplamak için, bu arada pandalara bu işlevsellik eklendi. Pandalar 0.15.0'dan başlayarak ,tz_localize(None) yerel saatle sonuçlanan saat dilimini kaldırmak için kullanabilirsiniz .
Whatsnew girişine bakın: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improements

Yani yukarıdaki örneğimle:

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

kullanmak tz_localize(None), zaman dilimi bilgilerini kaldırır ve yetersiz yerel saatle sonuçlanır :

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

Ayrıca, tz_convert(None)saat dilimi bilgilerini kaldırmak, ancak UTC'ye dönüştürmek için de kullanabilirsiniz , böylece saf UTC zamanı elde edebilirsiniz :

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

Bu kadar daha fazla ölçülebilir daha datetime.replacesolüsyon:

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
Eğer zaten UTC birşeyle ve yerel saate dönüştürmek ve gereği ile çalışıyoruz sonra : dilimini damla from tzlocal import get_localzone, tz_here = get_localzone(),<datetime object>.tz_convert(tz_here).tz_localize(None)
Nathan Lloyd

3
Yararlı bir dizininiz yoksa, ihtiyacınız olabilir t.dt.tz_localize(None)veya t.dt.tz_convert(None). Not .dt.
Acumenus

2
Bu çözüm, yalnızca Seride benzersiz bir tz olduğunda işe yarar. Aynı Seride birden fazla farklı tz'niz varsa, çözüme buradan bakın (ve oy verin) :-): stackoverflow.com/a/59204751/1054154
tozCSS

14

Bence istediğini, önerdiğinden daha verimli bir şekilde başaramazsın.

Temel sorun, zaman damgalarının (sizin de farkında olduğunuz gibi) iki bölümden oluşmasıdır. UTC saatini ve saat dilimini temsil eden veriler, tz_info. Saat dilimi bilgileri, yalnızca saat dilimi ekrana yazdırılırken görüntüleme amacıyla kullanılır. Görüntüleme zamanında, veriler uygun şekilde kaydırılır ve diziye +01: 00 (veya benzeri) eklenir. Tz_info değerinin çıkarılması (tz_convert (tz = Yok) kullanılarak) aslında zaman damgasının naif bölümünü temsil eden verileri değiştirmez.

Dolayısıyla, istediğiniz şeyi yapmanın tek yolu, temeldeki verileri değiştirmektir (pandalar buna izin vermez ... DatetimeIndex değişmezdir - DatetimeIndex'teki yardıma bakın) veya yeni bir zaman damgası nesneleri kümesi oluşturup bunları paketlemektir. yeni bir DatetimeIndex. Çözümünüz ikincisini yapıyor:

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

Referans için, (bkz. Tslib.pyx) replaceyöntemi Timestamp:

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

Bunun yeni bir nesne oluşturduğunu datetime.datetimegörmek için dokümanlara başvurabilirsiniz datetime.datetime.replace.

Yapabiliyorsanız, verimlilik için en iyi seçeneğiniz, verilerin kaynağını değiştirerek (yanlış olarak) zaman damgalarını saat dilimleri olmadan bildirmektir. Bahsettiniz:

Zaman dilimi saf zaman serileriyle çalışmak istiyorum (saat dilimleriyle ilgili ekstra güçlükleri önlemek için ve üzerinde çalıştığım vaka için onlara ihtiyacım yok)

Hangi ekstra güçlükten bahsettiğini merak ediyorum. Tüm yazılım geliştirme için genel bir kural olarak, zaman damganızı 'naif değerleri' UTC'de tutmanızı öneririm. Hangi zaman dilimine ait olduklarını merak ederek iki farklı int64 değerine bakmaktan biraz daha kötüsü var. Her zaman, her zaman, dahili depolama için her zaman UTC kullanırsanız, sayısız baş ağrısından kurtulursunuz. Benim mantram, Zaman Dilimleri yalnızca insan G / Ç'si içindir .


3
Cevabınız ve geç yanıtınız için teşekkürler: Benim durumum bir uygulama değil, sadece kendi çalışmam için bilimsel bir analiz (yani, dünyanın her yerindeki ortak çalışanlarla paylaşmak yok). Ve bu durumda, sadece saf zaman damgaları ile çalışmak daha kolay olabilir, ancak yerel saatinizde. Bu nedenle, saat dilimleri hakkında endişelenmeme gerek yok ve zaman damgasını yerel saat olarak yorumlayabilirim (ekstra 'güçlük', örneğin, her şeyin zaman dilimlerinde olması gerektiği olabilir, aksi takdirde "fark karşılaştırılamaz- naif ve ofsete duyarlı veri zamanları "). Ancak daha karmaşık uygulamalarla uğraşırken size tamamen katılıyorum.
joris

13

Her zaman hatırlamakta zorlandığım için, bunların her birinin ne yaptığının hızlı bir özeti:

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

tzDizinin özniteliğini açıkça ayarlamak işe yarıyor gibi görünüyor:

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

3
Geç yorum, ancak sonucun UTC'de değil, yerel saat diliminde gösterilen saat olmasını istiyorum. Ve soruda gösterdiğim gibi, tzYok olarak ayarlamak da onu UTC'ye dönüştürür.
joris

Dahası, zaman serileri zaten zaman dilimi farkındadır, bu nedenle tz_convertonu çağırmak bir hataya neden olacaktır.
joris

4

Bir Seride birden fazla farklı saat dilimi olduğunda kabul edilen çözüm işe yaramaz. AtarValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

Çözüm applyyöntemi kullanmaktır .

Lütfen aşağıdaki örneklere bakın:

# 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'nın " İstediğinizi yapmanın tek yolunun, temeldeki verileri değiştirmek " ve temel verileri değiştirmek için numpy kullanmak olduğu önerisine dayanarak ...

Bu benim için çalışıyor ve oldukça hızlı:

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)

Cevabınız için teşekkürler! Bununla birlikte, bunun yalnızca veri kümesi döneminde yaz / kış geçişi olmaması durumunda işe yarayacağını düşünüyorum.
joris

@joris Ah, iyi yakaladın! Bunu düşünmemiştim! Bu durumu en kısa sürede ele almak için çözümümü değiştireceğim.
Jack Kelly

Bunun hala yanlış olduğuna inanıyorum, çünkü sadece ilk seferin ofsetini hesaplıyorsunuz ve zaman içinde ilerledikçe değil. Bu, gün ışığından yararlanma saatini kaçırmanıza ve verilen tarih ve sonrasında buna göre ayarlama yapmanıza neden olmayacaktır.
Pierre-Luc Bertrand

2

Geç katkı, ancak Python tarih saatinde benzer bir şeyle karşılaşıldı ve pandalar aynı tarih için farklı zaman damgaları veriyor .

İçinde saat dilimine duyarlı tarih saatiniz varsapandas , teknik olarak, tz_localize(None)POSIX zaman damgasını (dahili olarak kullanılan) zaman damgasından yerel saat UTC gibi değiştirir. Yerel terimi bu bağlamda belirtilen saat diliminde yerel . Ör:

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

Bunun DST geçişleri sırasında sizi garip şeyler bırakacağını unutmayın , örneğin

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

Aksine, tz_convert(None)dahili zaman damgasını değiştirmez, yalnızca tzinfo.

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

Alt satırım şudur: Eğer yapabiliyorsanız veya sadece t.tz_convert(None)temeldeki POSIX zaman damgasını değiştirmeyen kullanabiliyorsanız, saat dilimine duyarlı tarih saatine bağlı kalın. O zaman pratikte UTC ile çalıştığınızı unutmayın.

(Windows 10, pandasv1.0.5 üzerinde Python 3.8.2 x64 .)


0

En önemli şey, tzinfobir datetime nesnesi tanımladığınızda eklemektir .

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())
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.