首页 > 解决方案 > 有效计算可能发生不同情况的两个系列之间的时间增量

问题描述

我正在使用包含到达和离开数据的大约 100 万行的数据集,转换来自 HTML 表的原始数据,并且在计算预定和实际出发之间的时间差时遇到了问题。下表显示了四种不同的时差情况,我需要一种快速计算时间增量的方法,转换为分钟,可以同时考虑所有这些情况。我目前使用的可以正确处理 4 种情况中的 3 种。

数据有两个日期/时间列,格式如下表所示,第三列是使用当前技术计算时间增量的结果(本文后面的代码)。

|          Sch Dp          | Act Dp  |  Diff  |
|--------------------------|---------|--------|
| 02/24/2014 10:22 PM (Mo) |         | NaN    |
| 02/25/2014 10:22 PM (Tu) | 10:24PM | 2.0    |
| 02/26/2014 10:22 PM (We) | 12:53AM | 151.0  |
| 11/02/2010 4:36 AM (Tu)  | 4:13AM  | 1417.0 |

上面说明的四种主要情况:

第 1 行:( 缺少数据案例)实际出发列由于取消而缺少数据(在 df 中的其他地方表示)

第 2 行:(正常情况)实际出发时间与预定出发时间相同,准时或晚于预定出发时间

第 3 行:(Depart Next Day Case)实际出发时间较晚,但出发日期更改,没有正式指示

第 4 行:(Depart Before Scheduled Case)实际出发时间比预定时间早几分钟

我遇到的问题是,由于实际出发列中没有给出日期,因此确定案例 3 和 4 的时间差更加复杂。我目前有以下代码对加载到数据框中的原始数据进行操作,它适用于案例 1-3,但不适用于案例 4。

sch_time =  pd.to_datetime(df['Sch Dp'], format='%I:%M %p', exact=False, errors='coerce')
act_time = pd.to_datetime(df['Act Dp'], format='%I:%M%p', exact=False, errors='coerce')
    
time_diff = pd.to_timedelta(act_time - sch_time,  errors='coerce') 
time_diff = time_diff - pd.to_timedelta(time_diff.dt.days, unit='d')
new_df['Diff'] =(60 * (time_diff.dt.days * 24 + time_diff.dt.seconds // 3600) + (time_diff.dt.seconds % 3600) // 60)

有没有办法以一种相对简单且计算效率高的方式来处理这样的事情?我可能可以编写一个函数来执行此操作并使用 pd.series.apply(),但是根据我在尝试解决此问题时所阅读和体验的内容,.apply() 非常慢并且出于方便而包含在内但不应该成为首选解决方案。由于我的数据框有近 100 万行,我预计 .apply() 不会是最佳的,甚至不会很快。我的猜测是必须有一种方法可以更有效地做到这一点。

(想知道战略数学计算是否有可能,可能是模数或绝对值,但实验一直在产生错误的结果。)

更新: 由于我还没有收到回复,我写了这个(功能但不优雅)函数,但我无法弄清楚如何将它与 .apply() 一起使用。它考虑了各个列(我将“Sch Dp”拆分为完整的日期+时间(没有星期几)、仅日期、仅时间,并将所有数据类型转换为适当的格式。

有人可以建议吗?

def calc_diff(full_sched, sched_date, sched_time, act_time):
    if pd.isnull(act_time):
        return np.nan
    else:
        if sched_time > pd.to_datetime('12:00:00').time():
            act_datetime = pd.Timestamp.combine(sched_date, act_time)
            if act_datetime < full_sched:
                act_datetime = pd.to_datetime(act_datetime) + pd.Timedelta(1, unit='day')
        else: 
            act_datetime = pd.Timestamp.combine(sched_date, act_time) 
        time_diff = pd.to_timedelta(act_datetime - full_sched) 
        time_diff = time_diff.total_seconds() // 60
    return time_diff

标签: pythonpandasdataframedatetimetimedelta

解决方案


如果我正确理解你的问题,你需要设置一个你期望延迟的时间增量范围(负/太早出发以及正/晚出发)。您可以使用它来确定是否应将一天添加到“实际出发”列(如您的示例中的第 3 行)或不(如您的示例中的第 4 行)。

# departure, slice of the day name and to datetime...
df['dep'] = pd.to_datetime(df['Sch Dp'].str[:-4])

# use date of scheduled departure, and time from actual departure.
# set specific format and errors=coerce so that the empty string gives NaT.
df['adep'] = pd.to_datetime(df['dep'].dt.date.astype(str)+ " "+df['Act D'], 
                            format='%Y-%m-%d %I:%M%p', errors='coerce')

# set the expected delay, derive a boolean mask from that.
max_expected_delay = pd.Timedelta(hours=4)
delta = df['adep']-df['dep']
m_late = (delta < max_expected_delay) & (max_expected_delay*-1 > delta)
m_early = (delta*-1 < max_expected_delay) & (max_expected_delay*-1 > delta*-1)

# add (or remove) a day if actual departure falls within expected range
df.loc[m_late, 'adep'] += pd.Timedelta(days=1)
df.loc[m_early, 'adep'] -= pd.Timedelta(days=1)

df['diff[min]'] = (df['adep']-df['dep']).dt.total_seconds()/60

#                      Sch Dp    Act D  ...                adep diff[min]
# 0  02/24/2014 10:22 PM (Mo)           ...                 NaT       NaN
# 1  02/25/2014 10:22 PM (Tu)  10:24PM  ... 2014-02-25 22:24:00       2.0
# 2  02/26/2014 10:22 PM (We)  12:53AM  ... 2014-02-27 00:53:00     151.0
# 3   11/02/2010 4:36 AM (Tu)   4:13AM  ... 2010-11-02 04:13:00     -23.0
# 4  11/02/2010 12:13 AM (Tu)  11:56PM  ... 2010-11-01 23:56:00     -17.0

推荐阅读