【问题标题】:Speed up datetime conversion to mixed time zones - Python pandas加快日期时间转换为混合时区 - Python pandas
【发布时间】:2021-02-23 01:21:40
【问题描述】:

注意:这是this问题的后续。

问题摘要:我有一个带有 UNIX 时间戳为整数的 pandas 数据框,没有任何时间信息。我需要将这些转换为特定的时区(然后使它们成为时区天真的对象)。问题在于,将这种转换作为对每一行的迭代进行是非常密集的,目前占我处理时间的大约 60%(在这个简化的示例中甚至更多)。我相信这可以通过使用额外的熊猫日期时间功能来减少,但我很难弄清楚如何做到这一点。为了扩大规模,我需要对数千个文件运行代码,每个文件都有几百万个观察值。

示例:

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

输出:

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

要点: setTZ() 函数非常慢。我认为这是因为我在代码上逐行迭代以进行这种转换。 to_datetime() 非常快。如果有一种方法可以合并时区并失去时间感知(因为我将同时比较不同时区的观察结果),那将是理想的。与 to_datetime() 函数相比,创建日期和时间列慢,但相对于 setTZ() 函数快。优化这些会很好。

可能的解决方案: 我猜我可以利用熊猫的一些日期时间函数,例如 tz_localize() 和 tz_convert(),但我需要能够将我的熊猫数据框的列转换为日期时间数组。我不清楚我该怎么做。我敢肯定还有其他解决方案。

【问题讨论】:

  • 我认为这是个好问题;关键是本地化到某个时区(不是从纪元到日期时间数据类型的秒数转换)。但是,我怀疑您是否可以“矢量化”操作:对于每个时间戳-时区组合,您需要在 tz 数据库中特定查找 UTC 偏移量和 DST 设置。由于这两个参数都受政治决策的影响,因此它们会随着时间而变化。所以没有办法为每个时间戳专门查找(如果你想保持程序普遍适用)。
  • 旁注:查看timeit 模块以比较计算速度
  • 旁注#2:刚刚注意到您想分别提取日期和时间;如果您采用简单的 datetime 列(没有混合时区),您可以分别通过 df['datetime'].dt.datedf['datetime'].dt.time 获得更快的速度(如果您在 datetime 列中全部包含这些列,请检查您是否真的需要将它们作为单独的列)。另见dt accessor
  • @MrFuppes 需要的实际输出分别是日期和时间列(我将删除 date_time 列)。我的目标输出具有原始 UNIX 列以及日期和时间列。也许有一种更快的方法可以将 UNIX 列简单地转换为这两列,这样我就可以跳过创建这个中间的 date_time 列?
  • 自纪元以来的 UNIX 秒数不区分日期和时间(它只是一个数字):没有。

标签: python pandas datetime timezone


【解决方案1】:

给定一个如上所述的数据框并扩展到中等 50k 行

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)

除了迭代使用 pandas 内置的 tz_convert 之外,您还可以使用带有 pandas 的 itertuples + Python 的 datetimezoneinfo 的列表推导:

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()]

在直接比较中,对于合成示例 df,list comp 的性能要好到 ~x8

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

【讨论】:

  • 这工作得更快。不幸的是,它似乎没有将时间转换为时区。我稍微更改了localTime_dt() 函数,使其接受时区参数:def localTime_dt(df,tzone): return [datetime.fromtimestamp(row.utc, tz=ZoneInfo(tzone)).replace(tzinfo=None) for row in df.itertuples()] 如果我在同一数据帧的副本上测试它,但使用不同的tzone,我会得到两个数据帧的相同结果输出,尽管他们有不同的时区
  • @amquack:是的,.replace(tzinfo=None) 删除了时区信息。它只是将值保留在该时区的正确时间。如果您最终想要一个单独的日期和时间列,那么时区基本上是无关紧要的。去掉.replace(tzinfo=None),看看有什么变化……
  • 忽略我所拥有的关于它不起作用的 cmets - 我发现了我如何测试它的错误。我认为现在一切正常!
猜你喜欢
  • 2016-01-27
  • 2016-03-21
  • 1970-01-01
  • 2018-02-08
  • 2016-10-11
  • 2019-08-23
  • 1970-01-01
  • 2018-10-30
相关资源
最近更新 更多