有效计算可能发生不同情况的两个系列之间的时间增量

Efficient calculation of timedeltas between two series where different cases can occur

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

数据有两个date/time列,格式如下table,第三列是用当前技术计算timedeltas的结果(代码后面post).

|          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 行:(第二天出发案例)实际出发时间较晚,但出发日期在没有正式指示的情况下发生变化

第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

如果我对你的问题理解正确,你需要设置一个 timedelta 范围,在该范围内你预计延迟(负/过早离开以及正/晚离开)。您可以使用它来确定是否应将某一天添加​​到“实际出发”列(如您的示例中的第 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