web-dev-qa-db-fra.com

Conversion de DateTimeIndex sensible au fuseau horaire des pandas en un horodatage naïf, mais dans certains fuseaux horaires

Vous pouvez utiliser la fonction tz_localize pour créer un timestone Timestamp ou DateTimeIndex, mais comment faire le contraire: comment convertir un horodatage sensible en fuseau horaire en naïf, tout en préservant son fuseau horaire?

Un exemple:

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

Je pouvais supprimer le fuseau horaire en le réglant sur Aucun, mais le résultat est ensuite converti en UTC (12 heures devient 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

Existe-t-il un autre moyen de convertir un DateTimeIndex en un fuseau horaire naïf, tout en préservant le fuseau horaire dans lequel il a été défini?


Certains contexte sur la raison pour laquelle je pose cette question: je veux travailler avec des séries temporelles naïves de fuseaux horaires (pour éviter les tracas supplémentaires liés aux fuseaux horaires, et je n’en ai pas besoin pour le cas sur lequel je travaille).
Mais pour une raison quelconque, je dois gérer une série de périodes prenant en compte le fuseau horaire dans mon fuseau horaire local (Europe/Bruxelles). Comme toutes mes autres données sont naïves dans le fuseau horaire (mais représentées dans mon fuseau horaire local), je souhaite convertir cette série temporelle en naïve pour pouvoir continuer à l'utiliser, mais elle doit également être représentée dans mon fuseau horaire local (supprimez donc simplement les informations de fuseau horaire, sans convertir l’heure visible par l’utilisateur en heure UTC). 

Je sais que l'heure est stockée en interne au format UTC et qu'elle est convertie dans un autre fuseau horaire uniquement lorsque vous le représentez. Il doit donc y avoir une sorte de conversion lorsque je souhaite le "délocaliser". Par exemple, avec le module python datetime, vous pouvez "supprimer" le fuseau horaire comme ceci:

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> 

Donc, sur cette base, je pourrais faire ce qui suit, mais je suppose que cela ne sera pas très efficace si vous travaillez avec une série temporelle plus longue:

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

Pour répondre à ma propre question, cette fonctionnalité a été ajoutée aux pandas entre-temps. À partir de de pandas 0.15.0, vous pouvez utiliser tz_localize(None) pour supprimer le fuseau horaire, ce qui donne l'heure locale.
Voir la rubrique whatsnew: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

Donc, avec mon exemple d'en haut:

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

utiliser tz_localize(None) supprime les informations de fuseau horaire, ce qui donne heure locale naïve:

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

De plus, vous pouvez également utiliser tz_convert(None) pour supprimer les informations de fuseau horaire mais convertir en UTC, donnant ainsi heure UTC naïve:

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

C'est beaucoup plus performant que la solution 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
53
joris

Je pense que vous ne pouvez pas atteindre ce que vous voulez d'une manière plus efficace que celle que vous aviez proposée.

Le problème sous-jacent est que les horodatages (comme vous semblez le savoir) se composent de deux parties. Les données représentant l'heure UTC et le fuseau horaire tz_info. Les informations de fuseau horaire sont utilisées uniquement à des fins d'affichage lors de l'impression du fuseau horaire à l'écran. Au moment de l'affichage, les données sont décalées de manière appropriée et +01: 00 (ou similaire) est ajouté à la chaîne. Supprimer la valeur de tz_info (utiliser tz_convert (tz = None)) ne modifie pas réellement les données représentant la partie naïve de l'horodatage. 

Donc, la seule façon de faire ce que vous voulez est de modifier les données sous-jacentes (les pandas ne le permettent pas ... DatetimeIndex sont immuables - voir l'aide sur DatetimeIndex) ou de créer un nouvel ensemble d'objets horodatés et de les envelopper. dans un nouveau DatetimeIndex. Votre solution fait ce dernier:

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

Pour référence, voici la méthode replace de Timestamp (voir tslib.pyx):

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

Vous pouvez vous référer aux docs sur datetime.datetime pour constater que datetime.datetime.replace crée également un nouvel objet. 

Si vous le pouvez, votre meilleur pari pour l'efficacité est de modifier la source des données afin qu'elle rapporte (de manière incorrecte) les horodatages sans leur fuseau horaire. Vous avez mentionné:

Je veux travailler avec des séries temporelles naïves de fuseaux horaires (pour éviter les tracas supplémentaires liés aux fuseaux horaires, et je n'en ai pas besoin pour le cas sur lequel je travaille)

Je serais curieux de savoir à quel tracas supplémentaire vous faites allusion. Je recommande en règle générale, pour tout développement de logiciel, de conserver les «valeurs naïves» de votre horodatage en UTC. Il n’ya guère de pire que de regarder deux valeurs int64 différentes en se demandant à quel fuseau horaire elles appartiennent. Si vous utilisez toujours, toujours, toujours le temps UTC pour le stockage interne, vous éviterez ainsi d'innombrables maux de tête. Mon mantra est Les fuseaux horaires sont réservés aux E/S humaines .

13
D. A.

La définition de l'attribut tz de l'index semble fonctionner explicitement:

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

Le plus important est d’ajouter tzinfo lorsque vous définissez un objet datetime.

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

S'appuyant sur la suggestion de D.A. selon laquelle " la seule façon de faire ce que vous voulez est de modifier les données sous-jacentes " et d'utiliser numpy pour modifier les données sous-jacentes ...

Cela fonctionne pour moi et est assez rapide:

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)
0
Jack Kelly