Accelera la conversione datetime in fusi orari misti: panda Python

Nov 11 2020

Nota: questo è un seguito a questa domanda.

Riepilogo del problema: ho un dataframe panda con timestamp UNIX come numeri interi senza alcuna informazione sull'ora. Devo convertirli in fusi orari specifici (e quindi renderli oggetti ingenui del fuso orario). Il problema è che eseguire questa conversione come iterazione su ogni riga è piuttosto impegnativo e attualmente rappresenta circa il 60% del mio tempo di elaborazione (anche di più in questo esempio semplificato). Credo che questo possa essere ridotto utilizzando la funzionalità datetime aggiuntiva dei panda, ma ho difficoltà a capire come farlo. Per dare scala, ho bisogno di eseguire il codice su migliaia di file che hanno un paio / pochi milioni di osservazioni ciascuno.

Esempio:

import pandas as pd
import time

#creating data:
n_obs=750000 # need to be a multiple of 15

l1=[1546555701, 1546378818, 1546574677, 1546399159, 1546572278]
l2=['America/Detroit','America/Chicago','America/Los_Angeles']
c1=l1*(int(n_obs/5))
c2=l2*(int(n_obs/3))

df=pd.DataFrame(list(zip(c1,c2)),columns=['timestamp','tz'])

print(df)

# operations:
sort_dict={}
tz_list=df['tz'].unique()

for x in tz_list:
    df_temp=df[df['tz']==x]
    sort_dict[x]=df_temp

def setTZ(row,x):
    return row['date_time'].tz_convert(x).replace(tzinfo=None)
    
for x in [tz_list[0]]: # I just time the first iteration of the loop for simplicity
    tic = time.perf_counter()
    sort_dict[x]['date_time']=pd.to_datetime(df['timestamp'],unit='s',utc=True)
    toc = time.perf_counter()
    print(f'to_datetime() completed in {toc-tic:0.4f} seconds')
    
    # the above works quite quickly, but the problem is in the following lines:
    tic = time.perf_counter()
    sort_dict[x]['date_time']=sort_dict[x].apply(lambda row: setTZ(row,x), axis=1)
    toc = time.perf_counter()
    print(f'setTZ() completed in {toc-tic:0.4f} seconds')

    tic = time.perf_counter()
    sort_dict[x]['date']=sort_dict[x].apply(lambda row: row['date_time'].date(),axis=1)
    toc = time.perf_counter()
    print(f'create date column with .date() completed in {toc-tic:0.4f} seconds')

    tic = time.perf_counter()
    sort_dict[x]['time']=sort_dict[x].apply(lambda row: row['date_time'].time(),axis=1)
    toc = time.perf_counter()
    print(f'create time column with .time() completed in {toc-tic:0.4f} seconds')

Produzione:

to_datetime() completed in 0.0311 seconds
setTZ() completed in 26.3287 seconds
create date column with .date() completed in 3.2471 seconds
create time column with .time() completed in 3.2625 seconds
# I also have a SettingWithCopyWarning error from my code, which I think comes from how I'm overwriting the dictionaries

Conclusioni : la funzione setTZ () è incredibilmente lenta. Penso che ciò sia dovuto al fatto che sto iterando riga per riga sul codice per effettuare questa conversione. to_datetime () è estremamente veloce. Se ci fosse un modo per incorporare il fuso orario e perdere il tempo consapevole (dal momento che confronterò le osservazioni allo stesso tempo tra i fusi orari) sarebbe l'ideale. La creazione delle colonne di data e ora è lenta rispetto alla funzione to_datetime (), ma rapida rispetto alla funzione setTZ (). Ottimizzarli sarebbe bello.

Possibile soluzione: immagino di poter sfruttare alcune delle funzioni datetime di panda, come tz_localize () e tz_convert (), ma devo essere in grado di convertire la colonna del mio dataframe panda in un array datetime. Non mi è chiaro come potrei farlo. Sono sicuro che esistono anche altre soluzioni.

Risposte

1 MrFuppes Nov 11 2020 at 14:24

Dato un dataframe come descritto ed esteso a un moderato 50k righe

from datetime import datetime
from backports.zoneinfo import ZoneInfo # backports not needed with Python 3.9
import pandas as pd

c1 = [1546555701, 1546378818, 1546574677, 1546399159, 1546572278]*10000
c2 = ['America/Detroit','America/Chicago','America/Los_Angeles','America/Los_Angeles','America/Detroit']*10000
df3 = pd.DataFrame({'utc': c1, 'tz': c2})

df3['datetime'] = pd.to_datetime(df3['utc'], unit='s', utc=True)

oltre a usare i panda integrati in modo tz_convertiterativo, potresti anche usare una comprensione delle liste con itertuple di panda + datetime & zoneinfo di Python :

def toLocalTime_pd(row): # as given
    return row['datetime'].tz_convert(row['tz']).replace(tzinfo=None)

def localTime_dt(df):
    return [datetime.fromtimestamp(row.utc, tz=ZoneInfo(row.tz)).replace(tzinfo=None) for row in df.itertuples()]

Nel confronto diretto, l' elenco comp funziona meglio di ~ x8 per l'esempio sintetico df:

%timeit df3.apply(lambda r: toLocalTime_pd(r), axis=1)
1.85 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit localTime_dt(df3)
217 ms ± 7.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)