Konvertieren Sie den zeitzonenabhängigen DateTimeIndex von Pandas in einen naiven Zeitstempel, jedoch in einer bestimmten Zeitzone

93

Sie können die Funktion verwenden tz_localize, um einen Zeitstempel oder eine DateTimeIndex-Zeitzone bekannt zu machen. Wie können Sie jedoch das Gegenteil tun: Wie können Sie einen zeitzonenbewussten Zeitstempel in einen naiven umwandeln, während die Zeitzone erhalten bleibt?

Ein Beispiel:

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

Ich könnte die Zeitzone entfernen, indem ich sie auf Keine setze, aber dann wird das Ergebnis in UTC konvertiert (12 Uhr wurde 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

Gibt es eine andere Möglichkeit, einen DateTimeIndex in eine naive Zeitzone zu konvertieren, aber unter Beibehaltung der Zeitzone, in der er festgelegt wurde?


Einige Zusammenhänge zu dem Grund, warum ich dies frage: Ich möchte mit zeitzonen-naiven Zeitreihen arbeiten (um den zusätzlichen Aufwand mit Zeitzonen zu vermeiden, und ich brauche sie nicht für den Fall, an dem ich arbeite).
Aber aus irgendeinem Grund muss ich mich in meiner lokalen Zeitzone (Europa / Brüssel) mit einer zeitzonenbewussten Zeitreihe befassen. Da alle meine anderen Daten zeitzonennaiv sind (aber in meiner lokalen Zeitzone dargestellt werden), möchte ich diese Zeitreihen in naiv konvertieren, um weiter damit arbeiten zu können. Sie muss jedoch auch in meiner lokalen Zeitzone dargestellt werden (entfernen Sie einfach die Zeitzoneninformationen). ohne die vom Benutzer sichtbare Zeit in UTC umzuwandeln ).

Ich weiß, dass die Zeit tatsächlich intern als UTC gespeichert und nur dann in eine andere Zeitzone konvertiert wird, wenn Sie sie darstellen. Daher muss es eine Konvertierung geben, wenn ich sie "delokalisieren" möchte. Mit dem Python-Datum / Uhrzeit-Modul können Sie beispielsweise die Zeitzone wie folgt "entfernen":

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> 

Auf dieser Grundlage könnte ich Folgendes tun, aber ich nehme an, dass dies bei der Arbeit mit einer größeren Zeitreihe nicht sehr effizient ist:

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
Joris
quelle
Zeitzone = Keine bedeutet UTC ... Ich bin nicht sicher, ob ich verstehe, was Sie hier fragen.
Andy Hayden
Ich habe eine Erklärung hinzugefügt. Ich möchte die Zeit behalten, die Sie als Benutzer "sehen". Ich hoffe das klärt es ein wenig.
Joris
Ah ha, ich wusste nicht, dass du das damit machen kannst replace.
Andy Hayden
@AndyHayden Eigentlich möchte ich also genau tz_localizedas replace(tzinfo=None)Gegenteil davon, was das für Datumsangaben tut, aber es ist in der Tat kein sehr offensichtlicher Weg.
Joris

Antworten:

116

Um meine eigene Frage zu beantworten, wurde diese Funktionalität in der Zwischenzeit Pandas hinzugefügt. Ab Pandas 0.15.0 können Sie tz_localize(None)die Zeitzone entfernen, die zur Ortszeit führt.
Siehe den aktuellen Eintrag: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

Also mit meinem Beispiel von oben:

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

using tz_localize(None)entfernt die Zeitzoneninformationen, was zu einer naiven Ortszeit führt :

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

Darüber hinaus können Sie auch tz_convert(None)die Zeitzoneninformationen entfernen, aber in UTC konvertieren, um eine naive UTC-Zeit zu erhalten :

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

Dies ist viel leistungsfähiger als die datetime.replaceLösung:

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
Joris
quelle
1
Im Fall arbeiten Sie mit etwas , das bereits UTC und Notwendigkeit ist es in der lokalen Zeit zu konvertieren und dann fällt die Zeitzone: from tzlocal import get_localzone, tz_here = get_localzone(),<datetime object>.tz_convert(tz_here).tz_localize(None)
Nathan Lloyd
3
Wenn Sie keinen nützlichen Index haben, benötigen Sie möglicherweise t.dt.tz_localize(None)oder t.dt.tz_convert(None). Beachten Sie die .dt.
Acumenus
1
Diese Lösung funktioniert nur, wenn es ein eindeutiges tz in der Serie gibt. Wenn Sie mehrere verschiedene tz in derselben Serie haben, dann sehen (und verbessern) Sie die Lösung hier :-): stackoverflow.com/a/59204751/1054154
tozCSS
14

Ich denke, Sie können nicht effizienter erreichen, als Sie vorgeschlagen haben.

Das zugrunde liegende Problem ist, dass die Zeitstempel (wie Sie scheinen) aus zwei Teilen bestehen. Die Daten, die die UTC-Zeit und die Zeitzone tz_info darstellen. Die Zeitzoneninformationen werden nur zu Anzeigezwecken verwendet, wenn die Zeitzone auf dem Bildschirm gedruckt wird. Zur Anzeigezeit werden die Daten entsprechend versetzt und +01: 00 (oder ähnlich) zur Zeichenfolge hinzugefügt. Durch das Entfernen des Werts tz_info (mit tz_convert (tz = None)) werden die Daten, die den naiven Teil des Zeitstempels darstellen, nicht geändert.

Die einzige Möglichkeit, das zu tun, was Sie möchten, besteht darin, die zugrunde liegenden Daten zu ändern (Pandas lassen dies nicht zu ... DatetimeIndex sind unveränderlich - siehe die Hilfe zu DatetimeIndex) oder einen neuen Satz von Zeitstempelobjekten zu erstellen und diese zu verpacken in einem neuen DatetimeIndex. Ihre Lösung macht Letzteres:

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

Als Referenz ist hier die replaceMethode von Timestamp(siehe tslib.pyx):

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

Sie können anhand der Dokumente datetime.datetimesehen, dass datetime.datetime.replaceauch ein neues Objekt erstellt wird.

Wenn Sie können, ist es für die Effizienz am besten, die Datenquelle so zu ändern, dass die Zeitstempel (fälschlicherweise) ohne ihre Zeitzone gemeldet werden. Du hast erwähnt:

Ich möchte mit zeitzonen-naiven Zeitreihen arbeiten (um den zusätzlichen Aufwand mit Zeitzonen zu vermeiden, und ich brauche sie nicht für den Fall, an dem ich arbeite).

Ich wäre gespannt, auf welchen zusätzlichen Ärger Sie sich beziehen. Ich empfehle in der Regel für alle Softwareentwicklungen, den Zeitstempel "naive Werte" in UTC beizubehalten. Es gibt kaum etwas Schlimmeres, als zwei verschiedene int64-Werte zu betrachten und sich zu fragen, zu welcher Zeitzone sie gehören. Wenn Sie immer, immer, immer UTC für den internen Speicher verwenden, vermeiden Sie unzählige Kopfschmerzen. Mein Mantra lautet: Zeitzonen sind nur für menschliche E / A vorgesehen .

DA
quelle
3
Vielen Dank für die Antwort und eine späte Antwort: Mein Fall ist keine Bewerbung, sondern nur eine wissenschaftliche Analyse für meine eigene Arbeit (z. B. kein Austausch mit Mitarbeitern auf der ganzen Welt). In diesem Fall kann es einfacher sein, nur mit naiven Zeitstempeln zu arbeiten, aber in Ihrer Ortszeit. Ich muss mich also nicht um Zeitzonen kümmern und kann den Zeitstempel einfach als Ortszeit interpretieren (der zusätzliche Aufwand kann z. B. sein, dass sich alles in Zeitzonen befinden muss, sonst erhalten Sie Dinge wie "Offset kann nicht verglichen werden". naive und Offset-fähige Datenzeiten "). Bei komplexeren Anwendungen stimme ich Ihnen jedoch voll und ganz zu.
Joris
7

Das tzexplizite Festlegen des Indexattributs scheint zu funktionieren:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None
Filmemacher
quelle
3
Später Kommentar, aber ich möchte, dass das Ergebnis die Zeit ist, die in der lokalen Zeitzone dargestellt wird, nicht in UTC. Und wie ich in der Frage zeige, tzkonvertiert das Setzen von "Keine" es auch in UTC.
Joris
Darüber hinaus ist die Zeitreihe bereits zeitzonenbewusst, sodass ein Aufruf tz_converteinen Fehler auslöst .
Joris
7

Weil ich immer Schwierigkeiten habe, mich zu erinnern, eine kurze Zusammenfassung dessen, was jeder von ihnen tut:

>>> 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')
Juan A. Navarro
quelle
3

Aufbauend auf dem Vorschlag von DA, dass " die einzige Möglichkeit, das zu tun, was Sie wollen, darin besteht, die zugrunde liegenden Daten zu ändern " und numpy verwendet, um die zugrunde liegenden Daten zu ändern ...

Das funktioniert bei mir und ist ziemlich schnell:

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)
Jack Kelly
quelle
Danke für deine Antwort! Ich denke jedoch, dass dies nur funktionieren wird, wenn es im Zeitraum des Datensatzes keinen Sommer- / Winterzeitübergang gibt.
Joris
@joris Ah, guter Fang! Daran hatte ich nicht gedacht! Ich werde meine Lösung so schnell wie möglich ändern, um diese Situation zu bewältigen.
Jack Kelly
Ich glaube, das ist immer noch falsch, da Sie nur den Versatz des ersten Males berechnen und nicht den Fortschritt im Laufe der Zeit. Dies führt dazu, dass Sie die Sommerzeit verpassen und an diesem Datum und danach nicht mehr entsprechend anpassen.
Pierre-Luc Bertrand
3

Die akzeptierte Lösung funktioniert nicht, wenn eine Serie mehrere verschiedene Zeitzonen enthält. Es wirftValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

Die Lösung besteht darin, die applyMethode zu verwenden.

Bitte beachten Sie die folgenden Beispiele:

# 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]
tozCSS
quelle
1

Der späte Beitrag stieß jedoch in Python datetime auf etwas Ähnliches, und Pandas geben unterschiedliche Zeitstempel für dasselbe Datum an .

Wenn Sie eine zeitzonenbezogene Datumszeit in pandashaben , tz_localize(None)ändert sich der POSIX-Zeitstempel (der intern verwendet wird) technisch so, als ob die Ortszeit vom Zeitstempel UTC wäre. Lokal bedeutet in diesem Zusammenhang lokal in der angegebenen Zeitzone . Ex:

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

Beachten Sie, dass dies bei Sommerzeitübergängen zu merkwürdigen Dingen führt , z

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

Im Gegensatz dazu wird tz_convert(None)der interne Zeitstempel nicht geändert, sondern nur der entfernt tzinfo.

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

Mein Fazit wäre: Halten Sie sich an die zeitzonenbezogene Datums- und Uhrzeitangabe, wenn Sie t.tz_convert(None)den zugrunde liegenden POSIX-Zeitstempel verwenden können oder nur, wenn er nicht geändert wird . Denken Sie daran, dass Sie dann praktisch mit UTC arbeiten.

(Python 3.8.2 x64 unter Windows 10, pandasv1.0.5.)

MrFuppes
quelle
0

Das Wichtigste ist das Hinzufügen, tzinfowenn Sie ein Datum / Uhrzeit-Objekt definieren.

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())
Yuchao Jiang
quelle