Puede usar la función tz_localize
para hacer que una marca de tiempo o una zona horaria DateTimeIndex sea consciente, pero ¿cómo puede hacer lo contrario?
Un ejemplo:
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
Podría eliminar la zona horaria configurándola en Ninguno, pero luego el resultado se convierte a UTC (las 12 en punto se convirtieron en 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
¿Hay otra forma de convertir un DateTimeIndex en una zona horaria ingenua, pero conservando la zona horaria en la que se estableció?
Un poco de contexto sobre la razón por la que pregunto esto: quiero trabajar con series de tiempo ingenuas de zonas horarias (para evitar la molestia adicional con las zonas horarias, y no las necesito para el caso en el que estoy trabajando).
Pero por alguna razón, tengo que lidiar con una serie horaria consciente de la zona horaria en mi zona horaria local (Europa / Bruselas). Como todos mis otros datos son ingenuos de zona horaria (pero están representados en mi zona horaria local), quiero convertir esta serie temporal en ingenuo para seguir trabajando con ellos, pero también tiene que estar representado en mi zona horaria local (así que simplemente elimine la información de la zona horaria, sin convertir la hora visible para el usuario a UTC).
Sé que la hora está almacenada internamente como UTC y solo se convierte a otra zona horaria cuando la representa, por lo que tiene que haber algún tipo de conversión cuando quiero "deslocalizarla". Por ejemplo, con el módulo de fecha y hora de Python puede "eliminar" la zona horaria de esta manera:
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>
Entonces, basado en esto, podría hacer lo siguiente, pero supongo que esto no será muy eficiente cuando trabaje con una serie temporal más grande:
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
replace
.tz_localize
que es lo que lareplace(tzinfo=None)
hace por datetimes, pero de hecho no es una manera muy obvia.Respuestas:
Para responder a mi propia pregunta, esta funcionalidad se ha agregado a los pandas mientras tanto. A partir de pandas 0.15.0 , puede usar
tz_localize(None)
para eliminar la zona horaria que da como resultado la hora local.Vea la nueva entrada: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements
Entonces, con mi ejemplo de arriba:
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')
el uso
tz_localize(None)
elimina la información de la zona horaria, lo que da como resultado una hora local ingenua :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')
Además, también puede usar
tz_convert(None)
para eliminar la información de la zona horaria pero convirtiéndola a UTC, lo que produce una hora UTC ingenua :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')
Esto es mucho más eficaz que la
datetime.replace
solución: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
fuente
from tzlocal import get_localzone
,tz_here = get_localzone()
,<datetime object>.tz_convert(tz_here).tz_localize(None)
t.dt.tz_localize(None)
ot.dt.tz_convert(None)
. Tenga en cuenta el.dt
.Creo que no puede lograr lo que quiere de una manera más eficiente de lo que propuso.
El problema subyacente es que las marcas de tiempo (como parece saber) se componen de dos partes. Los datos que representan la hora UTC y la zona horaria, tz_info. La información de la zona horaria se utiliza solo con fines de visualización cuando se imprime la zona horaria en la pantalla. En el momento de la visualización, los datos se compensan adecuadamente y se agrega +01: 00 (o similar) a la cadena. Eliminar el valor tz_info (usando tz_convert (tz = None)) no cambia en realidad los datos que representan la parte ingenua de la marca de tiempo.
Entonces, la única forma de hacer lo que desea es modificar los datos subyacentes (los pandas no permiten esto ... DatetimeIndex son inmutables; consulte la ayuda en DatetimeIndex), o crear un nuevo conjunto de objetos de marca de tiempo y envolverlos en un nuevo DatetimeIndex. Tu solución hace lo último:
pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Como referencia, aquí está el
replace
método deTimestamp
(ver tslib.pyx):def replace(self, **kwds): return Timestamp(datetime.replace(self, **kwds), offset=self.offset)
Puede consultar los documentos en
datetime.datetime
para ver quedatetime.datetime.replace
también crea un nuevo objeto.Si puede, su mejor apuesta por la eficiencia es modificar la fuente de los datos para que informe (incorrectamente) las marcas de tiempo sin su zona horaria. Mencionaste:
Tendría curiosidad a qué molestia adicional te refieres. Recomiendo, como regla general para todo el desarrollo de software, mantener los 'valores ingenuos' de la marca de tiempo en UTC. No hay nada peor que mirar dos valores int64 diferentes preguntándose a qué zona horaria pertenecen. Si siempre, siempre, siempre utiliza UTC para el almacenamiento interno, evitará innumerables dolores de cabeza. Mi mantra es que las zonas horarias son solo para E / S humanas .
fuente
Porque siempre me cuesta recordar, un resumen rápido de lo que hace cada uno de estos:
>>> 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')
fuente
Establecer el
tz
atributo del índice explícitamente parece funcionar:ts_utc = ts.tz_convert("UTC") ts_utc.index.tz = None
fuente
tz
en Ninguno también lo convierte a UTC.tz_convert
generará un error.La solución aceptada no funciona cuando hay varias zonas horarias diferentes en una serie. Lanza
ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True
La solución es utilizar el
apply
método.Consulte los ejemplos siguientes:
# 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]
fuente
Sobre la base de la sugerencia de DA de que " la única forma de hacer lo que quiere es modificar los datos subyacentes " y usar numpy para modificar los datos subyacentes ...
Esto funciona para mí y es bastante rápido:
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)
fuente
Contribución tardía, pero acaba de encontrar algo similar en Python datetime y los pandas dan diferentes marcas de tiempo para la misma fecha .
Si tiene una fecha y hora consciente de la zona horaria
pandas
, técnicamente,tz_localize(None)
cambia la marca de tiempo POSIX (que se usa internamente) como si la hora local de la marca de tiempo fuera UTC. Local en este contexto significa local en la zona horaria especificada . 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]')
Tenga en cuenta que esto lo dejará con cosas extrañas durante las transiciones de DST , por ejemplo
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')
Por el contrario,
tz_convert(None)
no modifica la marca de tiempo interna, solo elimina eltzinfo
.t_utc = t.tz_convert(None) (t_utc.values-t.values)//1e9 # array([0, 0], dtype='timedelta64[ns]')
Mi conclusión sería: quédese con la fecha y hora consciente de la zona horaria si puede o solo use la
t.tz_convert(None)
que no modifique la marca de tiempo POSIX subyacente. Solo tenga en cuenta que prácticamente está trabajando con UTC entonces.(Python 3.8.2 x64 en Windows 10,
pandas
v1.0.5.)fuente
Lo más importante es agregar
tzinfo
cuando define un objeto de fecha y hora.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())
fuente