Las pandas convierten datetime con una columna de zona horaria separada

Tengo un dataframe con una columna para la zona horaria y una columna para la fecha y hora. Me gustaría convertirlos a UTC primero para unirme a otros datos, y luego tendré algunos cálculos para convertirlos de UTC a la zona horaria local del espectador eventualmente.

datetime time_zone 2016-09-19 01:29:13 America/Bogota 2016-09-19 02:16:04 America/New_York 2016-09-19 01:57:54 Africa/Cairo def create_utc(df, column, time_format='%Y-%m-%d %H:%M:%S'): timezone = df['TZ'] df[column + '_utc'] = df[column].dt.tz_localize(timezone).dt.tz_convert('UTC').dt.strftime(time_format) df[column + '_utc'].replace('NaT', np.nan, inplace=True) df[column + '_utc'] = pd.to_datetime(df[column + '_utc']) return df 

Ese fue mi bash fallido. El error es que la verdad es ambigua, lo que tiene sentido porque la variable ‘zona horaria’ se refiere a una columna. ¿Cómo me refiero al valor en la misma fila?

Edición: aquí hay algunos resultados de las respuestas a continuación en un día de datos (394,000 filas y 22 zonas horarias únicas). Edit2: Agregué un ejemplo de grupo en caso de que alguien quiera ver los resultados. Es el más rápido, de lejos.

 %%timeit for tz in df['TZ'].unique(): df.ix[df['TZ'] == tz, 'datetime_utc2'] = df.ix[df['TZ'] == tz, 'datetime'].dt.tz_localize(tz).dt.tz_convert('UTC') df['datetime_utc2'] = df['datetime_utc2'].dt.tz_localize(None) 

1 loops, best of 3: 1.27 s per loop

 %%timeit df['datetime_utc'] = [d['datetime'].tz_localize(d['TZ']).tz_convert('UTC') for i, d in df.iterrows()] df['datetime_utc'] = df['datetime_utc'].dt.tz_localize(None) 

1 loops, best of 3: 50.3 s per loop

 df['datetime_utc'] = pd.concat([d['datetime'].dt.tz_localize(tz).dt.tz_convert('UTC') for tz, d in df.groupby('TZ')]) **1 loops, best of 3: 249 ms per loop** 

Aquí hay un enfoque vectorizado (se repetirá df.time_zone.nunique() veces):

 In [2]: t Out[2]: datetime time_zone 0 2016-09-19 01:29:13 America/Bogota 1 2016-09-19 02:16:04 America/New_York 2 2016-09-19 01:57:54 Africa/Cairo 3 2016-09-19 11:00:00 America/Bogota 4 2016-09-19 12:00:00 America/New_York 5 2016-09-19 13:00:00 Africa/Cairo In [3]: for tz in t.time_zone.unique(): ...: mask = (t.time_zone == tz) ...: t.loc[mask, 'datetime'] = \ ...: t.loc[mask, 'datetime'].dt.tz_localize(tz).dt.tz_convert('UTC') ...: In [4]: t Out[4]: datetime time_zone 0 2016-09-19 06:29:13 America/Bogota 1 2016-09-19 06:16:04 America/New_York 2 2016-09-18 23:57:54 Africa/Cairo 3 2016-09-19 16:00:00 America/Bogota 4 2016-09-19 16:00:00 America/New_York 5 2016-09-19 11:00:00 Africa/Cairo 

ACTUALIZAR:

 In [12]: df['new'] = df.groupby('time_zone')['datetime'] \ .transform(lambda x: x.dt.tz_localize(x.name)) In [13]: df Out[13]: datetime time_zone new 0 2016-09-19 01:29:13 America/Bogota 2016-09-19 06:29:13 1 2016-09-19 02:16:04 America/New_York 2016-09-19 06:16:04 2 2016-09-19 01:57:54 Africa/Cairo 2016-09-18 23:57:54 3 2016-09-19 11:00:00 America/Bogota 2016-09-19 16:00:00 4 2016-09-19 12:00:00 America/New_York 2016-09-19 16:00:00 5 2016-09-19 13:00:00 Africa/Cairo 2016-09-19 11:00:00 

Su problema es que tz_localize() solo puede tomar un valor escalar, por lo que tendremos que iterar a través del DataFrame:

 df['datetime_utc'] = [d['datetime'].tz_localize(d['time_zone']).tz_convert('UTC') for i,d in df.iterrows()] 

El resultado es:

  datetime time_zone datetime_utc 0 2016-09-19 01:29:13 America/Bogota 2016-09-19 06:29:13+00:00 1 2016-09-19 02:16:04 America/New_York 2016-09-19 06:16:04+00:00 2 2016-09-19 01:57:54 Africa/Cairo 2016-09-18 23:57:54+00:00 

Un enfoque alternativo es agrupar por zona horaria y convertir todas las filas coincidentes en una sola pasada:

 df['datetime_utc'] = pd.concat([d['datetime'].dt.tz_localize(tz).dt.tz_convert('UTC') for tz, d in df.groupby('time_zone')])