优化方法(对于数据帧)以查找与指定小时范围重叠的时间范围

时间:2018-05-01 21:50:17

标签: python pandas dataframe

我在前言中说我有一个有效的方法,但我希望优化和学习更多Pythonic方法来处理DataFrames。

前提如下:我有多次访问"由用户到某个位置。这些范围可以是任何日期时间的任何日期时间,但按时间顺序发生:

Jan 1, 15:00 to Jan 1, 18:35 
Jan 3, 09:12 to Jan 5, 10:54 
Jan 5, 11:00 to Jan 6, 19:48
etc.

现在,我将这些到达和离开时间设置为DataFrame,并且我希望确定用户在每天晚上8点到早上8点之间花费的总时间。

我目前的方法是在每一行上应用自定义函数:

def find_8am_8pm_hours(t1, t2):
    if t1 > t2:
        raise Exception('t1 must be before t2')
    total = dt.timedelta(minutes=0)
    while t1 < t2:
        t1 += dt.timedelta(minutes=1)
        if (t1.time() < dt.time(8, 0)) or (t1.time() > dt.time(20, 0)):
            total += dt.timedelta(minutes=1)
    return total 

并将其应用于DataFrame:

df['Time Spent 8am-8pm'] = df.apply(lambda row: find_8am_8pm_hours(row['Arrival Time'], row['Departure Time']), axis=1)

我最初在几秒钟内编写了具有粒度的函数,但实际上甚至需要一些时间来运行非常小的数据集(对于只有~20行的数据集,运行时间为几秒)。一旦我将近似值改为分钟,小数据集就会很快运行,但我想,对于大数据集,算法需要很长时间。

我知道while循环是罪魁祸首,但我无法想到任何更优雅的方法。我还考虑了if / else语句来处理时间重叠的特定情况,但是要处理24 + hr范围,将会有20种或更多种不同类型的案例需要处理。

2 个答案:

答案 0 :(得分:1)

让我帮你解决问题中的一些逻辑,实现部分应该很简单,无论是Python / Pandas还是其他编程语言。

见下图,我连续1-2天将窗口划分为6个区域8AM8PM(取决于调整后的到达时间和出发时间,我将在下面讨论) ):

            +---day1--+---day2--+
            |   z1    |   z4    |
            +---------+---------+<-- 8AM (a8)
            |   z2    |   z5    |
(p8) 8PM -->+---------+---------+
            |   z3    |   z6    |
            +---------+---------+

首先我们计算两个时间戳 t1 t2 之间的 delta_in_days , 每个单独的三角洲日将为您提供额外12小时的最终总数。

delta_in_days 添加到到达时间,以便我们可以专注于1天(24小时)帧内的窗口。假设 ts 是调整后的到达时间,而 te 是出发时间,(注意:我最初将它们定义为开始时间和结束时间,因此命名为< em> ts 和 te )然后

  • ts = t1 + delta_in_days
  • te = t2

同时设置:

  • p8ts同一天但晚上8点
  • a8te同一天,但上午8点

下面列出伪代码的可能情况:

案例1:

同一天

ts te - 基本上在 day2 p8 > a8

if both in the same zone: z4(te < a8) or z6(ts > p8): 
    total = te - ts
else:
    total = max(0, te - p8) + max(0, a8 - ts)

案例2:

ts te 在不同的日子里,如果在z6中 te ,那么 ts 必须在z3中。请记住,在调整后的到达时间后, ts te 必须在24小时内。

if te > p8 + 1day:
    total = (te - p8 - 1day) + (a8 - ts)

案例3:

ts te 在不同日期,如果 ts 在z1中,则 te 必须在z4 < / p>

if ts < a8 - 1day
    total = (a8 - 1day - ts) + (te - p8)

案例4:

[z2,z3]中的

ts ,[z4,z5] te

total = min(a8, te) - max(p8, ts)  

Python中的代码:

import pandas as pd
from io import StringIO

str="""Jan 1, 15:00 to Jan 1, 18:35 
Jan 3, 09:12 to Jan 5, 10:54 
Jan 5, 21:00 to Jan 6, 23:48
Jan 5, 23:00 to Jan 6, 20:48
Jan 5, 03:00 to Jan 6, 02:48
Jan 5, 10:00 to Jan 6, 05:48
Jan 5, 21:00 to Jan 6, 10:48
"""

df = pd.read_table(StringIO(str)
     , sep='\s*to\s*'
     , engine='python'
     , names=['t1','t2']
)

for field in ['t1', 't2']:
    df[field] = pd.to_datetime(df[field], format="%b %d, %H:%M")

delta_1_day = pd.Timedelta('1 days')
# add 12 hours for each delta_1_day
ns_spent_in_1_day = int(delta_1_day.value*12/24)

# the total time is counted in nano seconds
def count_off_hour_in_ns(x):
    t1 = x['t1']
    t2 = x['t2']

    # number of days from t1 to t2
    delta_days = (t2 - t1).days
    if delta_days <= 0:
        return 0

    # add delta_days to start-time so ts and te in 1-day window
    # define the start-time(ts) and end-time(te) of the window
    ts = t1 + pd.Timedelta('{} days'.format(delta_days))
    te = t2

    # 8PM the same day as ts
    p8 = ts.replace(hour=20, minute=0, second=0)

    # 8AM the same day as te
    a8 = te.replace(hour=8, minute=0, second=0)

    # Case-1: te and ts on the same day
    if p8 > a8:
        if te < a8 or ts > p8:
            total = (te - ts).value
        else:
            total = max(0, (te - p8).value) + max(0, (a8 - ts).value)
    # Below ts and te all in different days
    # Case-2: te in z6
    elif te > p8 + delta_1_day:
        total = (te - p8 - delta_1_day + a8 - ts).value
    # Case-3: ts in z1
    elif ts < a8 - delta_1_day:
        total = (a8 - delta_1_day - ts + te - p8).value
    # Case-4: other cases
    else:
        total = (min(te, a8) - max(ts, p8)).value

    return total + delta_days * ns_spent_in_1_day

df['total'] = df.apply(count_off_hour_in_ns, axis=1)

print(df)

                   t1                  t2           total
0 1900-01-01 15:00:00 1900-01-01 18:35:00               0
1 1900-01-03 09:12:00 1900-01-05 10:54:00  86400000000000
2 1900-01-05 21:00:00 1900-01-06 23:48:00  53280000000000
3 1900-01-05 23:00:00 1900-01-06 20:48:00  35280000000000
4 1900-01-05 03:00:00 1900-01-06 02:48:00  42480000000000
5 1900-01-05 10:00:00 1900-01-06 05:48:00  35280000000000
6 1900-01-05 21:00:00 1900-01-06 10:48:00  39600000000000

让我知道这是否有效。

答案 1 :(得分:0)

我想到的方法是让函数将每个时间范围分成24小时块(通过在晚上8点分割来切割每个时间范围)。每24小时一次,只能有3个类别:

  1. 早上8点前抵达,早上8点出发(抵达〜出发)
  2. 早上8点前抵达,早上8点出发(抵达~8点)
  3. 上午8点(0小时)后到达
  4. schedule

    然后简单地将每个24小时的块组合在一起。

    这样,该函数最多只进行几次算术运算,而不是每天迭代60 * 60 * 24 = 86,400次数据。

相关问题