167 votes

Convertir l'index DateTimeIndex de pandas, qui tient compte du fuseau horaire, en un horodatage naïf, mais dans un certain fuseau horaire.

Vous pouvez utiliser la fonction tz_localize pour rendre un Timestamp ou un DateTimeIndex conscient du fuseau horaire, mais comment faire le contraire : comment convertir un Timestamp conscient du fuseau horaire en un Timestamp 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 pourrais supprimer le fuseau horaire en lui attribuant la valeur None, mais le résultat est alors converti en UTC (12 heures deviennent 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 fuseau horaire naïf, mais en conservant le fuseau horaire dans lequel il a été défini ?


Quelques contexte sur la raison pour laquelle je demande ça : Je veux travailler avec des séries temporelles sans fuseau horaire (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 traiter une série temporelle dans mon fuseau horaire local (Europe/Bruxelles). Comme toutes mes autres données ne tiennent pas compte du fuseau horaire (mais sont représentées dans mon fuseau horaire local), je veux convertir cette série temporelle en série naïve pour pouvoir continuer à travailler avec elle, mais elle doit également être représentée dans mon fuseau horaire local (il suffit donc de supprimer les informations relatives au fuseau horaire, sans convertir la série temporelle en série naïve). visible par l'utilisateur l'heure en UTC).

Je sais que l'heure est en fait stockée en interne en UTC et qu'elle n'est convertie en un autre fuseau horaire que lorsque vous la représentez, il doit donc y avoir une sorte de conversion lorsque je veux la "délocaliser". Par exemple, avec le module python datetime, vous pouvez "supprimer" le fuseau horaire de la manière suivante :

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> 

Sur cette base, je pourrais faire ce qui suit, mais je suppose que cela ne sera pas très efficace si je travaille avec une série chronologique plus importante :

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

203voto

joris Points 10700

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

Donc, avec mon exemple ci-dessus :

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

en utilisant tz_localize(None) supprime les informations relatives au 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')

En outre, vous pouvez également utiliser tz_convert(None) pour enlever l'information sur le fuseau horaire mais en convertissant en UTC, ce qui donne heure naïve 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')

C'est beaucoup plus performant que le datetime.replace solution :

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

52voto

Juan A. Navarro Points 1768

Parce que j'ai toujours du mal à m'en souvenir, voici un résumé rapide de ce que fait chacun d'entre eux :

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

19voto

D. A. Points 807

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

Le problème sous-jacent est que les horodateurs (comme vous semblez le savoir) sont constitués de deux parties. Les données qui représentent l'heure UTC, et le fuseau horaire, tz_info. L'information sur le fuseau horaire n'est utilisée qu'à 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. La suppression de la valeur tz_info (en utilisant tz_convert(tz=None)) ne modifie pas réellement les données qui représentent la partie naïve de l'horodatage.

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

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

Pour référence, voici le replace méthode 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 documents sur datetime.datetime pour voir que datetime.datetime.replace crée également un nouvel objet.

Si vous le pouvez, votre meilleure chance d'efficacité est de modifier la source des données afin qu'elle rapporte (incorrectement) les horodatages sans leur fuseau horaire. Vous l'avez mentionné :

Je veux travailler avec des séries temporelles naïves (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 à quels tracas supplémentaires vous faites référence. Je recommande, comme règle générale pour tout développement de logiciel, de conserver les "valeurs naïves" de vos timestamp en UTC. Il n'y a rien de pire que de regarder deux valeurs int64 différentes en se demandant à quel fuseau horaire elles appartiennent. Si vous utilisez toujours, toujours, toujours l'UTC pour le stockage interne, vous éviterez d'innombrables maux de tête. Mon mantra est le suivant Les fuseaux horaires sont uniquement destinés aux E/S humaines .

12voto

oztalha Points 53

La solution acceptée ne fonctionne pas lorsqu'il y a plusieurs fuseaux horaires différents dans une série. Elle rejette ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

La solution consiste à utiliser le apply méthode.

Veuillez consulter les exemples ci-dessous :

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

8voto

filmor Points 7439

Fixer le tz de l'index de manière explicite semble fonctionner :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X