我在前言中说我有一个有效的方法,但我希望优化和学习更多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种或更多种不同类型的案例需要处理。
答案 0 :(得分:1)
让我帮你解决问题中的一些逻辑,实现部分应该很简单,无论是Python / Pandas还是其他编程语言。
见下图,我连续1-2天将窗口划分为6个区域8AM
和8PM
(取决于调整后的到达时间和出发时间,我将在下面讨论) ):
+---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 )然后
同时设置:
p8
与ts
同一天但晚上8点a8
与te
同一天,但上午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)